Fix interval calculation in ClickHouse queries (#4683)

This commit is contained in:
Kamil Kisiela 2024-05-09 13:00:34 +02:00 committed by GitHub
parent b94f3bc73a
commit fccb3514c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 434 additions and 229 deletions

View file

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

View file

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

View file

@ -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');
});
});

View file

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

View file

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

View file

@ -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],
};
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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],
);

View file

@ -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');
});

View file

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

View file

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

View file

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