filter operations by client name (#2487)

This commit is contained in:
Laurin Quast 2023-06-27 12:24:43 +02:00 committed by GitHub
parent 048a250320
commit 90e242bc07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 429 additions and 35 deletions

View file

@ -16,6 +16,7 @@ export default gql`
target: ID!
period: DateRangeInput!
operations: [ID!]
clientNames: [String!]
}
input OperationBodyByHashInput {

View file

@ -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<TargetSelector, 'target'>) {
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,
});
}

View file

@ -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<number> {
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<number> {
@ -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<Percentiles> {
@ -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);
}

View file

@ -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<

View file

@ -204,6 +204,7 @@ export interface OperationsStats {
target: string;
period: DateRange;
operations: readonly string[];
clients: readonly string[];
}
export interface DurationStats {

View file

@ -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<string[]>([]);
const [selectedClients, setSelectedClients] = useState<string[]>([]);
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}
/>
<ClientsFilterTrigger
period={period}
selected={selectedClients}
onFilter={setSelectedClients}
/>
<RadixSelect
onChange={updatePeriod}
defaultValue={selectedPeriod}
@ -94,6 +102,7 @@ function OperationsView({
target={targetCleanId}
period={period}
operationsFilter={selectedOperations}
clientNamesFilter={selectedClients}
/>
<OperationsList
className="mt-12"
@ -102,6 +111,7 @@ function OperationsView({
project={projectCleanId}
target={targetCleanId}
operationsFilter={selectedOperations}
clientNamesFilter={selectedClients}
/>
</>
);

View file

@ -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 (
<div style={style} className="flex items-center gap-4">
<Checkbox checked={selected} onCheckedChange={change} id={hash} />
<label
htmlFor={hash}
className="flex items-center justify-between overflow-hidden gap-4 w-full cursor-pointer"
>
<span className="grow text-ellipsis overflow-hidden">{client.name}</span>
<div className="shrink-0 text-right text-gray-500">{requests}</div>
</label>
</div>
);
}
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<string[]>(() =>
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<HTMLInputElement>) => {
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<ComponentType<ListChildComponentProps>>(
({ index, style }) => {
const client = visibleOperations[index];
return (
<ClientRow
style={style}
key={client.name}
client={client}
selected={selectedItems.includes(client.name || '')}
onSelect={onSelect}
/>
);
},
[visibleOperations, selectedItems, onSelect],
);
return (
<Drawer open={isOpen} onOpenChange={onClose} width={500}>
<Drawer.Title>Filter by client</Drawer.Title>
<div className="flex flex-col h-full space-y-3">
<Input
size="medium"
placeholder="Search for operation..."
onChange={onChange}
value={searchTerm}
onClear={() => {
setSearchTerm('');
setVisibleOperations(clients);
}}
/>
<div className="flex gap-2 items-center w-full">
<Button variant="link" onClick={selectAll}>
All
</Button>
<Button variant="link" onClick={selectNone}>
None
</Button>
<Button className="ml-auto" onClick={selectAll}>
Reset
</Button>
<Button
variant="primary"
disabled={selectedItems.length === 0}
onClick={() => {
onFilter(selectedItems);
onClose();
}}
>
Save
</Button>
</div>
<div className="pl-1 grow">
<AutoSizer>
{({ height, width }) =>
!height || !width ? (
<></>
) : (
<FixedSizeList
height={height}
width={width}
itemCount={visibleOperations.length}
itemSize={24}
overscanCount={5}
>
{renderRow}
</FixedSizeList>
)
}
</AutoSizer>
</div>
</div>
</Drawer>
);
}
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 <Spinner />;
}
const allClients = query.data.operationsStats?.clients?.nodes ?? [];
return (
<ClientsFilter
clients={allClients}
selected={selected}
isOpen={isOpen}
onClose={onClose}
onFilter={clientNames => {
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 (
<>
<Button variant="secondary" className="gap-2 rounded-md" onClick={toggle}>
Clients ({selected?.length || 'all'})<ChevronUpIcon className="rotate-180" />
</Button>
<ClientsFilterContainer
isOpen={isOpen}
onClose={toggle}
period={period}
selected={selected}
onFilter={onFilter}
/>
</>
);
}

View file

@ -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({
<div className={clsx('rounded-md p-5 border border-gray-800 bg-gray-900/50', className)}>
<Section.Title>Operations</Section.Title>
<Section.Subtitle>List of all operations with their statistics</Section.Subtitle>
<Table>
<THead>
<Tooltip.Provider>
@ -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<string | null>(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 (
<OperationsFallback
@ -436,6 +454,9 @@ export function OperationsList({
operations={operations}
operationsFilter={operationsFilter}
className={className}
clients={clients}
setClientFilter={setClientFilter}
clientFilter={clientFilter}
organization={organization}
project={project}
target={target}

View file

@ -689,6 +689,7 @@ export function OperationsStats({
target,
period,
operationsFilter,
clientNamesFilter,
}: {
organization: string;
project: string;
@ -698,6 +699,7 @@ export function OperationsStats({
to: string;
};
operationsFilter: string[];
clientNamesFilter: Array<string>;
}): ReactElement {
const resolution = 90;
const [query, refetchQuery] = useQuery({
@ -709,6 +711,7 @@ export function OperationsStats({
target,
period,
operations: operationsFilter,
clientNames: clientNamesFilter,
},
resolution,
},

View file

@ -6,5 +6,11 @@ query operationsStats($selector: OperationsStatsSelectorInput!) {
}
total
}
clients {
nodes {
name
count
}
}
}
}

View file

@ -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,