mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
Fix interval calculation in ClickHouse queries (#4683)
This commit is contained in:
parent
b94f3bc73a
commit
fccb3514c1
15 changed files with 434 additions and 229 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import formatISO from 'date-fns/formatISO';
|
||||
import subHours from 'date-fns/subHours';
|
||||
import { formatISO } from 'date-fns/formatISO';
|
||||
import { subHours } from 'date-fns/subHours';
|
||||
import { ProjectType } from '@app/gql/graphql';
|
||||
import { waitFor } from '../../testkit/flow';
|
||||
import { initSeed } from '../../testkit/seed';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
import { toEndOfInterval, toStartOfInterval } from '../date-time-helpers';
|
||||
|
||||
describe('toStartOfInterval', () => {
|
||||
test('1 DAY - rounds middle of the day to start of the day', () => {
|
||||
expect(toStartOfInterval(new Date('2021-03-23T11:00:00.000Z'), 1, 'd').toISOString()).toBe(
|
||||
'2021-03-23T00:00:00.000Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('2 DAY - rounds middle of the day to start of the every second day', () => {
|
||||
expect(toStartOfInterval(new Date('2021-03-23T11:00:00.000Z'), 2, 'd').toISOString()).toBe(
|
||||
'2021-03-22T00:00:00.000Z',
|
||||
);
|
||||
expect(toStartOfInterval(new Date('2021-01-01T11:00:00.000Z'), 2, 'd').toISOString()).toBe(
|
||||
'2021-01-01T00:00:00.000Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('1 HOUR - rounds middle of the hour to start of the hour', () => {
|
||||
expect(toStartOfInterval(new Date('2021-03-23T11:30:00.000Z'), 1, 'h').toISOString()).toBe(
|
||||
'2021-03-23T11:00:00.000Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('1 MINUTE - rounds middle of the minute to start of the minute', () => {
|
||||
expect(toStartOfInterval(new Date('2021-03-23T11:30:50.000Z'), 1, 'm').toISOString()).toBe(
|
||||
'2021-03-23T11:30:00.000Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('handles month bounds', () => {
|
||||
expect(toStartOfInterval(new Date('2021-02-01T11:00:00.000Z'), 2, 'd').toISOString()).toBe(
|
||||
'2021-01-31T00:00:00.000Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('handles day bounds', () => {
|
||||
expect(toStartOfInterval(new Date('2021-02-01T01:00:00.000Z'), 25, 'h').toISOString()).toBe(
|
||||
'2021-01-31T08:00:00.000Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('handles hour bounds', () => {
|
||||
expect(toStartOfInterval(new Date('2021-02-01T12:01:00.000Z'), 70, 'm').toISOString()).toBe(
|
||||
'2021-02-01T11:20:00.000Z',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toEndOfInterval', () => {
|
||||
test('1 DAY - rounds middle of the day to end of the day', () => {
|
||||
expect(toEndOfInterval(new Date('2021-03-23T11:00:00.000Z'), 1, 'd').toISOString()).toBe(
|
||||
'2021-03-23T23:59:59.999Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('2 DAY - rounds middle of the day to start of the every second day', () => {
|
||||
expect(toEndOfInterval(new Date('2021-03-23T11:00:00.000Z'), 2, 'd').toISOString()).toBe(
|
||||
'2021-03-23T23:59:59.999Z',
|
||||
);
|
||||
expect(toEndOfInterval(new Date('2021-01-01T11:00:00.000Z'), 2, 'd').toISOString()).toBe(
|
||||
'2021-01-02T23:59:59.999Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('1 HOUR - rounds middle of the hour to the end of the hour', () => {
|
||||
expect(toEndOfInterval(new Date('2021-03-23T11:30:00.000Z'), 1, 'h').toISOString()).toBe(
|
||||
'2021-03-23T11:59:59.999Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('1 MINUTE - rounds middle of the minute to the end of the minute', () => {
|
||||
expect(toEndOfInterval(new Date('2021-03-23T11:30:50.000Z'), 1, 'm').toISOString()).toBe(
|
||||
'2021-03-23T11:30:59.999Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('handles month bounds', () => {
|
||||
expect(toEndOfInterval(new Date('2021-01-31T11:00:00.000Z'), 2, 'd').toISOString()).toBe(
|
||||
'2021-02-01T23:59:59.999Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('handles day bounds', () => {
|
||||
expect(toEndOfInterval(new Date('2021-02-02T23:59:00.000Z'), 25, 'h').toISOString()).toBe(
|
||||
'2021-02-03T10:59:59.999Z',
|
||||
);
|
||||
});
|
||||
|
||||
test('handles hour bounds', () => {
|
||||
expect(toEndOfInterval(new Date('2021-02-01T12:38:00.000Z'), 70, 'm').toISOString()).toBe(
|
||||
'2021-02-01T13:39:59.999Z',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -35,7 +35,7 @@ describe('pickTableByPeriod', () => {
|
|||
});
|
||||
expect(table).toBe('hourly');
|
||||
});
|
||||
test('28 day period -> daily', () => {
|
||||
test('28 day period -> hourly', () => {
|
||||
const now = new Date();
|
||||
const table = pickTableByPeriod({
|
||||
now,
|
||||
|
|
@ -44,6 +44,17 @@ describe('pickTableByPeriod', () => {
|
|||
to: now,
|
||||
},
|
||||
});
|
||||
expect(table).toBe('hourly');
|
||||
});
|
||||
test('31 day period -> daily', () => {
|
||||
const now = new Date();
|
||||
const table = pickTableByPeriod({
|
||||
now,
|
||||
period: {
|
||||
from: subDays(now, 31),
|
||||
to: now,
|
||||
},
|
||||
});
|
||||
expect(table).toBe('daily');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
const minuteInMs = 60 * 1000;
|
||||
const hourInMs = 60 * minuteInMs;
|
||||
const dayInMs = 24 * hourInMs;
|
||||
|
||||
const unitToMs = {
|
||||
d: dayInMs,
|
||||
h: hourInMs,
|
||||
m: minuteInMs,
|
||||
};
|
||||
|
||||
export function toStartOfInterval(
|
||||
date: Date,
|
||||
intervalValue: number,
|
||||
intervalUnit: 'd' | 'h' | 'm',
|
||||
): Date {
|
||||
const unixTimestamp = date.getTime();
|
||||
const div = intervalValue * unitToMs[intervalUnit];
|
||||
return new Date(Math.floor(unixTimestamp / div) * div);
|
||||
}
|
||||
|
||||
export function toEndOfInterval(
|
||||
date: Date,
|
||||
intervalValue: number,
|
||||
intervalUnit: 'd' | 'h' | 'm',
|
||||
): Date {
|
||||
const unixTimestamp = date.getTime();
|
||||
const div = intervalValue * unitToMs[intervalUnit];
|
||||
return new Date(Math.ceil(unixTimestamp / div) * div - 1);
|
||||
}
|
||||
|
|
@ -1,19 +1,7 @@
|
|||
import {
|
||||
addMinutes,
|
||||
format,
|
||||
startOfDay,
|
||||
startOfHour,
|
||||
startOfMinute,
|
||||
subHours,
|
||||
subMinutes,
|
||||
} from 'date-fns';
|
||||
import { startOfDay, startOfHour, startOfMinute, subHours, subMinutes } from 'date-fns';
|
||||
import type { DateRange } from '../../../shared/entities';
|
||||
import type { Logger } from '../../shared/providers/logger';
|
||||
|
||||
const msMinute = 60 * 1_000;
|
||||
const msHour = msMinute * 60;
|
||||
const msDay = msHour * 24;
|
||||
|
||||
// How long rows are kept in the database, per table.
|
||||
const tableTTLInHours = {
|
||||
daily: 365 * 24,
|
||||
|
|
@ -21,19 +9,45 @@ const tableTTLInHours = {
|
|||
minutely: 24,
|
||||
};
|
||||
|
||||
const thresholdDataPointPerDay = 28;
|
||||
const thresholdDataPointPerHour = 24;
|
||||
type Table = 'hourly' | 'daily' | 'minutely';
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
return format(addMinutes(date, date.getTimezoneOffset()), 'yyyy-MM-dd HH:mm:ss');
|
||||
}
|
||||
const intervalUnitToTable = {
|
||||
m: 'minutely',
|
||||
h: 'hourly',
|
||||
d: 'daily',
|
||||
} as const;
|
||||
|
||||
const tableAlternatives: Record<Table, Table[]> = {
|
||||
minutely: ['minutely'],
|
||||
hourly: ['hourly', 'minutely'],
|
||||
daily: ['daily', 'hourly', 'minutely'],
|
||||
};
|
||||
|
||||
const tableFromDateCheck = {
|
||||
minutely: (date: Date) => date.getTime() === startOfMinute(date).getTime(),
|
||||
hourly: (date: Date) => date.getTime() === startOfHour(date).getTime(),
|
||||
daily: (date: Date) => date.getTime() === startOfDay(date).getTime(),
|
||||
};
|
||||
|
||||
const precisionScore = {
|
||||
minutely: 3,
|
||||
hourly: 2,
|
||||
daily: 1,
|
||||
};
|
||||
|
||||
/** pick the correct materialized view table for request data based on the input period */
|
||||
export function pickTableByPeriod(args: {
|
||||
now: Date;
|
||||
period: DateRange;
|
||||
intervalUnit?: 'h' | 'd' | 'm';
|
||||
logger?: Logger;
|
||||
}): 'hourly' | 'daily' | 'minutely' {
|
||||
}): Table {
|
||||
args.logger?.debug(
|
||||
'Picking table by period (from: %s, to: %s, intervalUnit: %s',
|
||||
args.period.from.toISOString(),
|
||||
args.period.to.toISOString(),
|
||||
args.intervalUnit ?? 'none',
|
||||
);
|
||||
// The oldest data point we can fetch from the database, per table.
|
||||
// ! We subtract 2 minutes as we round the date to the nearest minute on UI
|
||||
// and there's also a chance that request will be made at 59th second of the minute
|
||||
|
|
@ -44,37 +58,51 @@ export function pickTableByPeriod(args: {
|
|||
hourly: subMinutes(startOfHour(subHours(args.now, tableTTLInHours.hourly)), 2),
|
||||
minutely: subMinutes(startOfMinute(subHours(args.now, tableTTLInHours.minutely)), 2),
|
||||
};
|
||||
const intervalUnit = args.intervalUnit ? intervalUnitToTable[args.intervalUnit] : undefined;
|
||||
|
||||
if (
|
||||
args.period.to.getTime() <= tableOldestDateTimePoint.daily.getTime() ||
|
||||
args.period.from.getTime() <= tableOldestDateTimePoint.daily.getTime()
|
||||
) {
|
||||
args.logger?.error(
|
||||
`Requested date range ${formatDate(args.period.from)} - ${formatDate(args.period.to)} is too old.`,
|
||||
);
|
||||
let potentialTables: Array<'hourly' | 'daily' | 'minutely'> = ['daily', 'hourly', 'minutely'];
|
||||
|
||||
// filter out tables can't be used due to TTL
|
||||
potentialTables = potentialTables.filter(
|
||||
table => args.period.from.getTime() >= tableOldestDateTimePoint[table].getTime(),
|
||||
);
|
||||
args.logger?.debug('Potential tables after TTL filter: %s', potentialTables.join(','));
|
||||
|
||||
if (potentialTables.length === 0) {
|
||||
throw new Error(`The requested date range is too old for the selected query type.`);
|
||||
}
|
||||
|
||||
const daysDifference = Math.floor(
|
||||
(args.period.to.getTime() - args.period.from.getTime()) / msDay,
|
||||
);
|
||||
if (intervalUnit) {
|
||||
// filter out tables that don't support the requested interval
|
||||
potentialTables = potentialTables.filter(table =>
|
||||
tableAlternatives[intervalUnit].includes(table),
|
||||
);
|
||||
args.logger?.debug(
|
||||
'Potential tables after interval unit filter: %s',
|
||||
potentialTables.join(','),
|
||||
);
|
||||
|
||||
if (
|
||||
daysDifference >= thresholdDataPointPerDay ||
|
||||
args.period.to.getTime() <= tableOldestDateTimePoint.hourly.getTime() ||
|
||||
args.period.from.getTime() <= tableOldestDateTimePoint.hourly.getTime()
|
||||
) {
|
||||
return 'daily';
|
||||
if (potentialTables.length === 0) {
|
||||
throw new Error(`Requested interval unit is not supported by any possible table.`);
|
||||
}
|
||||
|
||||
// filter out tables that don't support the requested period
|
||||
// e.g. if the from date is 2021-01-01 12:00:00 and the interval is 'daily', we can't resolve it
|
||||
// as the daily table will have data for 2021-01-01 00:00:00
|
||||
potentialTables = potentialTables.filter(table => tableFromDateCheck[table](args.period.from));
|
||||
|
||||
args.logger?.debug('Potential tables after start date filter: %s', potentialTables.join(','));
|
||||
}
|
||||
|
||||
const hoursDifference = (args.period.to.getTime() - args.period.from.getTime()) / msHour;
|
||||
if (
|
||||
hoursDifference >= thresholdDataPointPerHour ||
|
||||
args.period.to.getTime() <= tableOldestDateTimePoint.minutely.getTime() ||
|
||||
args.period.from.getTime() <= tableOldestDateTimePoint.minutely.getTime()
|
||||
) {
|
||||
return 'hourly';
|
||||
if (potentialTables.length === 0) {
|
||||
throw new Error(`Requested start date is not supported by any possible table.`);
|
||||
}
|
||||
|
||||
return 'minutely';
|
||||
// pick the table with the highest precision score
|
||||
// e.g. if the period is 3 hours and we can use hourly and minutely tables, we should pick the minutely table
|
||||
const table = potentialTables.sort((a, b) => precisionScore[b] - precisionScore[a])[0];
|
||||
|
||||
args.logger?.debug('Selected table: %s', table);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ import type { DateRange } from '../../../shared/entities';
|
|||
|
||||
export const maxResolution = 90;
|
||||
|
||||
const inSeconds = {
|
||||
m: 60,
|
||||
h: 60 * 60,
|
||||
d: 60 * 60 * 24,
|
||||
};
|
||||
|
||||
export function calculateTimeWindow({
|
||||
period,
|
||||
resolution,
|
||||
|
|
@ -11,6 +17,7 @@ export function calculateTimeWindow({
|
|||
}): {
|
||||
value: number;
|
||||
unit: 'd' | 'h' | 'm';
|
||||
seconds: number;
|
||||
} {
|
||||
if (!Number.isInteger(resolution)) {
|
||||
throw new Error(`Invalid resolution. Expected an integer, received ${resolution}`);
|
||||
|
|
@ -30,13 +37,14 @@ export function calculateTimeWindow({
|
|||
d: 60 * 24,
|
||||
};
|
||||
|
||||
const value = Math.ceil(distanceInMinutes / resolution);
|
||||
const value = Math.floor(distanceInMinutes / resolution);
|
||||
const unit = calculateUnit(value);
|
||||
const correctedValue = Math.ceil(value / divideBy[unit]);
|
||||
const correctedValue = Math.floor(value / divideBy[unit]);
|
||||
|
||||
return {
|
||||
value: correctedValue,
|
||||
unit: calculateUnit(value),
|
||||
unit,
|
||||
seconds: correctedValue * inSeconds[unit],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ export class OperationsManager {
|
|||
schemaCoordinate: string;
|
||||
} & Listify<TargetSelector, 'target'>) {
|
||||
this.logger.info(
|
||||
'Counting requests with schema coordinate (period=%s, target=%s, coordinate=%s)',
|
||||
'Counting requests with schema coordinate (period=%o, target=%s, coordinate=%s)',
|
||||
period,
|
||||
target,
|
||||
schemaCoordinate,
|
||||
|
|
@ -256,7 +256,7 @@ export class OperationsManager {
|
|||
}: {
|
||||
period: DateRange;
|
||||
} & Listify<TargetSelector, 'target'>): Promise<number> {
|
||||
this.logger.info('Counting requests (period=%s, target=%s)', period, target);
|
||||
this.logger.info('Counting requests (period=%o, target=%s)', period, target);
|
||||
await this.authManager.ensureTargetAccess({
|
||||
organization,
|
||||
project,
|
||||
|
|
@ -277,7 +277,7 @@ export class OperationsManager {
|
|||
}: {
|
||||
period: DateRange;
|
||||
} & ProjectSelector): Promise<number> {
|
||||
this.logger.info('Counting requests of project (period=%s, project=%s)', period, project);
|
||||
this.logger.info('Counting requests of project (period=%o, project=%s)', period, project);
|
||||
const targets = await this.storage.getTargetIdsOfProject({
|
||||
organization,
|
||||
project,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { batch } from '@theguild/buddy';
|
|||
import type { DateRange } from '../../../shared/entities';
|
||||
import { batchBy } from '../../../shared/helpers';
|
||||
import { Logger } from '../../shared/providers/logger';
|
||||
import { toEndOfInterval, toStartOfInterval } from '../lib/date-time-helpers';
|
||||
import { pickTableByPeriod } from '../lib/pick-table-by-provider';
|
||||
import { ClickHouse, RowOf, sql } from './clickhouse-client';
|
||||
import { calculateTimeWindow } from './helpers';
|
||||
|
|
@ -104,14 +105,17 @@ export class OperationsReader {
|
|||
|
||||
if (resolution && (resolution < 1 || resolution > 90)) {
|
||||
throw new Error('Invalid resolution provided.');
|
||||
} else {
|
||||
// default value :shrug:
|
||||
resolution = 30;
|
||||
}
|
||||
|
||||
const now = new UTCDate();
|
||||
const interval = resolution ? calculateTimeWindow({ period, resolution }) : null;
|
||||
|
||||
const resolvedTable = pickTableByPeriod({ now, period, logger: this.logger });
|
||||
const resolvedTable = pickTableByPeriod({
|
||||
now,
|
||||
period,
|
||||
intervalUnit: interval?.unit,
|
||||
logger: this.logger,
|
||||
});
|
||||
|
||||
return {
|
||||
...queryMap[resolvedTable],
|
||||
|
|
@ -1784,9 +1788,36 @@ export class OperationsReader {
|
|||
>();
|
||||
|
||||
for (const [key, { targets, period, resolution }] of aggregationMap) {
|
||||
const interval = this.clickHouse.translateWindow(calculateTimeWindow({ period, resolution }));
|
||||
const startDateTimeFormatted = formatDate(period.from);
|
||||
const endDateTimeFormatted = formatDate(period.to);
|
||||
const interval = calculateTimeWindow({ period, resolution });
|
||||
const intervalRaw = this.clickHouse.translateWindow(interval);
|
||||
const roundedPeriod = {
|
||||
from: toStartOfInterval(period.from, interval.value, interval.unit),
|
||||
to: toEndOfInterval(period.to, interval.value, interval.unit),
|
||||
};
|
||||
const startDateTimeFormatted = formatDate(roundedPeriod.from);
|
||||
const endDateTimeFormatted = formatDate(roundedPeriod.to);
|
||||
|
||||
const createQuery = (tableName: string) => sql`
|
||||
SELECT
|
||||
toDateTime(
|
||||
intDiv(
|
||||
toUnixTimestamp(timestamp),
|
||||
toUInt32(${String(interval.seconds)})
|
||||
) * toUInt32(${String(interval.seconds)})
|
||||
) as date,
|
||||
sum(total) as total,
|
||||
target
|
||||
FROM ${sql.raw(tableName)}
|
||||
${this.createFilter({ target: targets, period: roundedPeriod })}
|
||||
GROUP BY target, date
|
||||
ORDER BY
|
||||
target,
|
||||
date
|
||||
WITH FILL
|
||||
FROM toDateTime(${startDateTimeFormatted}, 'UTC')
|
||||
TO toDateTime(${endDateTimeFormatted}, 'UTC')
|
||||
STEP INTERVAL ${intervalRaw}
|
||||
`;
|
||||
|
||||
aggregationResultMap.set(
|
||||
key,
|
||||
|
|
@ -1799,63 +1830,17 @@ export class OperationsReader {
|
|||
this.pickQueryByPeriod(
|
||||
{
|
||||
daily: {
|
||||
query: sql`
|
||||
SELECT
|
||||
toStartOfInterval(timestamp, INTERVAL ${interval}, 'UTC') as date,
|
||||
sum(total) as total,
|
||||
target
|
||||
FROM operations_daily
|
||||
${this.createFilter({ target: targets, period })}
|
||||
GROUP BY target, date
|
||||
ORDER BY
|
||||
target,
|
||||
date
|
||||
WITH FILL
|
||||
FROM toDateTime(${startDateTimeFormatted}, 'UTC')
|
||||
TO toDateTime(${endDateTimeFormatted}, 'UTC')
|
||||
STEP INTERVAL ${interval}
|
||||
`,
|
||||
query: createQuery('operations_daily'),
|
||||
queryId: 'targets_count_over_time_daily',
|
||||
timeout: 15_000,
|
||||
},
|
||||
hourly: {
|
||||
query: sql`
|
||||
SELECT
|
||||
toStartOfInterval(timestamp, INTERVAL ${interval}, 'UTC') as date,
|
||||
sum(total) as total,
|
||||
target
|
||||
FROM operations_hourly
|
||||
${this.createFilter({ target: targets, period })}
|
||||
GROUP BY target, date
|
||||
ORDER BY
|
||||
target,
|
||||
date
|
||||
WITH FILL
|
||||
FROM toDateTime(${startDateTimeFormatted}, 'UTC')
|
||||
TO toDateTime(${endDateTimeFormatted}, 'UTC')
|
||||
STEP INTERVAL ${interval}
|
||||
`,
|
||||
query: createQuery('operations_hourly'),
|
||||
queryId: 'targets_count_over_time_hourly',
|
||||
timeout: 15_000,
|
||||
},
|
||||
minutely: {
|
||||
query: sql`
|
||||
SELECT
|
||||
toStartOfInterval(timestamp, INTERVAL ${interval}, 'UTC') as date,
|
||||
sum(total) as total,
|
||||
target
|
||||
FROM operations_minutely
|
||||
${this.createFilter({ target: targets, period })}
|
||||
GROUP BY target
|
||||
ORDER BY
|
||||
target,
|
||||
date
|
||||
WITH FILL
|
||||
FROM toDateTime(${startDateTimeFormatted}, 'UTC')
|
||||
TO toDateTime(${endDateTimeFormatted}, 'UTC')
|
||||
STEP INTERVAL ${interval}
|
||||
|
||||
`,
|
||||
query: createQuery('operations_minutely'),
|
||||
queryId: 'targets_count_over_time_regular',
|
||||
timeout: 15_000,
|
||||
},
|
||||
|
|
@ -2201,13 +2186,16 @@ export class OperationsReader {
|
|||
clients?: readonly string[];
|
||||
schemaCoordinate?: string;
|
||||
}) {
|
||||
const createSQLQuery = (tableName: string, isAggregation: boolean) => {
|
||||
const startDateTimeFormatted = formatDate(period.from);
|
||||
const endDateTimeFormatted = formatDate(period.to);
|
||||
const interval = calculateTimeWindow({ period, resolution });
|
||||
const intervalUnit =
|
||||
interval.unit === 'd' ? 'DAY' : interval.unit === 'h' ? 'HOUR' : 'MINUTE';
|
||||
const interval = calculateTimeWindow({ period, resolution });
|
||||
const intervalRaw = this.clickHouse.translateWindow(interval);
|
||||
const roundedPeriod = {
|
||||
from: toStartOfInterval(period.from, interval.value, interval.unit),
|
||||
to: toEndOfInterval(period.to, interval.value, interval.unit),
|
||||
};
|
||||
const startDateTimeFormatted = formatDate(roundedPeriod.from);
|
||||
const endDateTimeFormatted = formatDate(roundedPeriod.to);
|
||||
|
||||
const createSQLQuery = (tableName: string, isAggregation: boolean) => {
|
||||
// TODO: remove this once we shift to the new table structure (PR #2712)
|
||||
const quantiles = isAggregation
|
||||
? 'quantilesMerge(0.75, 0.90, 0.95, 0.99)(duration_quantiles)'
|
||||
|
|
@ -2216,11 +2204,6 @@ export class OperationsReader {
|
|||
const totalOk = isAggregation ? 'sum(total_ok)' : 'sum(ok)';
|
||||
|
||||
return sql`
|
||||
WITH
|
||||
toDateTime(${startDateTimeFormatted}, 'UTC') as start_date_time,
|
||||
toDateTime(${endDateTimeFormatted}, 'UTC') as end_date_time,
|
||||
${intervalUnit} as interval_unit,
|
||||
toUInt16(${String(interval.value)}) as interval_value
|
||||
SELECT
|
||||
date,
|
||||
percentiles,
|
||||
|
|
@ -2228,12 +2211,11 @@ export class OperationsReader {
|
|||
totalOk
|
||||
FROM (
|
||||
SELECT
|
||||
date_add(
|
||||
${sql.raw(intervalUnit)},
|
||||
ceil(
|
||||
date_diff(interval_unit, start_date_time, timestamp, 'UTC') / interval_value
|
||||
) as UInt16 * interval_value,
|
||||
start_date_time
|
||||
toDateTime(
|
||||
intDiv(
|
||||
toUnixTimestamp(timestamp),
|
||||
toUInt32(${String(interval.seconds)})
|
||||
) * toUInt32(${String(interval.seconds)})
|
||||
) as date,
|
||||
${sql.raw(quantiles)} as percentiles,
|
||||
${sql.raw(total)} as total,
|
||||
|
|
@ -2241,14 +2223,14 @@ export class OperationsReader {
|
|||
FROM ${sql.raw(tableName)}
|
||||
${this.createFilter({
|
||||
target,
|
||||
period,
|
||||
period: roundedPeriod,
|
||||
operations,
|
||||
clients,
|
||||
extra: schemaCoordinate
|
||||
? [
|
||||
sql`hash IN (SELECT hash FROM coordinates_daily ${this.createFilter({
|
||||
target,
|
||||
period,
|
||||
period: roundedPeriod,
|
||||
extra: [sql`coordinate = ${schemaCoordinate}`],
|
||||
})})`,
|
||||
]
|
||||
|
|
@ -2259,7 +2241,7 @@ export class OperationsReader {
|
|||
WITH FILL
|
||||
FROM toDateTime(${startDateTimeFormatted}, 'UTC')
|
||||
TO toDateTime(${endDateTimeFormatted}, 'UTC')
|
||||
STEP INTERVAL ${this.clickHouse.translateWindow(interval)}
|
||||
STEP INTERVAL ${intervalRaw}
|
||||
)
|
||||
`;
|
||||
};
|
||||
|
|
@ -2486,30 +2468,33 @@ export class OperationsReader {
|
|||
};
|
||||
resolution: number;
|
||||
}) {
|
||||
const interval = this.clickHouse.translateWindow(
|
||||
calculateTimeWindow({
|
||||
period,
|
||||
resolution,
|
||||
}),
|
||||
);
|
||||
const startDateTimeFormatted = formatDate(period.from);
|
||||
const endDateTimeFormatted = formatDate(period.to);
|
||||
const interval = calculateTimeWindow({ period, resolution });
|
||||
const intervalRaw = this.clickHouse.translateWindow(interval);
|
||||
const roundedPeriod = {
|
||||
from: toStartOfInterval(period.from, interval.value, interval.unit),
|
||||
to: toEndOfInterval(period.to, interval.value, interval.unit),
|
||||
};
|
||||
const startDateTimeFormatted = formatDate(roundedPeriod.from);
|
||||
const endDateTimeFormatted = formatDate(roundedPeriod.to);
|
||||
|
||||
const createSQL = (tableName: string) => sql`
|
||||
SELECT
|
||||
toStartOfInterval(timestamp, INTERVAL ${interval}, 'UTC') as date,
|
||||
SELECT
|
||||
toDateTime(
|
||||
intDiv(
|
||||
toUnixTimestamp(timestamp),
|
||||
toUInt32(${String(interval.seconds)})
|
||||
) * toUInt32(${String(interval.seconds)})
|
||||
) as date,
|
||||
sum(total) as total
|
||||
FROM ${sql.raw(tableName)}
|
||||
PREWHERE
|
||||
timestamp >= toDateTime(${startDateTimeFormatted}, 'UTC')
|
||||
AND
|
||||
timestamp <= toDateTime(${endDateTimeFormatted}, 'UTC')
|
||||
${this.createFilter({ period: roundedPeriod })}
|
||||
GROUP BY date
|
||||
ORDER BY date
|
||||
WITH FILL
|
||||
FROM toDateTime(${startDateTimeFormatted}, 'UTC')
|
||||
TO toDateTime(${endDateTimeFormatted}, 'UTC')
|
||||
STEP INTERVAL ${interval}
|
||||
ORDER BY
|
||||
date
|
||||
WITH FILL
|
||||
FROM toDateTime(${startDateTimeFormatted}, 'UTC')
|
||||
TO toDateTime(${endDateTimeFormatted}, 'UTC')
|
||||
STEP INTERVAL ${intervalRaw}
|
||||
`;
|
||||
|
||||
const result = await this.clickHouse.query<{
|
||||
|
|
|
|||
|
|
@ -43,7 +43,10 @@ const TargetCard = (props: {
|
|||
return props.requestsOverTime.map<[string, number]>(node => [node.date, node.value]);
|
||||
}
|
||||
|
||||
return []; // it will use the previous data points when new data is not available yet (fetching)
|
||||
return [
|
||||
[new Date(subDays(new Date(), props.days)).toISOString(), 0],
|
||||
[new Date().toISOString(), 0],
|
||||
] as [string, number][];
|
||||
}, [props.requestsOverTime]);
|
||||
|
||||
const totalNumberOfRequests = useMemo(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ReactElement, useMemo, useRef } from 'react';
|
||||
import NextLink from 'next/link';
|
||||
import { formatISO } from 'date-fns';
|
||||
import { endOfDay, formatISO, startOfDay } from 'date-fns';
|
||||
import * as echarts from 'echarts';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import { Globe, History } from 'lucide-react';
|
||||
|
|
@ -18,11 +18,7 @@ import { subDays } from '@/lib/date-time';
|
|||
import { useFormattedNumber } from '@/lib/hooks';
|
||||
import { useRouteSelector } from '@/lib/hooks/use-route-selector';
|
||||
import { pluralize } from '@/lib/utils';
|
||||
|
||||
function floorDate(date: Date): Date {
|
||||
const time = 1000 * 60;
|
||||
return new Date(Math.floor(date.getTime() / time) * time);
|
||||
}
|
||||
import { UTCDate } from '@date-fns/utc';
|
||||
|
||||
const ProjectCard_ProjectFragment = graphql(`
|
||||
fragment ProjectCard_ProjectFragment on Project {
|
||||
|
|
@ -56,7 +52,10 @@ const ProjectCard = (props: {
|
|||
return props.requestsOverTime.map<[string, number]>(node => [node.date, node.value]);
|
||||
}
|
||||
|
||||
return []; // it will use the previous data points when new data is not available yet (fetching)
|
||||
return [
|
||||
[new Date(subDays(new Date(), props.days)).toISOString(), 0],
|
||||
[new Date().toISOString(), 0],
|
||||
] as [string, number][];
|
||||
}, [props.requestsOverTime]);
|
||||
|
||||
const totalNumberOfRequests = useMemo(
|
||||
|
|
@ -263,9 +262,9 @@ function OrganizationPageContent() {
|
|||
}>();
|
||||
|
||||
if (!period.current) {
|
||||
const now = floorDate(new Date());
|
||||
const from = formatISO(subDays(now, days));
|
||||
const to = formatISO(now);
|
||||
const now = new UTCDate();
|
||||
const from = formatISO(startOfDay(subDays(now, days)));
|
||||
const to = formatISO(endOfDay(now));
|
||||
|
||||
period.current = { from, to };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { startOfDay } from 'date-fns';
|
|||
import { resolveRange, type Period } from '@/lib/date-math';
|
||||
import { subDays } from '@/lib/date-time';
|
||||
import { useLocalStorage } from '@/lib/hooks';
|
||||
import { UTCDate } from '@date-fns/utc';
|
||||
|
||||
type SchemaExplorerContextType = {
|
||||
isArgumentListCollapsed: boolean;
|
||||
|
|
@ -35,7 +36,7 @@ const SchemaExplorerContext = createContext<SchemaExplorerContextType>({
|
|||
isArgumentListCollapsed: true,
|
||||
setArgumentListCollapsed: () => {},
|
||||
dataRetentionInDays: 7,
|
||||
startDate: startOfDay(subDays(new Date(), 7)),
|
||||
startDate: startOfDay(subDays(new UTCDate(), 7)),
|
||||
period: defaultPeriod,
|
||||
resolvedPeriod: resolveRange(defaultPeriod),
|
||||
setPeriod: () => {},
|
||||
|
|
@ -49,7 +50,7 @@ export function SchemaExplorerProvider({ children }: { children: ReactNode }): R
|
|||
);
|
||||
|
||||
const startDate = useMemo(
|
||||
() => startOfDay(subDays(new Date(), dataRetentionInDays)),
|
||||
() => startOfDay(subDays(new UTCDate(), dataRetentionInDays)),
|
||||
[dataRetentionInDays],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { UTCDate } from '@date-fns/utc';
|
||||
import { parse } from './date-math';
|
||||
|
||||
describe('parse', () => {
|
||||
const now = new Date('1996-06-25');
|
||||
const now = new UTCDate('1996-06-25');
|
||||
it('should parse date', () => {
|
||||
expect(parse('now-10m', now)?.toISOString()).toEqual('1996-06-24T23:50:00.000Z');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* @source https://github.com/grafana/grafana/blob/411c89012febe13323e4b8aafc8d692f4460e680/packages/grafana-data/src/datetime/datemath.ts#L1C1-L208C2
|
||||
*/
|
||||
import { add, format, formatISO, parse as parseDate, sub, type Duration } from 'date-fns';
|
||||
import { UTCDate } from '@date-fns/utc';
|
||||
|
||||
export type Period = {
|
||||
from: string;
|
||||
|
|
@ -48,7 +49,7 @@ const dateStringFormat = 'yyyy-MM-dd';
|
|||
|
||||
function parseDateString(input: string) {
|
||||
try {
|
||||
return parseDate(input, dateStringFormat, new Date());
|
||||
return parseDate(input, dateStringFormat, new UTCDate());
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -69,7 +70,7 @@ function isValidDateString(input: string) {
|
|||
* @param roundUp See parseDateMath function.
|
||||
* @param timezone Only string 'utc' is acceptable here, for anything else, local timezone is used.
|
||||
*/
|
||||
export function parse(text: string, now = new Date()): Date | undefined {
|
||||
export function parse(text: string, now = new UTCDate()): Date | undefined {
|
||||
if (!text) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -176,7 +177,7 @@ export function parseDateMath(mathString: string, now: Date): Date | undefined {
|
|||
}
|
||||
|
||||
export function resolveRange(period: Period) {
|
||||
const now = new Date();
|
||||
const now = new UTCDate();
|
||||
const from = parse(period.from, now);
|
||||
const to = parse(period.to, now);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { UTCDate } from '@date-fns/utc';
|
||||
import { parse } from '../date-math';
|
||||
import { resolveRangeAndResolution } from './use-date-range-controller';
|
||||
|
||||
|
|
@ -61,7 +62,7 @@ describe('useDateRangeController', () => {
|
|||
|
||||
for (const testCase of testCases) {
|
||||
test(`${testCase.now} -> ${testCase.from} to ${testCase.to}`, () => {
|
||||
const now = new Date(testCase.now);
|
||||
const now = new UTCDate(testCase.now);
|
||||
const result = resolveRangeAndResolution(
|
||||
{
|
||||
from: parse(testCase.from, now)!,
|
||||
|
|
|
|||
171
pnpm-lock.yaml
171
pnpm-lock.yaml
|
|
@ -66,7 +66,7 @@ importers:
|
|||
version: 5.0.2(graphql@16.8.1)
|
||||
'@graphql-codegen/cli':
|
||||
specifier: 5.0.2
|
||||
version: 5.0.2(@babel/core@7.24.0)(@types/node@20.12.11)(encoding@0.1.13)(enquirer@2.3.6)(graphql@16.8.1)(typescript@5.4.5)
|
||||
version: 5.0.2(@babel/core@7.22.9)(@types/node@20.12.11)(encoding@0.1.13)(enquirer@2.3.6)(graphql@16.8.1)(typescript@5.4.5)
|
||||
'@graphql-codegen/client-preset':
|
||||
specifier: 4.2.5
|
||||
version: 4.2.5(encoding@0.1.13)(graphql@16.8.1)
|
||||
|
|
@ -90,7 +90,7 @@ importers:
|
|||
version: 3.0.0(graphql@16.8.1)
|
||||
'@graphql-eslint/eslint-plugin':
|
||||
specifier: 3.20.1
|
||||
version: 3.20.1(patch_hash=n437g5o7zq7pnxdxldn52uql2q)(@babel/core@7.24.0)(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)
|
||||
version: 3.20.1(patch_hash=n437g5o7zq7pnxdxldn52uql2q)(@babel/core@7.22.9)(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)
|
||||
'@graphql-inspector/cli':
|
||||
specifier: 4.0.3
|
||||
version: 4.0.3(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)
|
||||
|
|
@ -1949,7 +1949,7 @@ importers:
|
|||
version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@theguild/components':
|
||||
specifier: 6.5.3
|
||||
version: 6.5.3(@types/react@18.3.1)(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5)(webpack@5.89.0(@swc/core@1.5.5(@swc/helpers@0.5.5))(esbuild@0.19.11))
|
||||
version: 6.5.3(@types/react@18.3.1)(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5)(webpack@5.89.0(@swc/core@1.5.5(@swc/helpers@0.5.5))(esbuild@0.19.11))
|
||||
clsx:
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1
|
||||
|
|
@ -1961,13 +1961,13 @@ importers:
|
|||
version: 4.0.3
|
||||
next:
|
||||
specifier: 14.2.3
|
||||
version: 14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-sitemap:
|
||||
specifier: 4.2.3
|
||||
version: 4.2.3(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||
version: 4.2.3(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||
next-themes:
|
||||
specifier: '*'
|
||||
version: 0.2.1(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 0.2.1(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react:
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1
|
||||
|
|
@ -20111,7 +20111,7 @@ snapshots:
|
|||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
|
||||
'@graphql-codegen/cli@5.0.2(@babel/core@7.24.0)(@types/node@20.12.11)(encoding@0.1.13)(enquirer@2.3.6)(graphql@16.8.1)(typescript@5.4.5)':
|
||||
'@graphql-codegen/cli@5.0.2(@babel/core@7.22.9)(@types/node@20.12.11)(encoding@0.1.13)(enquirer@2.3.6)(graphql@16.8.1)(typescript@5.4.5)':
|
||||
dependencies:
|
||||
'@babel/generator': 7.23.6
|
||||
'@babel/template': 7.22.15
|
||||
|
|
@ -20121,7 +20121,7 @@ snapshots:
|
|||
'@graphql-codegen/plugin-helpers': 5.0.3(graphql@16.8.1)
|
||||
'@graphql-tools/apollo-engine-loader': 8.0.0(encoding@0.1.13)(graphql@16.8.1)
|
||||
'@graphql-tools/code-file-loader': 8.1.0(graphql@16.8.1)
|
||||
'@graphql-tools/git-loader': 8.0.1(@babel/core@7.24.0)(graphql@16.8.1)
|
||||
'@graphql-tools/git-loader': 8.0.1(@babel/core@7.22.9)(graphql@16.8.1)
|
||||
'@graphql-tools/github-loader': 8.0.0(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)
|
||||
'@graphql-tools/graphql-file-loader': 8.0.0(graphql@16.8.1)
|
||||
'@graphql-tools/json-file-loader': 8.0.0(graphql@16.8.1)
|
||||
|
|
@ -20315,6 +20315,29 @@ snapshots:
|
|||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@graphql-eslint/eslint-plugin@3.20.1(patch_hash=n437g5o7zq7pnxdxldn52uql2q)(@babel/core@7.22.9)(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.21.4
|
||||
'@graphql-tools/code-file-loader': 7.3.23(@babel/core@7.22.9)(graphql@16.8.1)
|
||||
'@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.22.9)(graphql@16.8.1)
|
||||
'@graphql-tools/utils': 9.2.1(graphql@16.8.1)
|
||||
chalk: 4.1.2
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
fast-glob: 3.2.12
|
||||
graphql: 16.8.1
|
||||
graphql-config: 4.5.0(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)
|
||||
graphql-depth-limit: 1.1.0(graphql@16.8.1)
|
||||
lodash.lowercase: 4.3.0
|
||||
tslib: 2.6.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@types/node'
|
||||
- bufferutil
|
||||
- cosmiconfig-toml-loader
|
||||
- encoding
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
'@graphql-eslint/eslint-plugin@3.20.1(patch_hash=n437g5o7zq7pnxdxldn52uql2q)(@babel/core@7.24.0)(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.21.4
|
||||
|
|
@ -20367,7 +20390,7 @@ snapshots:
|
|||
'@graphql-inspector/graphql-loader': 4.0.2(graphql@16.8.1)
|
||||
'@graphql-inspector/introspect-command': 4.0.3(@graphql-inspector/config@4.0.2(graphql@16.8.1))(@graphql-inspector/loaders@4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1))(graphql@16.8.1)(yargs@17.7.2)
|
||||
'@graphql-inspector/json-loader': 4.0.2(graphql@16.8.1)
|
||||
'@graphql-inspector/loaders': 4.0.3(@babel/core@7.24.0)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1)
|
||||
'@graphql-inspector/loaders': 4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1)
|
||||
'@graphql-inspector/serve-command': 4.0.3(@graphql-inspector/config@4.0.2(graphql@16.8.1))(@graphql-inspector/loaders@4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1))(graphql@16.8.1)(yargs@17.7.2)
|
||||
'@graphql-inspector/similar-command': 4.0.3(@graphql-inspector/config@4.0.2(graphql@16.8.1))(@graphql-inspector/loaders@4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1))(graphql@16.8.1)(yargs@17.7.2)
|
||||
'@graphql-inspector/url-loader': 4.0.2(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)
|
||||
|
|
@ -20394,7 +20417,7 @@ snapshots:
|
|||
'@graphql-inspector/commands@4.0.3(@graphql-inspector/config@4.0.2(graphql@16.8.1))(@graphql-inspector/loaders@4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1))(graphql@16.8.1)(yargs@17.7.2)':
|
||||
dependencies:
|
||||
'@graphql-inspector/config': 4.0.2(graphql@16.8.1)
|
||||
'@graphql-inspector/loaders': 4.0.3(@babel/core@7.24.0)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1)
|
||||
'@graphql-inspector/loaders': 4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1)
|
||||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
yargs: 17.7.2
|
||||
|
|
@ -20497,10 +20520,10 @@ snapshots:
|
|||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
|
||||
'@graphql-inspector/loaders@4.0.3(@babel/core@7.24.0)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1)':
|
||||
'@graphql-inspector/loaders@4.0.3(@babel/core@7.22.9)(@graphql-inspector/config@4.0.2(graphql@16.8.1))(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@graphql-inspector/config': 4.0.2(graphql@16.8.1)
|
||||
'@graphql-tools/code-file-loader': 8.0.1(@babel/core@7.24.0)(graphql@16.8.1)
|
||||
'@graphql-tools/code-file-loader': 8.0.1(@babel/core@7.22.9)(graphql@16.8.1)
|
||||
'@graphql-tools/load': 8.0.0(graphql@16.8.1)
|
||||
'@graphql-tools/utils': 10.0.3(graphql@16.8.1)
|
||||
graphql: 16.8.1
|
||||
|
|
@ -20609,6 +20632,18 @@ snapshots:
|
|||
tslib: 2.6.2
|
||||
value-or-promise: 1.0.12
|
||||
|
||||
'@graphql-tools/code-file-loader@7.3.23(@babel/core@7.22.9)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.22.9)(graphql@16.8.1)
|
||||
'@graphql-tools/utils': 9.2.1(graphql@16.8.1)
|
||||
globby: 11.1.0
|
||||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
unixify: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
'@graphql-tools/code-file-loader@7.3.23(@babel/core@7.24.0)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.24.0)(graphql@16.8.1)
|
||||
|
|
@ -20633,18 +20668,6 @@ snapshots:
|
|||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
'@graphql-tools/code-file-loader@8.0.1(@babel/core@7.24.0)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@graphql-tools/graphql-tag-pluck': 8.0.1(@babel/core@7.24.0)(graphql@16.8.1)
|
||||
'@graphql-tools/utils': 10.2.0(graphql@16.8.1)
|
||||
globby: 11.1.0
|
||||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
unixify: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
'@graphql-tools/code-file-loader@8.1.0(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@graphql-tools/graphql-tag-pluck': 8.2.0(graphql@16.8.1)
|
||||
|
|
@ -20824,19 +20847,6 @@ snapshots:
|
|||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
'@graphql-tools/git-loader@8.0.1(@babel/core@7.24.0)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@graphql-tools/graphql-tag-pluck': 8.0.1(@babel/core@7.24.0)(graphql@16.8.1)
|
||||
'@graphql-tools/utils': 10.2.0(graphql@16.8.1)
|
||||
graphql: 16.8.1
|
||||
is-glob: 4.0.3
|
||||
micromatch: 4.0.5
|
||||
tslib: 2.6.2
|
||||
unixify: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
'@graphql-tools/github-loader@8.0.0(@types/node@20.12.11)(encoding@0.1.13)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@ardatan/sync-fetch': 0.0.1(encoding@0.1.13)
|
||||
|
|
@ -20870,6 +20880,19 @@ snapshots:
|
|||
tslib: 2.6.2
|
||||
unixify: 1.0.0
|
||||
|
||||
'@graphql-tools/graphql-tag-pluck@7.5.2(@babel/core@7.22.9)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.22.9)
|
||||
'@babel/traverse': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
'@graphql-tools/utils': 9.2.1(graphql@16.8.1)
|
||||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
'@graphql-tools/graphql-tag-pluck@7.5.2(@babel/core@7.24.0)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@babel/parser': 7.24.0
|
||||
|
|
@ -20896,19 +20919,6 @@ snapshots:
|
|||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
'@graphql-tools/graphql-tag-pluck@8.0.1(@babel/core@7.24.0)(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.24.0)
|
||||
'@babel/traverse': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
'@graphql-tools/utils': 10.2.0(graphql@16.8.1)
|
||||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
'@graphql-tools/graphql-tag-pluck@8.2.0(graphql@16.8.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
|
|
@ -25611,16 +25621,16 @@ snapshots:
|
|||
|
||||
'@theguild/buddy@0.1.0(patch_hash=ryylgra5xglhidfoiaxehn22hq)': {}
|
||||
|
||||
'@theguild/components@6.5.3(@types/react@18.3.1)(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5)(webpack@5.89.0(@swc/core@1.5.5(@swc/helpers@0.5.5))(esbuild@0.19.11))':
|
||||
'@theguild/components@6.5.3(@types/react@18.3.1)(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5)(webpack@5.89.0(@swc/core@1.5.5(@swc/helpers@0.5.5))(esbuild@0.19.11))':
|
||||
dependencies:
|
||||
'@giscus/react': 3.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@next/bundle-analyzer': 13.4.2
|
||||
clsx: 2.1.0
|
||||
fuzzy: 0.1.3
|
||||
next: 14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-videos: 1.5.0(webpack@5.89.0(@swc/core@1.5.5(@swc/helpers@0.5.5))(esbuild@0.19.11))
|
||||
nextra: 3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5)
|
||||
nextra-theme-docs: 3.0.0-alpha.22(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
nextra: 3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5)
|
||||
nextra-theme-docs: 3.0.0-alpha.22(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-paginate: 8.2.0(react@18.3.1)
|
||||
|
|
@ -33437,17 +33447,17 @@ snapshots:
|
|||
|
||||
neo-async@2.6.2: {}
|
||||
|
||||
next-sitemap@4.2.3(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
||||
next-sitemap@4.2.3(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
||||
dependencies:
|
||||
'@corex/deepmerge': 4.0.43
|
||||
'@next/env': 13.5.6
|
||||
fast-glob: 3.3.2
|
||||
minimist: 1.2.8
|
||||
next: 14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
||||
next-themes@0.2.1(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
next-themes@0.2.1(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
next: 14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
|
|
@ -33459,6 +33469,32 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- webpack
|
||||
|
||||
next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@next/env': 14.2.3
|
||||
'@swc/helpers': 0.5.5
|
||||
busboy: 1.6.0
|
||||
caniuse-lite: 1.0.30001600
|
||||
graceful-fs: 4.2.11
|
||||
postcss: 8.4.31
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
styled-jsx: 5.1.1(@babel/core@7.22.9)(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 14.2.3
|
||||
'@next/swc-darwin-x64': 14.2.3
|
||||
'@next/swc-linux-arm64-gnu': 14.2.3
|
||||
'@next/swc-linux-arm64-musl': 14.2.3
|
||||
'@next/swc-linux-x64-gnu': 14.2.3
|
||||
'@next/swc-linux-x64-musl': 14.2.3
|
||||
'@next/swc-win32-arm64-msvc': 14.2.3
|
||||
'@next/swc-win32-ia32-msvc': 14.2.3
|
||||
'@next/swc-win32-x64-msvc': 14.2.3
|
||||
'@opentelemetry/api': 1.8.0
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@next/env': 14.2.3
|
||||
|
|
@ -33485,7 +33521,7 @@ snapshots:
|
|||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
nextra-theme-docs@3.0.0-alpha.22(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
nextra-theme-docs@3.0.0-alpha.22(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@headlessui/react': 1.7.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@popperjs/core': 2.11.8
|
||||
|
|
@ -33494,15 +33530,15 @@ snapshots:
|
|||
flexsearch: 0.7.43
|
||||
focus-visible: 5.2.0
|
||||
intersection-observer: 0.12.2
|
||||
next: 14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-themes: 0.2.1(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
nextra: 3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5)
|
||||
next: 14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next-themes: 0.2.1(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
nextra: 3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
scroll-into-view-if-needed: 3.1.0
|
||||
zod: 3.23.8
|
||||
|
||||
nextra@3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5):
|
||||
nextra@3.0.0-alpha.22(@types/react@18.3.1)(next@14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.4.5):
|
||||
dependencies:
|
||||
'@headlessui/react': 1.7.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mdx-js/mdx': 3.0.1
|
||||
|
|
@ -33520,7 +33556,7 @@ snapshots:
|
|||
gray-matter: 4.0.3
|
||||
hast-util-to-estree: 3.1.0
|
||||
katex: 0.16.9
|
||||
next: 14.2.3(@babel/core@7.24.0)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
next: 14.2.3(@babel/core@7.22.9)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
p-limit: 4.0.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
|
@ -36562,6 +36598,13 @@ snapshots:
|
|||
hey-listen: 1.0.8
|
||||
tslib: 2.6.2
|
||||
|
||||
styled-jsx@5.1.1(@babel/core@7.22.9)(react@18.3.1):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.22.9
|
||||
|
||||
styled-jsx@5.1.1(@babel/core@7.24.0)(react@18.3.1):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue