Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ab9a0ba
feat: create initial collection prompt + edit name
mskorokhodov Apr 16, 2026
52961d7
fix: updated way to create first collection
mskorokhodov Apr 17, 2026
03d983d
Merge branch 'main' of github.com:graphql-hive/console into feat/lab-…
mskorokhodov Apr 17, 2026
f073d35
fix: add escape button handler to collection edit in lab
mskorokhodov Apr 17, 2026
705a13d
fix: cannot infer operation error + better error handling for executor
mskorokhodov Apr 17, 2026
e59a055
fix: curosr pointer for save button and open collection if active ope…
mskorokhodov Apr 17, 2026
f62d0c4
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov Apr 21, 2026
14956d0
feat: new projects screen
mskorokhodov Apr 21, 2026
491c45f
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov Apr 23, 2026
dd520c5
fetch project target only if it's in view
mskorokhodov Apr 24, 2026
9bc5eb4
one more iteration of ui
mskorokhodov Apr 29, 2026
567e799
refactoring and type/lint issues fix
mskorokhodov May 5, 2026
7427df0
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov May 5, 2026
5c89ecc
gemini comments addressed
mskorokhodov May 5, 2026
4207ace
implemented projects/targets server side sorting and search + intial …
mskorokhodov May 24, 2026
a3923f9
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov May 24, 2026
4e5fb28
better lazy loading for org screen + lint/types fixes
mskorokhodov May 26, 2026
644adee
prettier errors fix
mskorokhodov May 27, 2026
5161742
removed dups of countSchemaVersionsOfProject
mskorokhodov May 27, 2026
d729c77
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov May 27, 2026
3750830
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov May 29, 2026
dd1d1d8
sorting fix
mskorokhodov May 29, 2026
f2a0508
gemini comments addressed
mskorokhodov Jun 1, 2026
20681ec
batched per target calls to clickhouse
mskorokhodov Jun 1, 2026
f8e7eb1
gemini comments addressed
mskorokhodov Jun 1, 2026
17b162e
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov Jun 1, 2026
dd2e952
clickhouse buckets fix
mskorokhodov Jun 2, 2026
f5333a7
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov Jun 2, 2026
e96e90e
prettier issue addressed
mskorokhodov Jun 2, 2026
c64361f
design comments addressed
mskorokhodov Jun 2, 2026
a25eb9c
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov Jun 3, 2026
7c6beeb
code review comments addressed
mskorokhodov Jun 3, 2026
8d21b4c
invalid cursor error fix
mskorokhodov Jun 5, 2026
a7becdf
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov Jun 5, 2026
741f04a
typecheck fix
mskorokhodov Jun 5, 2026
8bb2d0c
schema version count query fix
mskorokhodov Jun 9, 2026
9b6990b
fix when all of loaded targets been loaded on sort in case if loaded …
mskorokhodov Jun 9, 2026
90e5ed6
Merge branch 'main' of github.com:graphql-hive/console into feat/new-…
mskorokhodov Jun 9, 2026
3f1467e
Merge branch 'main' into feat/new-projects-screen
mskorokhodov Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import LRU from 'lru-cache';
import { traceFn } from '@hive/service-common';
import type { DateRange } from '../../../shared/entities';
import type { Listify, Optional } from '../../../shared/helpers';
import { cache } from '../../../shared/helpers';
import { batchBy, cache } from '../../../shared/helpers';
import { Session } from '../../auth/lib/authz';
import { Logger } from '../../shared/providers/logger';
import type {
Expand Down Expand Up @@ -52,6 +52,27 @@ interface ReadFieldStatsOutput {
percentage: number;
}

export type DurationAndCountOverTime = Array<{
date: number;
total: number;
totalOk: number;
duration: {
avg: number;
p75: number;
p90: number;
p95: number;
p99: number;
};
}>;

const emptyDurationMetrics = {
avg: 0,
p75: 0,
p90: 0,
p95: 0,
p99: 0,
};

/**
* Responsible for auth checks.
* Talks to Storage.
Expand Down Expand Up @@ -311,6 +332,66 @@ export class OperationsManager {
});
}

async countRequestsByTargetIds({
organizationId: organization,
projectId: project,
targetIds,
period,
}: {
targetIds: readonly string[];
period: DateRange;
} & ProjectSelector): Promise<Map<string, number>> {
this.logger.info(
'Counting requests by target (period=%o, project=%s, targets=%s)',
period,
project,
targetIds.join(';'),
);

await this.session.assertPerformAction({
action: 'project:describe',
organizationId: organization,
params: {
organizationId: organization,
projectId: project,
},
});

return this.reader.countRequestsByTarget({
targets: targetIds,
period,
});
}

async countRequestsByTargetIdsOfOrganization({
organizationId: organization,
targetIds,
period,
}: {
targetIds: readonly string[];
period: DateRange;
} & OrganizationSelector): Promise<Map<string, number>> {
this.logger.info(
'Counting requests by target for organization (period=%o, organization=%s, targets=%s)',
period,
organization,
targetIds.join(';'),
);

await this.session.assertPerformAction({
action: 'organization:describe',
organizationId: organization,
params: {
organizationId: organization,
},
});

return this.reader.countRequestsByTarget({
targets: targetIds,
period,
});
}

async countRequestsOfProject({
organizationId: organization,
projectId: project,
Expand Down Expand Up @@ -649,6 +730,58 @@ export class OperationsManager {
});
}

readDurationAndCountOverTime = batchBy<
{
period: DateRange;
resolution: number;
operations?: readonly string[];
clients?: readonly string[];
clientVersionFilters?: readonly { clientName: string; versions: readonly string[] | null }[];
excludeOperations?: boolean;
excludeClientVersionFilters?: boolean;
} & TargetSelector,
DurationAndCountOverTime
>(
selector =>
JSON.stringify({
organizationId: selector.organizationId,
projectId: selector.projectId,
period: selector.period,
resolution: selector.resolution,
operations: selector.operations,
clients: selector.clients,
clientVersionFilters: selector.clientVersionFilters,
excludeOperations: selector.excludeOperations,
excludeClientVersionFilters: selector.excludeClientVersionFilters,
}),
async selectors => {
const selector = selectors[0];
const targetIds = Array.from(new Set(selectors.map(selector => selector.targetId)));

await this.session.assertPerformAction({
action: 'project:describe',
organizationId: selector.organizationId,
params: {
organizationId: selector.organizationId,
projectId: selector.projectId,
},
});

const results = await this.reader.durationAndCountOverTimeOfTargets({
targets: targetIds,
period: selector.period,
resolution: selector.resolution,
operations: selector.operations,
clients: selector.clients,
clientVersionFilters: selector.clientVersionFilters,
excludeOperations: selector.excludeOperations,
excludeClientVersionFilters: selector.excludeClientVersionFilters,
});

return selectors.map(selector => Promise.resolve(results.get(selector.targetId) ?? []));
},
);

async readFailuresOverTime({
period,
resolution,
Expand Down Expand Up @@ -743,44 +876,60 @@ export class OperationsManager {
});
}

async readGeneralDurationPercentiles({
period,
organizationId: organization,
projectId: project,
targetId: target,
operations,
clients,
clientVersionFilters,
excludeOperations,
excludeClientVersionFilters,
}: {
period: DateRange;
operations?: readonly string[];
clients?: readonly string[];
clientVersionFilters?: readonly { clientName: string; versions: readonly string[] | null }[];
excludeOperations?: boolean;
excludeClientVersionFilters?: boolean;
} & TargetSelector) {
this.logger.info('Reading overall duration percentiles (period=%o, target=%s)', period, target);
await this.session.assertPerformAction({
action: 'project:describe',
organizationId: organization,
params: {
organizationId: organization,
projectId: project,
},
});
readGeneralDurationPercentiles = batchBy<
{
period: DateRange;
operations?: readonly string[];
clients?: readonly string[];
clientVersionFilters?: readonly { clientName: string; versions: readonly string[] | null }[];
excludeOperations?: boolean;
excludeClientVersionFilters?: boolean;
} & TargetSelector,
typeof emptyDurationMetrics
>(
selector =>
JSON.stringify({
organizationId: selector.organizationId,
projectId: selector.projectId,
period: selector.period,
operations: selector.operations,
clients: selector.clients,
clientVersionFilters: selector.clientVersionFilters,
excludeOperations: selector.excludeOperations,
excludeClientVersionFilters: selector.excludeClientVersionFilters,
}),
async selectors => {
const selector = selectors[0];

this.logger.info(
'Reading overall duration percentiles (period=%o, targets=%s)',
selector.period,
selectors.map(selector => selector.targetId).join(';'),
);
await this.session.assertPerformAction({
action: 'project:describe',
organizationId: selector.organizationId,
params: {
organizationId: selector.organizationId,
projectId: selector.projectId,
},
});

return this.reader.generalDurationPercentiles({
target,
period,
operations,
clients,
clientVersionFilters,
excludeOperations,
excludeClientVersionFilters,
});
}
const durations = await this.reader.generalDurationPercentilesOfTargets({
targets: selectors.map(selector => selector.targetId),
period: selector.period,
operations: selector.operations,
clients: selector.clients,
clientVersionFilters: selector.clientVersionFilters,
excludeOperations: selector.excludeOperations,
excludeClientVersionFilters: selector.excludeClientVersionFilters,
});

return selectors.map(selector =>
Promise.resolve(durations.get(selector.targetId) ?? emptyDurationMetrics),
);
},
);

@cache<{ period: DateRange } & TargetSelector>(selector => JSON.stringify(selector))
async readDetailedDurationMetrics({
Expand Down
Loading
Loading