diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx index 0f55a8c28c..0fa6a78e45 100644 --- a/src/components/Stats.tsx +++ b/src/components/Stats.tsx @@ -1,12 +1,7 @@ -import { useQuery } from '@tanstack/react-query' import React, { ReactNode } from 'react' import styled from 'styled-components' -import { getIndexerClient } from '~/getters/getGraphClient' -import { - GetStreamsDocument, - GetStreamsQuery, - GetStreamsQueryVariables, -} from '../generated/gql/indexer' +import { defaultStreamStats } from '~/getters/getStreamStats' +import { useStreamStatsQuery } from '~/hooks/useStreamStats' type StatProps = { id: string @@ -88,53 +83,10 @@ const ButtonGrid = styled.div` } ` -function useStreamStatsQuery(streamId: string) { - return useQuery({ - queryKey: ['useStreamStatsQuery', streamId], - queryFn: async () => { - const client = getIndexerClient(137) - - if (!client) { - return defaultStreamStats - } - - const { - data: { streams }, - } = await client.query({ - query: GetStreamsDocument, - variables: { - streamIds: [streamId], - first: 1, - }, - }) - - const [stream = undefined] = streams.items - - if (!stream) { - return null - } - - const { messagesPerSecond, peerCount } = stream - - return { - latency: undefined as undefined | number, - messagesPerSecond, - peerCount, - } - }, - }) -} - interface StreamStatsProps { streamId: string } -const defaultStreamStats = { - latency: undefined, - messagesPerSecond: undefined, - peerCount: undefined, -} - export function StreamStats({ streamId }: StreamStatsProps) { const { data: stats } = useStreamStatsQuery(streamId) diff --git a/src/getters/getStreamStats.ts b/src/getters/getStreamStats.ts new file mode 100644 index 0000000000..fddaf68368 --- /dev/null +++ b/src/getters/getStreamStats.ts @@ -0,0 +1,44 @@ +import { getIndexerClient } from '~/getters/getGraphClient' +import { + GetStreamsDocument, + GetStreamsQuery, + GetStreamsQueryVariables, +} from '../generated/gql/indexer' + +export const defaultStreamStats = { + latency: undefined, + messagesPerSecond: undefined, + peerCount: undefined, +} + +export const getStreamStats = async (streamId: string) => { + const client = getIndexerClient(137) + + if (!client) { + return defaultStreamStats + } + + const { + data: { streams }, + } = await client.query({ + query: GetStreamsDocument, + variables: { + streamIds: [streamId], + first: 1, + }, + }) + + const [stream = undefined] = streams.items + + if (!stream) { + return null + } + + const { messagesPerSecond, peerCount } = stream + + return { + latency: undefined as undefined | number, + messagesPerSecond, + peerCount, + } +} diff --git a/src/hooks/useStreamStats.tsx b/src/hooks/useStreamStats.tsx new file mode 100644 index 0000000000..31b54ae8ac --- /dev/null +++ b/src/hooks/useStreamStats.tsx @@ -0,0 +1,47 @@ +import { useQuery } from '@tanstack/react-query' +import { getStreamStats } from '~/getters/getStreamStats' + +type StreamStats = { + latency: number | undefined + messagesPerSecond: number + peerCount: number +} + +export function useStreamStatsQuery(streamId: string) { + return useQuery({ + queryKey: ['useStreamStatsQuery', streamId], + queryFn: async () => { + return getStreamStats(streamId) + }, + }) +} + +export function useMultipleStreamStatsQuery(streamIds: string[]) { + return useQuery({ + queryKey: ['useMultipleStreamStatsQuery', streamIds], + queryFn: async () => { + const stats = (await Promise.all( + streamIds.map(getStreamStats), + )) as StreamStats[] + return stats.reduce( + (acc: StreamStats, curr: StreamStats) => ({ + // For latency, we can take the average of non-undefined values + latency: + acc.latency === undefined && curr.latency === undefined + ? undefined + : ((acc.latency || 0) + (curr.latency || 0)) / + (acc.latency !== undefined && curr.latency !== undefined + ? 2 + : 1), + messagesPerSecond: acc.messagesPerSecond + curr.messagesPerSecond, + peerCount: acc.peerCount + curr.peerCount, + }), + { + latency: undefined, + messagesPerSecond: 0, + peerCount: 0, + }, + ) + }, + }) +} diff --git a/src/shared/components/Tile/Badge.tsx b/src/shared/components/Tile/Badge.tsx index f2d8dd1ff0..ff7041ffae 100644 --- a/src/shared/components/Tile/Badge.tsx +++ b/src/shared/components/Tile/Badge.tsx @@ -1,6 +1,7 @@ import React, { ReactNode } from 'react' import styled, { css } from 'styled-components' import Link from '~/shared/components/Link' +import { useMultipleStreamStatsQuery } from '~/hooks/useStreamStats' const SingleBadge = styled.div` display: flex; @@ -26,6 +27,7 @@ const SingleBadge = styled.div` margin-left: 8px; } ` + type BadgeContainerProps = { children: ReactNode top?: boolean @@ -134,4 +136,59 @@ const DataUnionBadge = ({ const BadgeLink = ({ ...props }) => -export { DataUnionBadge } +const StatsBadge = styled.div` + display: flex; + align-items: center; + padding: 4px 8px; + gap: 10px; + background: rgba(245, 245, 247, 0.6); + backdrop-filter: blur(13.3871px); + border-radius: 8px; + + font-weight: 500; + font-size: 16px; + line-height: 24px; + color: #323232; + + a, + a:link, + a:active, + a:focus, + a:hover, + a:visited { + color: white !important; + } + + > * + * { + margin-left: 8px; + } +` + +interface StreamStatsBadgeProps extends Omit { + streamIds: string[] +} + +const StreamStatsBadge = ({ streamIds, ...props }: StreamStatsBadgeProps) => { + const { data: stats, isLoading, error } = useMultipleStreamStatsQuery(streamIds) + + if (error || isLoading) { + return null + } + + const messagesPerSecond = stats?.messagesPerSecond + const formattedMsgRate = + typeof messagesPerSecond === 'number' ? messagesPerSecond.toFixed(1) : 'N/A' + + return ( + + + + {streamIds.length} {streamIds.length === 1 ? 'Stream' : 'Streams'} + + {formattedMsgRate} Msg/s + + + ) +} + +export { DataUnionBadge, StreamStatsBadge } diff --git a/src/shared/components/Tile/index.tsx b/src/shared/components/Tile/index.tsx index de0ef8277e..e2c1e582cf 100644 --- a/src/shared/components/Tile/index.tsx +++ b/src/shared/components/Tile/index.tsx @@ -13,7 +13,7 @@ import { useCurrentChainId } from '~/utils/chains' import { Route as R, routeOptions } from '~/utils/routes' import { useCurrentChainSymbolicName } from '~/utils/chains' import Summary from './Summary' -import { DataUnionBadge } from './Badge' +import { DataUnionBadge, StreamStatsBadge } from './Badge' const Image = styled(Img)` img& { @@ -212,10 +212,11 @@ function MarketplaceProductTile({ /> + {!!showDataUnionBadge && (