mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
chore: Prevent Date.now() and new Date() via eslint (#1937)
## Summary This PR adds lint rules disallowing Date.now() and new Date(), which can cause unnecessary re-renders. ### Screenshots or video No behavior changes are expected. ### How to test locally or on Vercel This can be tested in the preview environment - it is an app-only change ### References - Linear Issue: Closes HDX-2187 - Related PRs:
This commit is contained in:
parent
50aa44bd39
commit
2b53b8e9ab
22 changed files with 140 additions and 75 deletions
|
|
@ -11,6 +11,76 @@ import playwrightPlugin from 'eslint-plugin-playwright';
|
|||
import reactHookFormPlugin from 'eslint-plugin-react-hook-form';
|
||||
import { fixupPluginRules } from '@eslint/compat';
|
||||
|
||||
// Kept separate so test overrides can drop just the date rules while keeping
|
||||
// the UI style rules (bi-icons, Button/ActionIcon variants).
|
||||
const UI_SYNTAX_RESTRICTIONS = [
|
||||
// Temporary rule to enforce use of @tabler/icons-react instead of bi bi-icons
|
||||
// Will remove after we've updated all icons and let some PRs merge.
|
||||
{
|
||||
selector: 'Literal[value=/\\bbi-\\b/i]',
|
||||
message: 'Please update to use @tabler/icons-react instead',
|
||||
},
|
||||
// Enforce custom Button/ActionIcon variants (see agent_docs/code_style.md)
|
||||
// NOTE: Icon-only Buttons should use ActionIcon instead - this requires manual review
|
||||
// as ESLint cannot detect children content patterns
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="light"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="filled"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="outline"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="default"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="light"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="filled"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="outline"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
|
||||
},
|
||||
];
|
||||
|
||||
const DATE_SYNTAX_RESTRICTIONS = [
|
||||
{
|
||||
selector:
|
||||
'CallExpression[callee.object.name="Date"][callee.property.name="now"]',
|
||||
message:
|
||||
'Date.now() can cause unnecessary re-renders. Import NOW from @/config for a stable reference, or wrap in useMemo/useCallback for values that must be current.',
|
||||
},
|
||||
{
|
||||
selector: 'NewExpression[callee.name="Date"][arguments.length=0]',
|
||||
message:
|
||||
'new Date() can cause unnecessary re-renders. Use new Date(NOW) for a stable reference, or wrap in useMemo/useCallback for values that must be current.',
|
||||
},
|
||||
];
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
|
|
@ -80,59 +150,10 @@ export default [
|
|||
],
|
||||
},
|
||||
],
|
||||
// Temporary rule to enforce use of @tabler/icons-react instead of bi bi-icons
|
||||
// Will remove after we've updated all icons and let some PRs merge.
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: 'Literal[value=/\\bbi-\\b/i]',
|
||||
message: 'Please update to use @tabler/icons-react instead',
|
||||
},
|
||||
// Enforce custom Button/ActionIcon variants (see agent_docs/code_style.md)
|
||||
// NOTE: Icon-only Buttons should use ActionIcon instead - this requires manual review
|
||||
// as ESLint cannot detect children content patterns
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="light"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="filled"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="outline"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="Button"] JSXAttribute[name.name="variant"][value.value="default"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for Button. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="light"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="filled"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
'JSXElement[openingElement.name.name="ActionIcon"] JSXAttribute[name.name="variant"][value.value="outline"]',
|
||||
message:
|
||||
'Use variant="primary", "secondary", or "danger" for ActionIcon. See agent_docs/code_style.md',
|
||||
},
|
||||
...UI_SYNTAX_RESTRICTIONS,
|
||||
...DATE_SYNTAX_RESTRICTIONS,
|
||||
],
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
|
|
@ -179,6 +200,13 @@ export default [
|
|||
'@typescript-eslint/no-unsafe-type-assertion': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/**/__tests__/**/*.{ts,tsx}', 'src/**/*.test.{ts,tsx}'],
|
||||
rules: {
|
||||
// Drop date rules — new Date() / Date.now() are fine in tests
|
||||
'no-restricted-syntax': ['error', ...UI_SYNTAX_RESTRICTIONS],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['tests/e2e/**/*.{ts,js}'],
|
||||
...playwrightPlugin.configs['flat/recommended'],
|
||||
|
|
@ -189,6 +217,8 @@ export default [
|
|||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
'playwright/no-networkidle': 'off', // temporary until we have a better way to deal with react re-renders
|
||||
// Drop date rules — Date.now() is fine in e2e tests for unique IDs/timestamps
|
||||
'no-restricted-syntax': ['error', ...UI_SYNTAX_RESTRICTIONS],
|
||||
},
|
||||
},
|
||||
...storybook.configs['flat/recommended'],
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ function AlertHistoryCard({
|
|||
alertUrl: string;
|
||||
}) {
|
||||
const start = new Date(history.createdAt.toString());
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const today = React.useMemo(() => new Date(), []);
|
||||
|
||||
const href = React.useMemo(() => {
|
||||
|
|
@ -141,6 +143,7 @@ function AckAlert({ alert }: { alert: AlertsPageItem }) {
|
|||
|
||||
const handleSilenceAlert = React.useCallback(
|
||||
(duration: Duration) => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const mutedUntil = add(new Date(), duration);
|
||||
silenceAlert.mutate(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -212,13 +212,16 @@ function BenchmarkPage() {
|
|||
// Hack to get time range
|
||||
useEffect(() => {
|
||||
if (_queries.length > 0 && _connections.length > 0) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
setStartTime(new Date(Date.now() - 1000));
|
||||
}
|
||||
}, [_queries, _connections]);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryIds != null && queryIds[0] != null) {
|
||||
setEndTime(
|
||||
new Date(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
Date.now() - 1000 * 9, // minus hard-coded flush interval
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ const Tile = forwardRef(
|
|||
let tooltip = `Has alert and is in ${alert.state} state`;
|
||||
if (alert.silenced?.at) {
|
||||
const silencedAt = new Date(alert.silenced.at);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
tooltip += `. Ack'd ${formatRelative(silencedAt, new Date())}`;
|
||||
}
|
||||
return tooltip;
|
||||
|
|
|
|||
|
|
@ -602,6 +602,7 @@ function useLiveUpdate({
|
|||
const [refreshOnVisible, setRefreshOnVisible] = useState(false);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
onTimeRangeSelect(new Date(Date.now() - interval), new Date(), null);
|
||||
}, [onTimeRangeSelect, interval]);
|
||||
|
||||
|
|
|
|||
|
|
@ -136,8 +136,8 @@ export default function DOMPlayer({
|
|||
serviceName,
|
||||
sessionId,
|
||||
sourceId,
|
||||
startDate: dateRange?.[0] ?? new Date(),
|
||||
endDate: dateRange?.[1] ?? new Date(),
|
||||
startDate: dateRange[0],
|
||||
endDate: dateRange[1],
|
||||
limit: 1000000, // large enough to get all events
|
||||
onEvent: (event: { b: string; ck: number; tcks: number; t: number }) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import {
|
|||
K8S_CPU_PERCENTAGE_NUMBER_FORMAT,
|
||||
K8S_MEM_NUMBER_FORMAT,
|
||||
} from './ChartUtils';
|
||||
import { NOW } from './config';
|
||||
import { withAppNav } from './layout';
|
||||
import NamespaceDetailsSidePanel from './NamespaceDetailsSidePanel';
|
||||
import NodeDetailsSidePanel from './NodeDetailsSidePanel';
|
||||
|
|
@ -772,8 +773,8 @@ const NamespacesTable = ({
|
|||
dateRange: [
|
||||
// We should only look at the latest values, otherwise we might
|
||||
// aggregate pod metrics from pods that have been terminated
|
||||
sub(dateRange[1] ?? new Date(), { minutes: 5 }),
|
||||
dateRange[1] ?? new Date(),
|
||||
sub(dateRange[1], { minutes: 5 }),
|
||||
dateRange[1],
|
||||
],
|
||||
seriesReturnType: 'column',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
|
||||
import { useQueriedChartConfig } from './hooks/useChartConfig';
|
||||
import api from './api';
|
||||
import { NOW } from './config';
|
||||
import { useConnections } from './connection';
|
||||
import { useSources } from './source';
|
||||
import { useLocalStorage } from './utils';
|
||||
|
|
@ -34,8 +35,6 @@ interface OnboardingStep {
|
|||
href?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const NOW = Date.now();
|
||||
const OnboardingChecklist = ({
|
||||
onAddDataClick,
|
||||
}: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import {
|
||||
|
|
@ -62,15 +62,12 @@ export default function SessionSidePanel({
|
|||
},
|
||||
);
|
||||
|
||||
// console.log({ logId: sessionId, subDrawerOpen });
|
||||
const maxTime =
|
||||
session != null ? new Date(session?.maxTimestamp) : new Date();
|
||||
// const minTime =
|
||||
// session != null ? new Date(session?.['min_timestamp']) : new Date();
|
||||
const timeAgo = formatDistanceToNowStrictShort(maxTime);
|
||||
// const durationStr = new Date(maxTime.getTime() - minTime.getTime())
|
||||
// .toISOString()
|
||||
// .slice(11, 19);
|
||||
const timeAgo = useMemo(() => {
|
||||
const maxTime =
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
session != null ? new Date(session?.maxTimestamp) : new Date();
|
||||
return formatDistanceToNowStrictShort(maxTime);
|
||||
}, [session]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Papa from 'papaparse';
|
|||
|
||||
interface CsvExportButtonProps {
|
||||
data: Record<string, any>[];
|
||||
filename: string;
|
||||
filename: string | (() => string);
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
title?: string;
|
||||
|
|
@ -46,7 +46,8 @@ export const CsvExportButton: React.FC<CsvExportButtonProps> = ({
|
|||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${filename}.csv`;
|
||||
a.download =
|
||||
typeof filename === 'string' ? `${filename}.csv` : `${filename()}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ const InfraSubpanelGroup = ({
|
|||
}[range];
|
||||
return [
|
||||
sub(new Date(timestamp), duration),
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
min([add(new Date(timestamp), duration), new Date()]),
|
||||
];
|
||||
}, [timestamp, range]);
|
||||
|
|
|
|||
|
|
@ -932,6 +932,15 @@ export const RawLogTable = memo(
|
|||
shiftHighlightedLineId(-1);
|
||||
});
|
||||
|
||||
const getCsvFilename = useCallback(() => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
.replace(/[:.]/g, '-')
|
||||
.slice(0, 19);
|
||||
return `hyperdx_search_results_${timestamp}.csv`;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Flex direction="column" h="100%">
|
||||
<Box pos="relative" style={{ flex: 1, minHeight: 0 }}>
|
||||
|
|
@ -1050,7 +1059,7 @@ export const RawLogTable = memo(
|
|||
|
||||
<CsvExportButton
|
||||
data={csvData}
|
||||
filename={`hyperdx_search_results_${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}`}
|
||||
filename={getCsvFilename}
|
||||
className="fs-6"
|
||||
>
|
||||
<MantineTooltip
|
||||
|
|
|
|||
|
|
@ -176,10 +176,12 @@ export const KubeTimeline = ({
|
|||
anchorEvent?: AnchorEvent;
|
||||
}) => {
|
||||
const startDate = React.useMemo(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
() => dateRange?.[0] ?? sub(new Date(), { days: 1 }),
|
||||
[dateRange],
|
||||
);
|
||||
const endDate = React.useMemo(
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
() => dateRange?.[1] ?? new Date(),
|
||||
[dateRange],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const chartConfigByMetricType = ({
|
|||
metricSource: TSource;
|
||||
metricType: MetricsDataType;
|
||||
}) => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const now = new Date();
|
||||
let _dateRange: DateRange['dateRange'] = dateRange
|
||||
? dateRange
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ const TimePickerComponent = ({
|
|||
|
||||
useHotkeys('d', () => toggle(), { preventDefault: true }, [toggle]);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const today = React.useMemo(() => new Date(), []);
|
||||
|
||||
const relativeTimeOptions = React.useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ function normalizeParsedDate(parsed?: chrono.ParsedComponents): Date | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const now = new Date();
|
||||
const parsedDate = parsed.date();
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ export function parseTimeRangeInput(
|
|||
? parsedTimeResults[0]
|
||||
: parsedTimeResults[1];
|
||||
const start = normalizeParsedDate(parsedTimeResult.start);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const end = normalizeParsedDate(parsedTimeResult.end) || new Date();
|
||||
if (end && start && end < start) {
|
||||
// For date range strings that omit years, the chrono parser will infer the year
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ export const IS_LOCAL_MODE = //true;
|
|||
export const IS_CLICKHOUSE_BUILD =
|
||||
process.env.NEXT_PUBLIC_CLICKHOUSE_BUILD === 'true';
|
||||
|
||||
/** Time captured at module load, use this a stable fallback/default time value instead of Date.now() defined in each React component file */
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const NOW = Date.now();
|
||||
|
||||
// Features in development
|
||||
export const IS_K8S_DASHBOARD_ENABLED = true;
|
||||
export const IS_METRICS_ENABLED = true;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
} from '@hyperdx/common-utils/dist/core/metadata';
|
||||
import { BuilderChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
|
||||
|
||||
import { NOW } from '@/config';
|
||||
import {
|
||||
deduplicate2dArray,
|
||||
useJsonColumns,
|
||||
|
|
@ -19,9 +20,6 @@ export interface ILanguageFormatter {
|
|||
formatKeyValPair: (key: string, value: string) => string;
|
||||
}
|
||||
|
||||
// Defined outside of the component to fix rerenders
|
||||
const NOW = Date.now();
|
||||
|
||||
export function useAutoCompleteOptions(
|
||||
formatter: ILanguageFormatter,
|
||||
value: string,
|
||||
|
|
@ -118,10 +116,12 @@ export function useAutoCompleteOptions(
|
|||
// just assuming 1/2 day is okay to query over right now
|
||||
dateRange: [new Date(NOW - (86400 * 1000) / 2), new Date(NOW)],
|
||||
}));
|
||||
|
||||
const { data: keyVals } = useMultipleGetKeyValues({
|
||||
chartConfigs,
|
||||
keys: searchKeys,
|
||||
});
|
||||
|
||||
const keyValCompleteOptions = useMemo<
|
||||
{ value: string; label: string }[]
|
||||
>(() => {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export const useDashboardRefresh = ({
|
|||
const timeDiff =
|
||||
searchedTimeRange[1].getTime() - searchedTimeRange[0].getTime();
|
||||
const timeDiffRoundedToSecond = Math.round(timeDiff / 1000) * 1000;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const newEnd = new Date();
|
||||
const newStart = new Date(newEnd.getTime() - timeDiffRoundedToSecond);
|
||||
onTimeRangeSelect(newStart, newEnd);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ function isInputTimeQueryLive(inputTimeQuery: string) {
|
|||
}
|
||||
|
||||
export function parseRelativeTimeQuery(interval: number) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const end = startOfSecond(new Date());
|
||||
return [subMilliseconds(end, interval), end];
|
||||
}
|
||||
|
|
@ -260,6 +261,7 @@ export function useTimeQuery({
|
|||
) {
|
||||
// If we haven't set a live tail time range yet, but we're ready and should be in live tail, let's just return one right now
|
||||
// this is due to the first interval of live tail not kicking in until 2 seconds after our first render
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const end = startOfSecond(new Date());
|
||||
const newLiveTailTimeRange: [Date, Date] = [
|
||||
sub(end, { minutes: 15 }),
|
||||
|
|
@ -269,6 +271,7 @@ export function useTimeQuery({
|
|||
} else {
|
||||
// We're not ready yet, safe to return anything.
|
||||
// Downstream querying components need to be disabled on isReady
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return [new Date(), new Date()];
|
||||
}
|
||||
}, [
|
||||
|
|
@ -292,6 +295,7 @@ export function useTimeQuery({
|
|||
);
|
||||
}, [isReady, isLiveEnabled, timeRangeQuery, inputTimeQuery]);
|
||||
const refreshLiveTailTimeRange = () => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const end = startOfSecond(new Date());
|
||||
setLiveTailTimeRange([sub(end, { minutes: 15 }), end]);
|
||||
};
|
||||
|
|
@ -529,6 +533,7 @@ export function useNewTimeQuery({
|
|||
}
|
||||
|
||||
export function getLiveTailTimeRange(): [Date, Date] {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const end = startOfSecond(new Date());
|
||||
return [sub(end, { minutes: 15 }), end];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { TableConnection } from '@hyperdx/common-utils/dist/core/metadata';
|
|||
import { SourceKind, TSource } from '@hyperdx/common-utils/dist/types';
|
||||
import { SortingState } from '@tanstack/react-table';
|
||||
|
||||
import { NOW } from './config';
|
||||
import { dateRangeToString } from './timeQuery';
|
||||
import { MetricsDataType, NumberFormat } from './types';
|
||||
|
||||
|
|
@ -35,8 +36,8 @@ export function generateSearchUrl({
|
|||
lineId?: string;
|
||||
isUTC?: boolean;
|
||||
}) {
|
||||
const fromDate = dateRange ? dateRange[0] : new Date();
|
||||
const toDate = dateRange ? dateRange[1] : new Date();
|
||||
const fromDate = dateRange ? dateRange[0] : new Date(NOW);
|
||||
const toDate = dateRange ? dateRange[1] : new Date(NOW);
|
||||
const qparams = new URLSearchParams({
|
||||
q: query ?? '',
|
||||
from: fromDate.getTime().toString(),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export function intervalToMinutes(interval: AlertInterval): number {
|
|||
}
|
||||
|
||||
export function intervalToDateRange(interval: AlertInterval): [Date, Date] {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const now = new Date();
|
||||
if (interval === '1m') return [sub(now, { minutes: 15 }), now];
|
||||
if (interval === '5m') return [sub(now, { hours: 1 }), now];
|
||||
|
|
@ -135,6 +136,7 @@ export const DEFAULT_TILE_ALERT: z.infer<typeof ChartAlertBaseSchema> = {
|
|||
export function isAlertSilenceExpired(silenced?: {
|
||||
until: string | Date;
|
||||
}): boolean {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return silenced ? new Date() > new Date(silenced.until) : false;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue