Skip to content

Commit 139ad22

Browse files
authored
Merge pull request #1967 from streamr-dev/FRONT-1544-project-stats
[FRONT-1544] Add stream stats badge to project listing tiles
2 parents 2753ace + f0764d1 commit 139ad22

File tree

5 files changed

+154
-53
lines changed

5 files changed

+154
-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: 58 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,59 @@ 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-weight: 500;
149+
font-size: 16px;
150+
line-height: 24px;
151+
color: #323232;
152+
153+
a,
154+
a:link,
155+
a:active,
156+
a:focus,
157+
a:hover,
158+
a:visited {
159+
color: white !important;
160+
}
161+
162+
> * + * {
163+
margin-left: 8px;
164+
}
165+
`
166+
167+
interface StreamStatsBadgeProps extends Omit<BadgeContainerProps, 'children'> {
168+
streamIds: string[]
169+
}
170+
171+
const StreamStatsBadge = ({ streamIds, ...props }: StreamStatsBadgeProps) => {
172+
const { data: stats, isLoading, error } = useMultipleStreamStatsQuery(streamIds)
173+
174+
if (error || isLoading) {
175+
return null
176+
}
177+
178+
const messagesPerSecond = stats?.messagesPerSecond
179+
const formattedMsgRate =
180+
typeof messagesPerSecond === 'number' ? messagesPerSecond.toFixed(1) : 'N/A'
181+
182+
return (
183+
<BadgeContainer {...props}>
184+
<StatsBadge>
185+
<span>
186+
{streamIds.length} {streamIds.length === 1 ? 'Stream' : 'Streams'}
187+
</span>
188+
<span>{formattedMsgRate} Msg/s</span>
189+
</StatsBadge>
190+
</BadgeContainer>
191+
)
192+
}
193+
194+
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)