Skip to content

Commit 46ff22e

Browse files
committed
Add stream stats badge to project listing tiles
1 parent 79a30f6 commit 46ff22e

File tree

5 files changed

+148
-53
lines changed

5 files changed

+148
-53
lines changed

src/components/Stats.tsx

Lines changed: 2 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import { useQuery } from '@tanstack/react-query'
21
import React, { ReactNode } from 'react'
32
import styled from 'styled-components'
4-
import { getIndexerClient } from '~/getters/getGraphClient'
5-
import {
6-
GetStreamsDocument,
7-
GetStreamsQuery,
8-
GetStreamsQueryVariables,
9-
} from '../generated/gql/indexer'
3+
import { defaultStreamStats } from '~/getters/getStreamStats'
4+
import { useStreamStatsQuery } from '~/hooks/useStreamStats'
105

116
type StatProps = {
127
id: string
@@ -88,53 +83,10 @@ const ButtonGrid = styled.div`
8883
}
8984
`
9085

91-
function useStreamStatsQuery(streamId: string) {
92-
return useQuery({
93-
queryKey: ['useStreamStatsQuery', streamId],
94-
queryFn: async () => {
95-
const client = getIndexerClient(137)
96-
97-
if (!client) {
98-
return defaultStreamStats
99-
}
100-
101-
const {
102-
data: { streams },
103-
} = await client.query<GetStreamsQuery, GetStreamsQueryVariables>({
104-
query: GetStreamsDocument,
105-
variables: {
106-
streamIds: [streamId],
107-
first: 1,
108-
},
109-
})
110-
111-
const [stream = undefined] = streams.items
112-
113-
if (!stream) {
114-
return null
115-
}
116-
117-
const { messagesPerSecond, peerCount } = stream
118-
119-
return {
120-
latency: undefined as undefined | number,
121-
messagesPerSecond,
122-
peerCount,
123-
}
124-
},
125-
})
126-
}
127-
12886
interface StreamStatsProps {
12987
streamId: string
13088
}
13189

132-
const defaultStreamStats = {
133-
latency: undefined,
134-
messagesPerSecond: undefined,
135-
peerCount: undefined,
136-
}
137-
13890
export function StreamStats({ streamId }: StreamStatsProps) {
13991
const { data: stats } = useStreamStatsQuery(streamId)
14092

src/getters/getStreamStats.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { getIndexerClient } from '~/getters/getGraphClient'
2+
import {
3+
GetStreamsDocument,
4+
GetStreamsQuery,
5+
GetStreamsQueryVariables,
6+
} from '../generated/gql/indexer'
7+
8+
export const defaultStreamStats = {
9+
latency: undefined,
10+
messagesPerSecond: undefined,
11+
peerCount: undefined,
12+
}
13+
14+
export const getStreamStats = async (streamId: string) => {
15+
const client = getIndexerClient(137)
16+
17+
if (!client) {
18+
return defaultStreamStats
19+
}
20+
21+
const {
22+
data: { streams },
23+
} = await client.query<GetStreamsQuery, GetStreamsQueryVariables>({
24+
query: GetStreamsDocument,
25+
variables: {
26+
streamIds: [streamId],
27+
first: 1,
28+
},
29+
})
30+
31+
const [stream = undefined] = streams.items
32+
33+
if (!stream) {
34+
return null
35+
}
36+
37+
const { messagesPerSecond, peerCount } = stream
38+
39+
return {
40+
latency: undefined as undefined | number,
41+
messagesPerSecond,
42+
peerCount,
43+
}
44+
}

src/hooks/useStreamStats.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useQuery } from '@tanstack/react-query'
2+
import { getStreamStats } from '~/getters/getStreamStats'
3+
4+
type StreamStats = {
5+
latency: number | undefined
6+
messagesPerSecond: number
7+
peerCount: number
8+
}
9+
10+
export function useStreamStatsQuery(streamId: string) {
11+
return useQuery({
12+
queryKey: ['useStreamStatsQuery', streamId],
13+
queryFn: async () => {
14+
return getStreamStats(streamId)
15+
},
16+
})
17+
}
18+
19+
export function useMultipleStreamStatsQuery(streamIds: string[]) {
20+
return useQuery({
21+
queryKey: ['useMultipleStreamStatsQuery', streamIds],
22+
queryFn: async () => {
23+
const stats = (await Promise.all(
24+
streamIds.map(getStreamStats),
25+
)) as StreamStats[]
26+
return stats.reduce(
27+
(acc: StreamStats, curr: StreamStats) => ({
28+
// For latency, we can take the average of non-undefined values
29+
latency:
30+
acc.latency === undefined && curr.latency === undefined
31+
? undefined
32+
: ((acc.latency || 0) + (curr.latency || 0)) /
33+
(acc.latency !== undefined && curr.latency !== undefined
34+
? 2
35+
: 1),
36+
messagesPerSecond: acc.messagesPerSecond + curr.messagesPerSecond,
37+
peerCount: acc.peerCount + curr.peerCount,
38+
}),
39+
{
40+
latency: undefined,
41+
messagesPerSecond: 0,
42+
peerCount: 0,
43+
},
44+
)
45+
},
46+
})
47+
}

src/shared/components/Tile/Badge.tsx

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { ReactNode } from 'react'
22
import styled, { css } from 'styled-components'
33
import Link from '~/shared/components/Link'
4+
import { useMultipleStreamStatsQuery } from '~/hooks/useStreamStats'
45

56
const SingleBadge = styled.div`
67
display: flex;
@@ -26,6 +27,7 @@ const SingleBadge = styled.div`
2627
margin-left: 8px;
2728
}
2829
`
30+
2931
type BadgeContainerProps = {
3032
children: ReactNode
3133
top?: boolean
@@ -134,4 +136,53 @@ const DataUnionBadge = ({
134136

135137
const BadgeLink = ({ ...props }) => <Link {...props} />
136138

137-
export { DataUnionBadge }
139+
const StatsBadge = styled.div`
140+
display: flex;
141+
align-items: center;
142+
padding: 4px 8px;
143+
gap: 10px;
144+
background: rgba(245, 245, 247, 0.6);
145+
backdrop-filter: blur(13.3871px);
146+
border-radius: 8px;
147+
148+
font-family: 'IBM Plex Sans';
149+
font-style: normal;
150+
font-weight: 500;
151+
font-size: 16px;
152+
line-height: 24px;
153+
color: #525252;
154+
155+
a,
156+
a:link,
157+
a:active,
158+
a:focus,
159+
a:hover,
160+
a:visited {
161+
color: white !important;
162+
}
163+
164+
> * + * {
165+
margin-left: 8px;
166+
}
167+
`
168+
169+
interface StreamStatsBadgeProps extends Omit<BadgeContainerProps, 'children'> {
170+
streamIds: string[]
171+
}
172+
173+
const StreamStatsBadge = ({ streamIds, ...props }: StreamStatsBadgeProps) => {
174+
const stats = useMultipleStreamStatsQuery(streamIds)
175+
176+
return (
177+
<BadgeContainer {...props}>
178+
<StatsBadge>
179+
<span>
180+
{streamIds.length} {streamIds.length === 1 ? 'stream' : 'streams'}
181+
</span>
182+
<span>{stats.data?.messagesPerSecond.toFixed(1) ?? 'N/A'} msg/s</span>
183+
</StatsBadge>
184+
</BadgeContainer>
185+
)
186+
}
187+
188+
export { DataUnionBadge, StreamStatsBadge }

src/shared/components/Tile/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { useCurrentChainId } from '~/utils/chains'
1313
import { Route as R, routeOptions } from '~/utils/routes'
1414
import { useCurrentChainSymbolicName } from '~/utils/chains'
1515
import Summary from './Summary'
16-
import { DataUnionBadge } from './Badge'
16+
import { DataUnionBadge, StreamStatsBadge } from './Badge'
1717

1818
const Image = styled(Img)`
1919
img& {
@@ -212,10 +212,11 @@ function MarketplaceProductTile({
212212
/>
213213
</TileImageContainer>
214214
</Link>
215+
<StreamStatsBadge top left streamIds={product.streams} />
215216
{!!showDataUnionBadge && (
216217
<DataUnionBadge
217218
top
218-
left
219+
right
219220
linkTo={R.projectOverview(
220221
product.id,
221222
routeOptions(chainName, undefined, 'stats'),

0 commit comments

Comments
 (0)