From 90e242bc07b2d0fcf74ff92d7f75fc5d475fa24d Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Tue, 27 Jun 2023 12:24:43 +0200 Subject: [PATCH] filter operations by client name (#2487) --- .../src/modules/operations/module.graphql.ts | 1 + .../providers/operations-manager.ts | 62 ++++- .../operations/providers/operations-reader.ts | 63 ++++- .../api/src/modules/operations/resolvers.ts | 34 ++- packages/services/api/src/shared/mappers.ts | 1 + .../[projectId]/[targetId]/operations.tsx | 14 +- .../components/target/operations/Filters.tsx | 247 ++++++++++++++++++ .../src/components/target/operations/List.tsx | 21 ++ .../components/target/operations/Stats.tsx | 3 + .../graphql/query.operations-stats.graphql | 6 + scripts/seed-local-env.ts | 12 +- 11 files changed, 429 insertions(+), 35 deletions(-) diff --git a/packages/services/api/src/modules/operations/module.graphql.ts b/packages/services/api/src/modules/operations/module.graphql.ts index 82098d7dc..9b84c89c8 100644 --- a/packages/services/api/src/modules/operations/module.graphql.ts +++ b/packages/services/api/src/modules/operations/module.graphql.ts @@ -16,6 +16,7 @@ export default gql` target: ID! period: DateRangeInput! operations: [ID!] + clientNames: [String!] } input OperationBodyByHashInput { diff --git a/packages/services/api/src/modules/operations/providers/operations-manager.ts b/packages/services/api/src/modules/operations/providers/operations-manager.ts index aca91bdcd..bce7c6770 100644 --- a/packages/services/api/src/modules/operations/providers/operations-manager.ts +++ b/packages/services/api/src/modules/operations/providers/operations-manager.ts @@ -127,7 +127,12 @@ export class OperationsManager { target, period, operations, - }: { period: DateRange; operations?: readonly string[] } & TargetSelector) { + clients, + }: { + period: DateRange; + operations?: readonly string[]; + clients?: readonly string[]; + } & TargetSelector) { this.logger.info('Counting unique operations (period=%o, target=%s)', period, target); await this.authManager.ensureTargetAccess({ organization, @@ -140,6 +145,7 @@ export class OperationsManager { target, period, operations, + clients, }); } @@ -165,7 +171,11 @@ export class OperationsManager { target, period, operations, - }: { period: DateRange; operations?: readonly string[] } & Listify) { + clients, + }: { period: DateRange; operations?: readonly string[]; clients?: readonly string[] } & Listify< + TargetSelector, + 'target' + >) { this.logger.info('Counting requests (period=%s, target=%s)', period, target); await this.authManager.ensureTargetAccess({ organization, @@ -179,6 +189,7 @@ export class OperationsManager { target, period, operations, + clients, }) .then(r => r.total); } @@ -189,7 +200,12 @@ export class OperationsManager { target, period, operations, - }: { period: DateRange; operations?: readonly string[] } & TargetSelector) { + clients, + }: { + period: DateRange; + operations?: readonly string[]; + clients?: readonly string[]; + } & TargetSelector) { this.logger.info('Counting failures (period=%o, target=%s)', period, target); await this.authManager.ensureTargetAccess({ organization, @@ -202,6 +218,7 @@ export class OperationsManager { target, period, operations, + clients, }); } @@ -311,9 +328,11 @@ export class OperationsManager { project, target, operations, + clients, }: { period: DateRange; operations?: readonly string[]; + clients?: readonly string[]; } & TargetSelector) { this.logger.info('Reading operations stats (period=%o, target=%s)', period, target); await this.authManager.ensureTargetAccess({ @@ -328,6 +347,7 @@ export class OperationsManager { target, period, operations, + clients, }); } @@ -441,10 +461,12 @@ export class OperationsManager { project, target, operations, + clients, }: { period: DateRange; resolution: number; operations?: readonly string[]; + clients?: readonly string[]; } & TargetSelector) { this.logger.info( 'Reading requests over time (period=%o, resolution=%s, target=%s)', @@ -464,6 +486,7 @@ export class OperationsManager { period, resolution, operations, + clients, }); } @@ -474,10 +497,12 @@ export class OperationsManager { project, target, operations, + clients, }: { period: DateRange; resolution: number; operations?: readonly string[]; + clients?: readonly string[]; } & TargetSelector) { this.logger.info( 'Reading failures over time (period=%o, resolution=%s, target=%s)', @@ -497,6 +522,7 @@ export class OperationsManager { period, resolution, operations, + clients, }); } @@ -507,10 +533,12 @@ export class OperationsManager { project, target, operations, + clients, }: { period: DateRange; resolution: number; operations?: readonly string[]; + clients?: readonly string[]; } & TargetSelector) { this.logger.info( 'Reading duration over time (period=%o, resolution=%s, target=%s)', @@ -530,6 +558,7 @@ export class OperationsManager { period, resolution, operations, + clients, }); } @@ -539,7 +568,12 @@ export class OperationsManager { project, target, operations, - }: { period: DateRange; operations?: readonly string[] } & TargetSelector) { + clients, + }: { + period: DateRange; + operations?: readonly string[]; + clients?: readonly string[]; + } & TargetSelector) { this.logger.info('Reading overall duration percentiles (period=%o, target=%s)', period, target); await this.authManager.ensureTargetAccess({ organization, @@ -552,6 +586,7 @@ export class OperationsManager { target, period, operations, + clients, }); } @@ -562,11 +597,17 @@ export class OperationsManager { project, target, operations, - }: { period: DateRange; operations?: readonly string[] } & TargetSelector) { + clients, + }: { + period: DateRange; + operations?: readonly string[]; + clients?: readonly string[]; + } & TargetSelector) { this.logger.info( - 'Reading detailed duration percentiles (period=%o, target=%s)', + 'Reading detailed duration percentiles (period=%o, target=%s, clientFilter=%s)', period, target, + clients, ); await this.authManager.ensureTargetAccess({ organization, @@ -579,6 +620,7 @@ export class OperationsManager { target, period, operations, + clients, }); } @@ -588,7 +630,12 @@ export class OperationsManager { project, target, operations, - }: { period: DateRange; operations?: readonly string[] } & TargetSelector) { + clients, + }: { + period: DateRange; + operations?: readonly string[]; + clients?: readonly string[]; + } & TargetSelector) { this.logger.info('Reading duration histogram (period=%o, target=%s)', period, target); await this.authManager.ensureTargetAccess({ organization, @@ -601,6 +648,7 @@ export class OperationsManager { target, period, operations, + clients, }); } diff --git a/packages/services/api/src/modules/operations/providers/operations-reader.ts b/packages/services/api/src/modules/operations/providers/operations-reader.ts index 543846468..d39c7a248 100644 --- a/packages/services/api/src/modules/operations/providers/operations-reader.ts +++ b/packages/services/api/src/modules/operations/providers/operations-reader.ts @@ -262,10 +262,12 @@ export class OperationsReader { target, period, operations, + clients, }: { target: string | readonly string[]; period: DateRange; operations?: readonly string[]; + clients?: readonly string[]; }, span?: Span, ): Promise<{ @@ -281,6 +283,7 @@ export class OperationsReader { target, period, operations, + clients, }, )}`, queryId: 'count_operations_daily', @@ -293,6 +296,7 @@ export class OperationsReader { target, period, operations, + clients, }, )}`, queryId: 'count_operations_hourly', @@ -305,6 +309,7 @@ export class OperationsReader { target, period, operations, + clients, }, )}`, queryId: 'count_operations_regular', @@ -335,12 +340,14 @@ export class OperationsReader { target, period, operations, + clients, }: { target: string; period: DateRange; operations?: readonly string[]; + clients?: readonly string[]; }): Promise { - return this.countOperations({ target, period, operations }).then(r => r.notOk); + return this.countOperations({ target, period, operations, clients }).then(r => r.notOk); } @sentry('OperationsReader.countUniqueDocuments') @@ -349,10 +356,12 @@ export class OperationsReader { target, period, operations, + clients, }: { target: string; period: DateRange; operations?: readonly string[]; + clients?: readonly string[]; }, span?: Span, ): Promise { @@ -366,6 +375,7 @@ export class OperationsReader { target, period, operations, + clients, })} `, queryId: 'count_unique_documents_daily', @@ -380,6 +390,7 @@ export class OperationsReader { target, period, operations, + clients, })} `, queryId: 'count_unique_documents_hourly', @@ -394,6 +405,7 @@ export class OperationsReader { target, period, operations, + clients, })} `, queryId: 'count_unique_documents', @@ -417,10 +429,12 @@ export class OperationsReader { target, period, operations, + clients, }: { target: string; period: DateRange; operations?: readonly string[]; + clients?: readonly string[]; }, span?: Span, ): Promise< @@ -443,6 +457,7 @@ export class OperationsReader { target, period, operations, + clients, })} GROUP BY hash `, @@ -461,6 +476,7 @@ export class OperationsReader { target, period, operations, + clients, })} GROUP BY hash `, @@ -476,6 +492,7 @@ export class OperationsReader { target, period, operations, + clients, })} GROUP BY hash `, @@ -1043,17 +1060,20 @@ export class OperationsReader { period, resolution, operations, + clients, }: { target: string; period: DateRange; resolution: number; operations?: readonly string[]; + clients?: readonly string[]; }) { const results = await this.getDurationAndCountOverTime({ target, period, resolution, operations, + clients, }); return results.map(row => ({ @@ -1068,17 +1088,20 @@ export class OperationsReader { period, resolution, operations, + clients, }: { target: string; period: DateRange; resolution: number; operations?: readonly string[]; + clients?: readonly string[]; }) { const result = await this.getDurationAndCountOverTime({ target, period, resolution, operations, + clients, }); return result.map(row => ({ @@ -1093,11 +1116,13 @@ export class OperationsReader { period, resolution, operations, + clients, }: { target: string; period: DateRange; resolution: number; operations?: readonly string[]; + clients?: readonly string[]; }): Promise< Array<{ date: any; @@ -1109,6 +1134,7 @@ export class OperationsReader { period, resolution, operations, + clients, }); } @@ -1118,10 +1144,12 @@ export class OperationsReader { target, period, operations, + clients, }: { target: string; period: DateRange; operations?: readonly string[]; + clients?: readonly string[]; }, span?: Span, ): Promise< @@ -1143,7 +1171,7 @@ export class OperationsReader { ( SELECT log10(duration) as logDuration FROM operations_new - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} ) ORDER BY latency `, @@ -1166,10 +1194,12 @@ export class OperationsReader { target, period, operations, + clients, }: { target: string; period: DateRange; operations?: readonly string[]; + clients?: readonly string[]; }, span?: Span, ): Promise { @@ -1183,7 +1213,7 @@ export class OperationsReader { SELECT quantilesMerge(0.75, 0.90, 0.95, 0.99)(duration_quantiles) as percentiles FROM operations_daily - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} `, queryId: 'general_duration_percentiles_daily', timeout: 15_000, @@ -1194,7 +1224,7 @@ export class OperationsReader { SELECT quantilesMerge(0.75, 0.90, 0.95, 0.99)(duration_quantiles) as percentiles FROM operations_hourly - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} `, queryId: 'general_duration_percentiles_hourly', timeout: 15_000, @@ -1205,7 +1235,7 @@ export class OperationsReader { SELECT quantiles(0.75, 0.90, 0.95, 0.99)(duration) as percentiles FROM operations - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} `, queryId: 'general_duration_percentiles_regular', timeout: 15_000, @@ -1225,10 +1255,12 @@ export class OperationsReader { target, period, operations, + clients, }: { target: string; period: DateRange; operations?: readonly string[]; + clients?: readonly string[]; }, span?: Span, ) { @@ -1244,7 +1276,7 @@ export class OperationsReader { hash, quantilesMerge(0.75, 0.90, 0.95, 0.99)(duration_quantiles) as percentiles FROM operations_daily - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} GROUP BY hash `, queryId: 'duration_percentiles_daily', @@ -1257,7 +1289,7 @@ export class OperationsReader { hash, quantilesMerge(0.75, 0.90, 0.95, 0.99)(duration_quantiles) as percentiles FROM operations_hourly - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} GROUP BY hash `, queryId: 'duration_percentiles_hourly', @@ -1270,7 +1302,7 @@ export class OperationsReader { hash, quantiles(0.75, 0.90, 0.95, 0.99)(duration) as percentiles FROM operations - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} GROUP BY hash `, queryId: 'duration_percentiles_regular', @@ -1319,11 +1351,13 @@ export class OperationsReader { period, resolution, operations, + clients, }: { target: string; period: DateRange; resolution: number; operations?: readonly string[]; + clients?: readonly string[]; }, span?: Span, ) { @@ -1350,7 +1384,8 @@ export class OperationsReader { sum(total) as total, sum(total_ok) as totalOk FROM operations_daily - ${this.createFilter({ target, period, operations })} + }: { + ${this.createFilter({ target, period, operations, clients })} GROUP BY date ORDER BY date `, @@ -1372,7 +1407,7 @@ export class OperationsReader { sum(total) as total, sum(total_ok) as totalOk FROM operations_hourly - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} GROUP BY date ORDER BY date `, @@ -1394,7 +1429,7 @@ export class OperationsReader { count(*) as total, sum(ok) as totalOk FROM operations - ${this.createFilter({ target, period, operations })} + ${this.createFilter({ target, period, operations, clients })} GROUP BY date ORDER BY date `, @@ -1638,11 +1673,13 @@ export class OperationsReader { target, period, operations, + clients, extra = [], }: { target?: string | readonly string[]; period?: DateRange; operations?: readonly string[]; + clients?: readonly string[]; extra?: SqlValue[]; }): SqlValue { const where: SqlValue[] = []; @@ -1666,6 +1703,10 @@ export class OperationsReader { where.push(sql`(hash) IN (${sql.array(operations, 'String')})`); } + if (clients?.length) { + where.push(sql`"client_name" IN (${sql.array(clients, 'String')})`); + } + if (extra.length) { where.push(...extra); } diff --git a/packages/services/api/src/modules/operations/resolvers.ts b/packages/services/api/src/modules/operations/resolvers.ts index 8c33d5a49..865dcb5b7 100644 --- a/packages/services/api/src/modules/operations/resolvers.ts +++ b/packages/services/api/src/modules/operations/resolvers.ts @@ -70,6 +70,10 @@ export const resolvers: OperationsModule.Resolvers = { project, target, operations, + clients: + // TODO: figure out if the mapping should actually happen here :thinking: + selector.clientNames?.map(clientName => (clientName === 'unknown' ? '' : clientName)) ?? + [], }; }, async clientStatsByTargets(_, { selector }, { injector }) { @@ -124,8 +128,8 @@ export const resolvers: OperationsModule.Resolvers = { }, OperationsStats: { async operations( - { organization, project, target, period, operations: operationsFilter }, - _, + { organization, project, target, period, operations: operationsFilter, clients }, + args, { injector }, ) { const operationsManager = injector.get(OperationsManager); @@ -136,6 +140,7 @@ export const resolvers: OperationsModule.Resolvers = { target, period, operations: operationsFilter, + clients, }), operationsManager.readDetailedDurationPercentiles({ organization, @@ -143,6 +148,7 @@ export const resolvers: OperationsModule.Resolvers = { target, period, operations: operationsFilter, + clients, }), ]); @@ -161,17 +167,18 @@ export const resolvers: OperationsModule.Resolvers = { }) .sort((a, b) => b.count - a.count); }, - totalRequests({ organization, project, target, period, operations }, _, { injector }) { + totalRequests({ organization, project, target, period, operations, clients }, _, { injector }) { return injector.get(OperationsManager).countRequests({ organization, project, target, period, operations, + clients, }); }, totalFailures( - { organization, project, target, period, operations: operationsFilter }, + { organization, project, target, period, operations: operationsFilter, clients }, _, { injector }, ) { @@ -181,10 +188,11 @@ export const resolvers: OperationsModule.Resolvers = { target, period, operations: operationsFilter, + clients, }); }, totalOperations( - { organization, project, target, period, operations: operationsFilter }, + { organization, project, target, period, operations: operationsFilter, clients }, _, { injector }, ) { @@ -194,10 +202,11 @@ export const resolvers: OperationsModule.Resolvers = { target, period, operations: operationsFilter, + clients, }); }, requestsOverTime( - { organization, project, target, period, operations: operationsFilter }, + { organization, project, target, period, operations: operationsFilter, clients }, { resolution }, { injector }, ) { @@ -208,10 +217,11 @@ export const resolvers: OperationsModule.Resolvers = { period, resolution, operations: operationsFilter, + clients, }); }, failuresOverTime( - { organization, project, target, period, operations: operationsFilter }, + { organization, project, target, period, operations: operationsFilter, clients }, { resolution }, { injector }, ) { @@ -222,10 +232,11 @@ export const resolvers: OperationsModule.Resolvers = { period, resolution, operations: operationsFilter, + clients, }); }, durationOverTime( - { organization, project, target, period, operations: operationsFilter }, + { organization, project, target, period, operations: operationsFilter, clients }, { resolution }, { injector }, ) { @@ -236,6 +247,7 @@ export const resolvers: OperationsModule.Resolvers = { period, resolution, operations: operationsFilter, + clients, }); }, clients( @@ -252,7 +264,7 @@ export const resolvers: OperationsModule.Resolvers = { }); }, duration( - { organization, project, target, period, operations: operationsFilter }, + { organization, project, target, period, operations: operationsFilter, clients }, _, { injector }, ) { @@ -262,10 +274,11 @@ export const resolvers: OperationsModule.Resolvers = { target, period, operations: operationsFilter, + clients, }); }, async durationHistogram( - { organization, project, target, period, operations: operationsFilter }, + { organization, project, target, period, operations: operationsFilter, clients }, _, { injector }, ) { @@ -275,6 +288,7 @@ export const resolvers: OperationsModule.Resolvers = { target, period, operations: operationsFilter, + clients, }); const uniqueDurations = new Map< diff --git a/packages/services/api/src/shared/mappers.ts b/packages/services/api/src/shared/mappers.ts index 3f99af646..de3217ef9 100644 --- a/packages/services/api/src/shared/mappers.ts +++ b/packages/services/api/src/shared/mappers.ts @@ -204,6 +204,7 @@ export interface OperationsStats { target: string; period: DateRange; operations: readonly string[]; + clients: readonly string[]; } export interface DurationStats { diff --git a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/operations.tsx b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/operations.tsx index 9b23488ae..5b3a948e4 100644 --- a/packages/web/app/pages/[orgId]/[projectId]/[targetId]/operations.tsx +++ b/packages/web/app/pages/[orgId]/[projectId]/[targetId]/operations.tsx @@ -4,7 +4,10 @@ import { formatISO, subDays, subHours, subMinutes } from 'date-fns'; import { useQuery } from 'urql'; import { authenticated } from '@/components/authenticated-container'; import { TargetLayout } from '@/components/layouts/target'; -import { OperationsFilterTrigger } from '@/components/target/operations/Filters'; +import { + ClientsFilterTrigger, + OperationsFilterTrigger, +} from '@/components/target/operations/Filters'; import { OperationsList } from '@/components/target/operations/List'; import { OperationsStats } from '@/components/target/operations/Stats'; import { Subtitle, Title } from '@/components/ui/page'; @@ -43,7 +46,7 @@ function OperationsView({ const selectedPeriod: PeriodKey = (new URLSearchParams(periodParam).get('period') as PeriodKey) ?? '1d'; const [selectedOperations, setSelectedOperations] = useState([]); - + const [selectedClients, setSelectedClients] = useState([]); const period = useMemo(() => { const now = floorDate(new Date()); const sub = selectedPeriod.endsWith('h') ? 'h' : selectedPeriod.endsWith('m') ? 'm' : 'd'; @@ -81,6 +84,11 @@ function OperationsView({ selected={selectedOperations} onFilter={setSelectedOperations} /> + ); diff --git a/packages/web/app/src/components/target/operations/Filters.tsx b/packages/web/app/src/components/target/operations/Filters.tsx index 0e5f9a39e..db8029fdd 100644 --- a/packages/web/app/src/components/target/operations/Filters.tsx +++ b/packages/web/app/src/components/target/operations/Filters.tsx @@ -260,3 +260,250 @@ export function OperationsFilterTrigger({ ); } + +function ClientRow({ + client, + selected, + onSelect, + style, +}: { + client: { name: string; count: number }; + selected: boolean; + onSelect(id: string, selected: boolean): void; + style: any; +}): ReactElement { + const requests = useFormattedNumber(client.count); + const hash = client.name; + const change = useCallback(() => { + if (hash) { + onSelect(hash, !selected); + } + }, [onSelect, hash, selected]); + + return ( +
+ + +
+ ); +} + +function ClientsFilter({ + onClose, + isOpen, + onFilter, + clients, + selected, +}: { + onClose(): void; + onFilter(keys: string[]): void; + isOpen: boolean; + clients: readonly { name: string; count: number }[]; + selected?: string[]; +}): ReactElement { + function getClientNames() { + return clients.map(client => client.name); + } + + const [selectedItems, setSelectedItems] = useState(() => + selected?.length ? selected : getClientNames(), + ); + + const onSelect = useCallback( + (operationHash: string, selected: boolean) => { + const itemAt = selectedItems.findIndex(hash => hash === operationHash); + const exists = itemAt > -1; + + if (selected && !exists) { + setSelectedItems([...selectedItems, operationHash]); + } else if (!selected && exists) { + setSelectedItems(selectedItems.filter(hash => hash !== operationHash)); + } + }, + [selectedItems, setSelectedItems], + ); + const [searchTerm, setSearchTerm] = useState(''); + const debouncedFilter = useDebouncedCallback((value: string) => { + setVisibleOperations( + clients.filter(client => client.name.toLocaleLowerCase().includes(value.toLocaleLowerCase())), + ); + }, 500); + + const onChange = useCallback( + (event: ChangeEvent) => { + const { value } = event.currentTarget; + + setSearchTerm(value); + debouncedFilter(value); + }, + [setSearchTerm, debouncedFilter], + ); + + const [visibleOperations, setVisibleOperations] = useState(clients); + + const selectAll = useCallback(() => { + setSelectedItems(getClientNames()); + }, [clients]); + const selectNone = useCallback(() => { + setSelectedItems([]); + }, [setSelectedItems]); + + const renderRow = useCallback>( + ({ index, style }) => { + const client = visibleOperations[index]; + + return ( + + ); + }, + [visibleOperations, selectedItems, onSelect], + ); + + return ( + + Filter by client + +
+ { + setSearchTerm(''); + setVisibleOperations(clients); + }} + /> +
+ + + + +
+
+ + {({ height, width }) => + !height || !width ? ( + <> + ) : ( + + {renderRow} + + ) + } + +
+
+
+ ); +} + +function ClientsFilterContainer({ + period, + isOpen, + onClose, + onFilter, + selected, +}: { + onFilter(keys: string[]): void; + onClose(): void; + isOpen: boolean; + period: DateRangeInput; + selected?: string[]; +}): ReactElement | null { + const router = useRouteSelector(); + const [query] = useQuery({ + query: OperationsStatsDocument, + variables: { + selector: { + organization: router.organizationId, + project: router.projectId, + target: router.targetId, + period, + operations: [], + }, + }, + }); + + if (!isOpen) { + return null; + } + + if (query.fetching || query.error || !query.data) { + return ; + } + + const allClients = query.data.operationsStats?.clients?.nodes ?? []; + + return ( + { + onFilter(clientNames.length === allClients.length ? [] : clientNames); + }} + /> + ); +} + +export function ClientsFilterTrigger({ + period, + onFilter, + selected, +}: { + period: DateRangeInput; + onFilter(keys: string[]): void; + selected?: string[]; +}): ReactElement { + const [isOpen, toggle] = useToggle(); + + return ( + <> + + + + ); +} diff --git a/packages/web/app/src/components/target/operations/List.tsx b/packages/web/app/src/components/target/operations/List.tsx index 87ef13498..ceb1fb24e 100644 --- a/packages/web/app/src/components/target/operations/List.tsx +++ b/packages/web/app/src/components/target/operations/List.tsx @@ -203,6 +203,9 @@ function OperationsTable({ organization: string; project: string; target: string; + clients: readonly { name: string }[]; + clientFilter: string | null; + setClientFilter: (filter: string) => void; }): ReactElement { const tableInstance = useTableInstance(table, { columns, @@ -235,6 +238,7 @@ function OperationsTable({
Operations List of all operations with their statistics + @@ -324,6 +328,9 @@ function OperationsTableContainer({ organization, project, target, + clients, + clientFilter, + setClientFilter, className, }: { operations: readonly OperationStatsFieldsFragment[]; @@ -331,6 +338,9 @@ function OperationsTableContainer({ organization: string; project: string; target: string; + clients: readonly { name: string }[]; + clientFilter: string | null; + setClientFilter: (client: string) => void; className?: string; }): ReactElement { const data = useMemo(() => { @@ -393,6 +403,9 @@ function OperationsTableContainer({ organization={organization} project={project} target={target} + clients={clients} + clientFilter={clientFilter} + setClientFilter={setClientFilter} /> ); } @@ -404,6 +417,7 @@ export function OperationsList({ target, period, operationsFilter = [], + clientNamesFilter = [], }: { className?: string; organization: string; @@ -411,7 +425,9 @@ export function OperationsList({ target: string; period: DateRangeInput; operationsFilter: readonly string[]; + clientNamesFilter: string[]; }): ReactElement { + const [clientFilter, setClientFilter] = useState(null); const [query, refetch] = useQuery({ query: OperationsStatsDocument, variables: { @@ -421,10 +437,12 @@ export function OperationsList({ target, period, operations: [], + clientNames: clientNamesFilter, }, }, }); const operations = query.data?.operationsStats?.operations?.nodes ?? []; + const clients = query.data?.operationsStats?.clients?.nodes ?? []; return ( ; }): ReactElement { const resolution = 90; const [query, refetchQuery] = useQuery({ @@ -709,6 +711,7 @@ export function OperationsStats({ target, period, operations: operationsFilter, + clientNames: clientNamesFilter, }, resolution, }, diff --git a/packages/web/app/src/graphql/query.operations-stats.graphql b/packages/web/app/src/graphql/query.operations-stats.graphql index 2521fe4b4..b4c3cc199 100644 --- a/packages/web/app/src/graphql/query.operations-stats.graphql +++ b/packages/web/app/src/graphql/query.operations-stats.graphql @@ -6,5 +6,11 @@ query operationsStats($selector: OperationsStatsSelectorInput!) { } total } + clients { + nodes { + name + count + } + } } } diff --git a/scripts/seed-local-env.ts b/scripts/seed-local-env.ts index 50bee3607..fc814e22d 100644 --- a/scripts/seed-local-env.ts +++ b/scripts/seed-local-env.ts @@ -88,13 +88,15 @@ async function single() { const randNumber = Math.random() * 100; console.log('Reporting usage query...'); - const done = hiveInstance.collectUsage({ - document: randNumber > 50 ? query1 : query2, - schema, - variableValues: {}, - }); + const done = hiveInstance.collectUsage(); done( + { + document: randNumber > 50 ? query1 : query2, + schema, + variableValues: {}, + contextValue: {}, + }, randNumber > 90 ? { errors: undefined,