diff --git a/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx b/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx index a29f0180b4..114db17924 100644 --- a/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx +++ b/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx @@ -5,13 +5,14 @@ import './CustomTimePicker.styles.scss'; import { Input, Popover, Tooltip, Typography } from 'antd'; import cx from 'classnames'; import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal'; -import { Options } from 'container/TopNav/DateTimeSelection/config'; import { FixedDurationSuggestionOptions, + Options, RelativeDurationSuggestionOptions, } from 'container/TopNav/DateTimeSelectionV2/config'; import dayjs from 'dayjs'; -import { defaultTo, noop } from 'lodash-es'; +import { isValidTimeFormat } from 'lib/getMinMax'; +import { defaultTo, isFunction, noop } from 'lodash-es'; import debounce from 'lodash-es/debounce'; import { CheckCircle, ChevronDown, Clock } from 'lucide-react'; import { @@ -33,7 +34,14 @@ interface CustomTimePickerProps { onError: (value: boolean) => void; selectedValue: string; selectedTime: string; - onValidCustomDateChange: ([t1, t2]: any[]) => void; + onValidCustomDateChange: ({ + time: [t1, t2], + timeStr, + }: { + time: [dayjs.Dayjs | null, dayjs.Dayjs | null]; + timeStr: string; + }) => void; + onCustomTimeStatusUpdate?: (isValid: boolean) => void; open: boolean; setOpen: Dispatch>; items: any[]; @@ -53,6 +61,7 @@ function CustomTimePicker({ open, setOpen, onValidCustomDateChange, + onCustomTimeStatusUpdate, newPopover, customDateTimeVisible, setCustomDTPickerVisible, @@ -85,6 +94,7 @@ function CustomTimePicker({ return Options[index].label; } } + for ( let index = 0; index < RelativeDurationSuggestionOptions.length; @@ -94,12 +104,17 @@ function CustomTimePicker({ return RelativeDurationSuggestionOptions[index].label; } } + for (let index = 0; index < FixedDurationSuggestionOptions.length; index++) { if (FixedDurationSuggestionOptions[index].value === selectedTime) { return FixedDurationSuggestionOptions[index].label; } } + if (isValidTimeFormat(selectedTime)) { + return selectedTime; + } + return ''; }; @@ -161,13 +176,22 @@ function CustomTimePicker({ setInputStatus('error'); onError(true); setInputErrorMessage('Please enter time less than 6 months'); + if (isFunction(onCustomTimeStatusUpdate)) { + onCustomTimeStatusUpdate(true); + } } else { - onValidCustomDateChange([minTime, currentTime]); + onValidCustomDateChange({ + time: [minTime, currentTime], + timeStr: inputValue, + }); } } else { setInputStatus('error'); onError(true); setInputErrorMessage(null); + if (isFunction(onCustomTimeStatusUpdate)) { + onCustomTimeStatusUpdate(false); + } } }, 300); @@ -320,4 +344,5 @@ CustomTimePicker.defaultProps = { setCustomDTPickerVisible: noop, onCustomDateHandler: noop, handleGoLive: noop, + onCustomTimeStatusUpdate: noop, }; diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index 31ec5fcd20..6b70ae9786 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -29,4 +29,5 @@ export enum QueryParams { expandedWidgetId = 'expandedWidgetId', integration = 'integration', pagination = 'pagination', + relativeTime = 'relativeTime', } diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index 6e8c167c29..2110216cf1 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -7,7 +7,10 @@ import GridPanelSwitch from 'container/GridPanelSwitch'; import { getFormatNameByOptionId } from 'container/NewWidget/RightContainer/alertFomatCategories'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useResizeObserver } from 'hooks/useDimensions'; @@ -39,7 +42,7 @@ export interface ChartPreviewProps { query: Query | null; graphType?: PANEL_TYPES; selectedTime?: timePreferenceType; - selectedInterval?: Time | TimeV2; + selectedInterval?: Time | TimeV2 | CustomTimeType; headline?: JSX.Element; alertDef?: AlertDef; userQueryKey?: string; @@ -53,7 +56,7 @@ function ChartPreview({ query, graphType = PANEL_TYPES.TIME_SERIES, selectedTime = 'GLOBAL_TIME', - selectedInterval = '5min', + selectedInterval = '5m', headline, userQueryKey, allowSelectedIntervalForStepGen = false, diff --git a/frontend/src/container/FormAlertRules/utils.ts b/frontend/src/container/FormAlertRules/utils.ts index 0d41ac5197..3015f0c426 100644 --- a/frontend/src/container/FormAlertRules/utils.ts +++ b/frontend/src/container/FormAlertRules/utils.ts @@ -12,22 +12,30 @@ import { // toChartInterval converts eval window to chart selection time interval export const toChartInterval = (evalWindow: string | undefined): Time => { switch (evalWindow) { + case '1m0s': + return '1m'; case '5m0s': - return '5min'; + return '5m'; case '10m0s': - return '10min'; + return '10m'; case '15m0s': - return '15min'; + return '15m'; case '30m0s': - return '30min'; + return '30m'; case '1h0m0s': - return '1hr'; + return '1h'; + case '3h0m0s': + return '3h'; case '4h0m0s': - return '4hr'; + return '4h'; + case '6h0m0s': + return '6h'; + case '12h0m0s': + return '12h'; case '24h0m0s': - return '1day'; + return '1d'; default: - return '5min'; + return '5m'; } }; diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index 33df881c80..1633f0b947 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -1,6 +1,7 @@ import { DEFAULT_ENTITY_VERSION } from 'constants/app'; import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; +import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; import { useIsDarkMode } from 'hooks/useDarkMode'; @@ -81,8 +82,13 @@ function GridCardGraph({ const searchParams = new URLSearchParams(window.location.search); const startTime = searchParams.get(QueryParams.startTime); const endTime = searchParams.get(QueryParams.endTime); + const relativeTime = searchParams.get( + QueryParams.relativeTime, + ) as CustomTimeType; - if (startTime && endTime && startTime !== endTime) { + if (relativeTime) { + dispatch(UpdateTimeInterval(relativeTime)); + } else if (startTime && endTime && startTime !== endTime) { dispatch( UpdateTimeInterval('custom', [ parseInt(getTimeString(startTime), 10), diff --git a/frontend/src/container/LogsExplorerChart/index.tsx b/frontend/src/container/LogsExplorerChart/index.tsx index 2f909bea25..7f4d648529 100644 --- a/frontend/src/container/LogsExplorerChart/index.tsx +++ b/frontend/src/container/LogsExplorerChart/index.tsx @@ -2,6 +2,7 @@ import Graph from 'components/Graph'; import Spinner from 'components/Spinner'; import { QueryParams } from 'constants/query'; import { themeColors } from 'constants/theme'; +import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config'; import useUrlQuery from 'hooks/useUrlQuery'; import getChartData, { GetChartDataProps } from 'lib/getChartData'; import GetMinMax from 'lib/getMinMax'; @@ -65,8 +66,13 @@ function LogsExplorerChart({ const searchParams = new URLSearchParams(window.location.search); const startTime = searchParams.get(QueryParams.startTime); const endTime = searchParams.get(QueryParams.endTime); + const relativeTime = searchParams.get( + QueryParams.relativeTime, + ) as CustomTimeType; - if (startTime && endTime && startTime !== endTime) { + if (relativeTime) { + dispatch(UpdateTimeInterval(relativeTime)); + } else if (startTime && endTime && startTime !== endTime) { dispatch( UpdateTimeInterval('custom', [ parseInt(getTimeString(startTime), 10), diff --git a/frontend/src/container/LogsSearchFilter/utils.ts b/frontend/src/container/LogsSearchFilter/utils.ts index 390a3c14b0..91f47e6ecc 100644 --- a/frontend/src/container/LogsSearchFilter/utils.ts +++ b/frontend/src/container/LogsSearchFilter/utils.ts @@ -1,9 +1,12 @@ import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import { GetMinMaxPayload } from 'lib/getMinMax'; export const getGlobalTime = ( - selectedTime: Time | TimeV2, + selectedTime: Time | TimeV2 | CustomTimeType, globalTime: GetMinMaxPayload, ): GetMinMaxPayload | undefined => { if (selectedTime === 'custom') { diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx index aa7553af53..7245d960f9 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx @@ -3,6 +3,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import GridPanelSwitch from 'container/GridPanelSwitch'; import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types'; import { timePreferance } from 'container/NewWidget/RightContainer/timeItems'; +import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useResizeObserver } from 'hooks/useDimensions'; @@ -97,8 +98,13 @@ function WidgetGraph({ const searchParams = new URLSearchParams(window.location.search); const startTime = searchParams.get(QueryParams.startTime); const endTime = searchParams.get(QueryParams.endTime); + const relativeTime = searchParams.get( + QueryParams.relativeTime, + ) as CustomTimeType; - if (startTime && endTime && startTime !== endTime) { + if (relativeTime) { + dispatch(UpdateTimeInterval(relativeTime)); + } else if (startTime && endTime && startTime !== endTime) { dispatch( UpdateTimeInterval('custom', [ parseInt(getTimeString(startTime), 10), diff --git a/frontend/src/container/ServiceApplication/types.ts b/frontend/src/container/ServiceApplication/types.ts index 0717538cb8..4733b3053a 100644 --- a/frontend/src/container/ServiceApplication/types.ts +++ b/frontend/src/container/ServiceApplication/types.ts @@ -1,6 +1,9 @@ import { ServiceDataProps } from 'api/metrics/getTopLevelOperations'; import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; import { UseQueryResult } from 'react-query'; import { SuccessResponse } from 'types/api'; @@ -25,7 +28,7 @@ export interface GetQueryRangeRequestDataProps { topLevelOperations: [keyof ServiceDataProps, string[]][]; maxTime: number; minTime: number; - globalSelectedInterval: Time | TimeV2; + globalSelectedInterval: Time | TimeV2 | CustomTimeType; } export interface GetServiceListFromQueryProps { diff --git a/frontend/src/container/TopNav/AutoRefresh/config.ts b/frontend/src/container/TopNav/AutoRefresh/config.ts index 64aaca6c64..99c9db8ca5 100644 --- a/frontend/src/container/TopNav/AutoRefresh/config.ts +++ b/frontend/src/container/TopNav/AutoRefresh/config.ts @@ -1,7 +1,7 @@ import GetMinMax, { GetMinMaxPayload } from 'lib/getMinMax'; import { Time } from '../DateTimeSelection/config'; -import { Time as TimeV2 } from '../DateTimeSelectionV2/config'; +import { CustomTimeType, Time as TimeV2 } from '../DateTimeSelectionV2/config'; export const options: IOptions[] = [ { @@ -68,7 +68,7 @@ export interface IOptions { } export const getMinMax = ( - selectedTime: Time | TimeV2, + selectedTime: Time | TimeV2 | CustomTimeType, minTime: number, maxTime: number, ): GetMinMaxPayload => diff --git a/frontend/src/container/TopNav/AutoRefreshV2/config.ts b/frontend/src/container/TopNav/AutoRefreshV2/config.ts index a84f932fbc..8baae51d01 100644 --- a/frontend/src/container/TopNav/AutoRefreshV2/config.ts +++ b/frontend/src/container/TopNav/AutoRefreshV2/config.ts @@ -1,7 +1,7 @@ import GetMinMax, { GetMinMaxPayload } from 'lib/getMinMax'; import { Time } from '../DateTimeSelection/config'; -import { Time as TimeV2 } from '../DateTimeSelectionV2/config'; +import { CustomTimeType, Time as TimeV2 } from '../DateTimeSelectionV2/config'; export const options: IOptions[] = [ { @@ -68,7 +68,7 @@ export interface IOptions { } export const getMinMax = ( - selectedTime: Time | TimeV2, + selectedTime: Time | TimeV2 | CustomTimeType, minTime: number, maxTime: number, ): GetMinMaxPayload => diff --git a/frontend/src/container/TopNav/DateTimeSelection/config.ts b/frontend/src/container/TopNav/DateTimeSelection/config.ts index 3618686c95..102fe00c43 100644 --- a/frontend/src/container/TopNav/DateTimeSelection/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelection/config.ts @@ -1,16 +1,18 @@ import ROUTES from 'constants/routes'; -type FiveMin = '5min'; -type TenMin = '10min'; -type FifteenMin = '15min'; -type ThirtyMin = '30min'; -type OneMin = '1min'; -type SixHour = '6hr'; -type OneHour = '1hr'; -type FourHour = '4hr'; -type OneDay = '1day'; -type ThreeDay = '3days'; -type OneWeek = '1week'; +type FiveMin = '5m'; +type TenMin = '10m'; +type FifteenMin = '15m'; +type ThirtyMin = '30m'; +type OneMin = '1m'; +type SixHour = '6h'; +type OneHour = '1h'; +type FourHour = '4h'; +type ThreeHour = '3h'; +type TwelveHour = '12h'; +type OneDay = '1d'; +type ThreeDay = '3d'; +type OneWeek = '1w'; type Custom = 'custom'; export type Time = @@ -22,37 +24,62 @@ export type Time = | FourHour | SixHour | OneHour + | ThreeHour | Custom | OneWeek | OneDay + | TwelveHour | ThreeDay; export const Options: Option[] = [ - { value: '5min', label: 'Last 5 min' }, - { value: '15min', label: 'Last 15 min' }, - { value: '30min', label: 'Last 30 min' }, - { value: '1hr', label: 'Last 1 hour' }, - { value: '6hr', label: 'Last 6 hour' }, - { value: '1day', label: 'Last 1 day' }, - { value: '3days', label: 'Last 3 days' }, - { value: '1week', label: 'Last 1 week' }, + { value: '5m', label: 'Last 5 min' }, + { value: '15m', label: 'Last 15 min' }, + { value: '30m', label: 'Last 30 min' }, + { value: '1h', label: 'Last 1 hour' }, + { value: '6h', label: 'Last 6 hour' }, + { value: '1d', label: 'Last 1 day' }, + { value: '3d', label: 'Last 3 days' }, + { value: '1w', label: 'Last 1 week' }, { value: 'custom', label: 'Custom' }, ]; +type TimeFrame = { + '5min': string; + '15min': string; + '30min': string; + '1hr': string; + '6hr': string; + '1day': string; + '3days': string; + '1week': string; + [key: string]: string; // Index signature to allow any string as index +}; + +export const RelativeTimeMap: TimeFrame = { + '5min': '5m', + '15min': '15m', + '30min': '30m', + '1hr': '1h', + '6hr': '6h', + '1day': '1d', + '3days': '3d', + '1week': '1w', +}; + export interface Option { value: Time; label: string; } export const RelativeDurationOptions: Option[] = [ - { value: '5min', label: 'Last 5 min' }, - { value: '15min', label: 'Last 15 min' }, - { value: '30min', label: 'Last 30 min' }, - { value: '1hr', label: 'Last 1 hour' }, - { value: '6hr', label: 'Last 6 hour' }, - { value: '1day', label: 'Last 1 day' }, - { value: '3days', label: 'Last 3 days' }, - { value: '1week', label: 'Last 1 week' }, + { value: '5m', label: 'Last 5 min' }, + { value: '15m', label: 'Last 15 min' }, + { value: '30m', label: 'Last 30 min' }, + { value: '1h', label: 'Last 1 hour' }, + { value: '6h', label: 'Last 6 hour' }, + { value: '1d', label: 'Last 1 day' }, + { value: '3d', label: 'Last 3 days' }, + { value: '1w', label: 'Last 1 week' }, ]; export const getDefaultOption = (route: string): Time => { diff --git a/frontend/src/container/TopNav/DateTimeSelection/index.tsx b/frontend/src/container/TopNav/DateTimeSelection/index.tsx index 614e977a12..3d023959f2 100644 --- a/frontend/src/container/TopNav/DateTimeSelection/index.tsx +++ b/frontend/src/container/TopNav/DateTimeSelection/index.tsx @@ -28,7 +28,7 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import AutoRefresh from '../AutoRefresh'; import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal'; -import { Time as TimeV2 } from '../DateTimeSelectionV2/config'; +import { CustomTimeType, Time as TimeV2 } from '../DateTimeSelectionV2/config'; import { getDefaultOption, getOptions, @@ -122,7 +122,7 @@ function DateTimeSelection({ const getInputLabel = ( startTime?: Dayjs, endTime?: Dayjs, - timeInterval: Time | TimeV2 = '15min', + timeInterval: Time | TimeV2 | CustomTimeType = '15m', ): string | Time => { if (startTime && endTime && timeInterval === 'custom') { const format = 'YYYY/MM/DD HH:mm'; @@ -225,7 +225,7 @@ function DateTimeSelection({ [location.pathname], ); - const onSelectHandler = (value: Time | TimeV2): void => { + const onSelectHandler = (value: Time | TimeV2 | CustomTimeType): void => { if (value !== 'custom') { updateTimeInterval(value); updateLocalStorageForRoutes(value); @@ -358,7 +358,7 @@ function DateTimeSelection({ }} selectedTime={selectedTime} onValidCustomDateChange={(dateTime): void => - onCustomDateHandler(dateTime as DateTimeRangeType) + onCustomDateHandler(dateTime.time as DateTimeRangeType) } selectedValue={getInputLabel( dayjs(minTime / 1000000), @@ -406,7 +406,7 @@ function DateTimeSelection({ interface DispatchProps { updateTimeInterval: ( - interval: Time | TimeV2, + interval: Time | TimeV2 | CustomTimeType, dateTimeRange?: [number, number], ) => (dispatch: Dispatch) => void; globalTimeLoading: () => void; diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss b/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss index bd4cc3cdb1..22efca5009 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss @@ -54,9 +54,61 @@ } } } + + .share-link-btn { + height: 34px; + } + + .shareable-link-popover { + margin-left: 8px; + } } -.date-time-root { +.share-modal-content { + display: flex; + flex-direction: column; + gap: 8px; + padding: 16px; + width: 420px; + + .absolute-relative-time-toggler-container { + display: flex; + gap: 8px; + align-items: center; + } + + .absolute-relative-time-toggler { + display: flex; + gap: 4px; + align-items: center; + } + + .absolute-relative-time-error { + font-size: 12px; + color: var(--bg-amber-600); + } + + .share-link { + display: flex; + align-items: center; + + .share-url { + flex: 1; + border: 1px solid var(--bg-slate-400); + border-radius: 2px; + background: var(--bg-ink-300); + height: 32px; + padding: 6px 8px; + } + + .copy-url-btn { + width: 32px; + } + } +} + +.date-time-root, +.shareable-link-popover-root { .ant-popover-inner { border-radius: 4px !important; border: 1px solid var(--bg-slate-400); @@ -185,7 +237,8 @@ } } - .date-time-root { + .date-time-root, + .shareable-link-popover-root { .ant-popover-inner { border: 1px solid var(--bg-vanilla-400); background: var(--bg-vanilla-100) !important; @@ -234,4 +287,13 @@ } } } + + .share-modal-content { + .share-link { + .share-url { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + } + } + } } diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts index 6231505580..f92beb6b8d 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts @@ -1,24 +1,24 @@ /* eslint-disable sonarjs/no-duplicate-string */ import ROUTES from 'constants/routes'; -type FiveMin = '5min'; -type TenMin = '10min'; -type FifteenMin = '15min'; -type ThirtyMin = '30min'; -type FortyFiveMin = '45min'; -type OneMin = '1min'; -type ThreeHour = '3hr'; -type SixHour = '6hr'; -type OneHour = '1hr'; -type FourHour = '4hr'; -type TwelveHour = '12hr'; -type OneDay = '1day'; -type ThreeDay = '3days'; -type FourDay = '4days'; -type TenDay = '10days'; -type OneWeek = '1week'; -type TwoWeek = '2weeks'; -type SixWeek = '6weeks'; +type FiveMin = '5m'; +type TenMin = '10m'; +type FifteenMin = '15m'; +type ThirtyMin = '30m'; +type FortyFiveMin = '45m'; +type OneMin = '1m'; +type ThreeHour = '3h'; +type SixHour = '6h'; +type OneHour = '1h'; +type FourHour = '4h'; +type TwelveHour = '12h'; +type OneDay = '1d'; +type ThreeDay = '3d'; +type FourDay = '4d'; +type TenDay = '10d'; +type OneWeek = '1w'; +type TwoWeek = '2w'; +type SixWeek = '6w'; type TwoMonths = '2months'; type Custom = 'custom'; @@ -44,15 +44,19 @@ export type Time = | TwoWeek | TwoMonths; +export type TimeUnit = 'm' | 'h' | 'd' | 'w'; + +export type CustomTimeType = `${string}${TimeUnit}`; + export const Options: Option[] = [ - { value: '5min', label: 'Last 5 minutes' }, - { value: '15min', label: 'Last 15 minutes' }, - { value: '30min', label: 'Last 30 minutes' }, - { value: '1hr', label: 'Last 1 hour' }, - { value: '6hr', label: 'Last 6 hours' }, - { value: '1day', label: 'Last 1 day' }, - { value: '3days', label: 'Last 3 days' }, - { value: '1week', label: 'Last 1 week' }, + { value: '5m', label: 'Last 5 minutes' }, + { value: '15m', label: 'Last 15 minutes' }, + { value: '30m', label: 'Last 30 minutes' }, + { value: '1h', label: 'Last 1 hour' }, + { value: '6h', label: 'Last 6 hours' }, + { value: '1d', label: 'Last 1 day' }, + { value: '3d', label: 'Last 3 days' }, + { value: '1w', label: 'Last 1 week' }, { value: 'custom', label: 'Custom' }, ]; @@ -61,36 +65,92 @@ export interface Option { label: string; } +export const OLD_RELATIVE_TIME_VALUES = [ + '1min', + '10min', + '15min', + '1hr', + '30min', + '45min', + '5min', + '1day', + '3days', + '4days', + '10days', + '1week', + '2weeks', + '6weeks', + '3hr', + '4hr', + '6hr', + '12hr', +]; + export const RelativeDurationOptions: Option[] = [ - { value: '5min', label: 'Last 5 minutes' }, - { value: '15min', label: 'Last 15 minutes' }, - { value: '30min', label: 'Last 30 minutes' }, - { value: '1hr', label: 'Last 1 hour' }, - { value: '6hr', label: 'Last 6 hour' }, - { value: '1day', label: 'Last 1 day' }, - { value: '3days', label: 'Last 3 days' }, - { value: '1week', label: 'Last 1 week' }, + { value: '5m', label: 'Last 5 minutes' }, + { value: '15m', label: 'Last 15 minutes' }, + { value: '30m', label: 'Last 30 minutes' }, + { value: '1h', label: 'Last 1 hour' }, + { value: '6h', label: 'Last 6 hour' }, + { value: '1d', label: 'Last 1 day' }, + { value: '3d', label: 'Last 3 days' }, + { value: '1w', label: 'Last 1 week' }, ]; export const RelativeDurationSuggestionOptions: Option[] = [ - { value: '3hr', label: '3h' }, - { value: '4days', label: '4d' }, - { value: '6weeks', label: '6w' }, - { value: '12hr', label: '12 hours' }, - { value: '10days', label: '10d' }, - { value: '2weeks', label: '2 weeks' }, + { value: '3h', label: 'Last 3 hours' }, + { value: '4d', label: 'Last 4 days' }, + { value: '6w', label: 'Last 6 weeks' }, + { value: '12h', label: 'Last 12 hours' }, + { value: '10d', label: 'Last 10 days' }, + { value: '2w', label: 'Last 2 weeks' }, { value: '2months', label: 'Last 2 months' }, - { value: '1day', label: 'today' }, + { value: '1d', label: 'today' }, ]; export const FixedDurationSuggestionOptions: Option[] = [ - { value: '45min', label: '45m' }, - { value: '12hr', label: '12 hours' }, - { value: '10days', label: '10d' }, - { value: '2weeks', label: '2 weeks' }, + { value: '45m', label: 'Last 45 mins' }, + { value: '12h', label: 'Last 12 hours' }, + { value: '10d', label: 'Last 10 days' }, + { value: '2w', label: 'Last 2 weeks' }, { value: '2months', label: 'Last 2 months' }, - { value: '1day', label: 'today' }, + { value: '1d', label: 'today' }, ]; +export const convertOldTimeToNewValidCustomTimeFormat = ( + time: string, +): CustomTimeType => { + const regex = /^(\d+)([a-zA-Z]+)/; + const match = regex.exec(time); + + if (match) { + let unit = 'm'; + + switch (match[2]) { + case 'min': + unit = 'm'; + break; + case 'hr': + unit = 'h'; + break; + case 'day': + case 'days': + unit = 'd'; + break; + case 'week': + case 'weeks': + unit = 'w'; + break; + + default: + break; + } + + return `${match[1]}${unit}` as CustomTimeType; + } + + return '30m'; +}; + export const getDefaultOption = (route: string): Time => { if (route === ROUTES.SERVICE_MAP) { return RelativeDurationOptions[2].value; diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx index 3ef5125ad7..3fa36610a0 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx @@ -1,7 +1,8 @@ import './DateTimeSelectionV2.styles.scss'; import { SyncOutlined } from '@ant-design/icons'; -import { Button } from 'antd'; +import { Color } from '@signozhq/design-tokens'; +import { Button, Popover, Switch, Typography } from 'antd'; import getLocalStorageKey from 'api/browser/localstorage/get'; import setLocalStorageKey from 'api/browser/localstorage/set'; import CustomTimePicker from 'components/CustomTimePicker/CustomTimePicker'; @@ -26,10 +27,12 @@ import GetMinMax from 'lib/getMinMax'; import getTimeString from 'lib/getTimeString'; import history from 'lib/history'; import { isObject } from 'lodash-es'; +import { Check, Copy, Info, Send } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useQueryClient } from 'react-query'; import { connect, useSelector } from 'react-redux'; import { RouteComponentProps, withRouter } from 'react-router-dom'; +import { useCopyToClipboard } from 'react-use'; import { bindActionCreators, Dispatch } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; import { GlobalTimeLoading, UpdateTimeInterval } from 'store/actions'; @@ -42,9 +45,12 @@ import { GlobalReducer } from 'types/reducer/globalTime'; import AutoRefresh from '../AutoRefreshV2'; import { DateTimeRangeType } from '../CustomDateTimeModal'; import { + convertOldTimeToNewValidCustomTimeFormat, + CustomTimeType, getDefaultOption, getOptions, LocalStorageTimeRange, + OLD_RELATIVE_TIME_VALUES, Time, TimeRange, } from './config'; @@ -66,6 +72,10 @@ function DateTimeSelection({ const searchStartTime = urlQuery.get('startTime'); const searchEndTime = urlQuery.get('endTime'); const queryClient = useQueryClient(); + const [enableAbsoluteTime, setEnableAbsoluteTime] = useState(false); + const [isValidteRelativeTime, setIsValidteRelativeTime] = useState(false); + const [, handleCopyToClipboard] = useCopyToClipboard(); + const [isURLCopied, setIsURLCopied] = useState(false); const { localstorageStartTime, @@ -178,7 +188,7 @@ function DateTimeSelection({ const getInputLabel = ( startTime?: Dayjs, endTime?: Dayjs, - timeInterval: Time = '15min', + timeInterval: Time | CustomTimeType = '15m', ): string | Time => { if (startTime && endTime && timeInterval === 'custom') { const format = 'DD/MM/YYYY HH:mm'; @@ -284,28 +294,38 @@ function DateTimeSelection({ [location.pathname], ); - const onSelectHandler = (value: Time): void => { + const onSelectHandler = (value: Time | CustomTimeType): void => { if (value !== 'custom') { setIsOpen(false); updateTimeInterval(value); updateLocalStorageForRoutes(value); + setIsValidteRelativeTime(true); if (refreshButtonHidden) { setRefreshButtonHidden(false); } } else { setRefreshButtonHidden(true); setCustomDTPickerVisible(true); + setIsValidteRelativeTime(false); + setEnableAbsoluteTime(false); + + return; } const { maxTime, minTime } = GetMinMax(value, getTime()); if (!isLogsExplorerPage) { - urlQuery.set(QueryParams.startTime, minTime.toString()); - urlQuery.set(QueryParams.endTime, maxTime.toString()); + urlQuery.delete('startTime'); + urlQuery.delete('endTime'); + + urlQuery.set(QueryParams.relativeTime, value); + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; history.replace(generatedUrl); } + // For logs explorer - time range handling is managed in useCopyLogLink.ts:52 + if (!stagedQuery) { return; } @@ -319,18 +339,22 @@ function DateTimeSelection({ }; const onCustomDateHandler = (dateTimeRange: DateTimeRangeType): void => { + // console.log('dateTimeRange', dateTimeRange); if (dateTimeRange !== null) { const [startTimeMoment, endTimeMoment] = dateTimeRange; if (startTimeMoment && endTimeMoment) { const startTime = startTimeMoment; const endTime = endTimeMoment; setCustomDTPickerVisible(false); + updateTimeInterval('custom', [ startTime.toDate().getTime(), endTime.toDate().getTime(), ]); + setLocalStorageKey('startTime', startTime.toString()); setLocalStorageKey('endTime', endTime.toString()); + updateLocalStorageForRoutes(JSON.stringify({ startTime, endTime })); if (!isLogsExplorerPage) { @@ -339,6 +363,7 @@ function DateTimeSelection({ startTime?.toDate().getTime().toString(), ); urlQuery.set(QueryParams.endTime, endTime?.toDate().getTime().toString()); + urlQuery.delete(QueryParams.relativeTime); const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; history.replace(generatedUrl); } @@ -346,6 +371,57 @@ function DateTimeSelection({ } }; + const onValidCustomDateHandler = (dateTimeStr: CustomTimeType): void => { + setIsOpen(false); + updateTimeInterval(dateTimeStr); + updateLocalStorageForRoutes(dateTimeStr); + + urlQuery.delete('startTime'); + urlQuery.delete('endTime'); + + setIsValidteRelativeTime(true); + + const { maxTime, minTime } = GetMinMax(dateTimeStr, getTime()); + + if (!isLogsExplorerPage) { + urlQuery.delete('startTime'); + urlQuery.delete('endTime'); + + urlQuery.set(QueryParams.relativeTime, dateTimeStr); + + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; + history.replace(generatedUrl); + } + + if (!stagedQuery) { + return; + } + + // the second boolean param directs the qb about the time change so to merge the query and retain the current state + initQueryBuilderData(updateStepInterval(stagedQuery, maxTime, minTime), true); + }; + + const getCustomOrIntervalTime = ( + time: Time, + currentRoute: string, + ): Time | CustomTimeType => { + if (searchEndTime !== null && searchStartTime !== null) { + return 'custom'; + } + if ( + (localstorageEndTime === null || localstorageStartTime === null) && + time === 'custom' + ) { + return getDefaultOption(currentRoute); + } + + if (OLD_RELATIVE_TIME_VALUES.indexOf(time) > -1) { + return convertOldTimeToNewValidCustomTimeFormat(time); + } + + return time; + }; + // this is triggred when we change the routes and based on that we are changing the default options useEffect(() => { const metricsTimeDuration = getLocalStorageKey( @@ -365,21 +441,9 @@ function DateTimeSelection({ const currentOptions = getOptions(currentRoute); setOptions(currentOptions); - const getCustomOrIntervalTime = (time: Time): Time => { - if (searchEndTime !== null && searchStartTime !== null) { - return 'custom'; - } - if ( - (localstorageEndTime === null || localstorageStartTime === null) && - time === 'custom' - ) { - return getDefaultOption(currentRoute); - } + const updatedTime = getCustomOrIntervalTime(time, currentRoute); - return time; - }; - - const updatedTime = getCustomOrIntervalTime(time); + setIsValidteRelativeTime(updatedTime !== 'custom'); const [preStartTime = 0, preEndTime = 0] = getTime() || []; @@ -388,18 +452,113 @@ function DateTimeSelection({ updateTimeInterval(updatedTime, [preStartTime, preEndTime]); if (updatedTime !== 'custom') { - const { minTime, maxTime } = GetMinMax(updatedTime); - urlQuery.set(QueryParams.startTime, minTime.toString()); - urlQuery.set(QueryParams.endTime, maxTime.toString()); + urlQuery.delete('startTime'); + urlQuery.delete('endTime'); + + urlQuery.set(QueryParams.relativeTime, updatedTime); } else { - urlQuery.set(QueryParams.startTime, preStartTime.toString()); - urlQuery.set(QueryParams.endTime, preEndTime.toString()); + const startTime = preStartTime.toString(); + const endTime = preEndTime.toString(); + + urlQuery.set(QueryParams.startTime, startTime); + urlQuery.set(QueryParams.endTime, endTime); } + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; + history.replace(generatedUrl); // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.pathname, updateTimeInterval, globalTimeLoading]); + // eslint-disable-next-line sonarjs/cognitive-complexity + const shareModalContent = (): JSX.Element => { + let currentUrl = window.location.href; + + const startTime = urlQuery.get(QueryParams.startTime); + const endTime = urlQuery.get(QueryParams.endTime); + const isCustomTime = !!(startTime && endTime && selectedTime === 'custom'); + + if (enableAbsoluteTime || isCustomTime) { + if (selectedTime === 'custom') { + if (searchStartTime && searchEndTime) { + urlQuery.set(QueryParams.startTime, searchStartTime.toString()); + urlQuery.set(QueryParams.endTime, searchEndTime.toString()); + } + } else { + const { minTime, maxTime } = GetMinMax(selectedTime); + + urlQuery.set(QueryParams.startTime, minTime.toString()); + urlQuery.set(QueryParams.endTime, maxTime.toString()); + } + + urlQuery.delete(QueryParams.relativeTime); + + currentUrl = `${window.location.origin}${ + location.pathname + }?${urlQuery.toString()}`; + } else { + urlQuery.delete(QueryParams.startTime); + urlQuery.delete(QueryParams.endTime); + + urlQuery.set(QueryParams.relativeTime, selectedTime); + currentUrl = `${window.location.origin}${ + location.pathname + }?${urlQuery.toString()}`; + } + + return ( +
+
+
+ {(selectedTime === 'custom' || !isValidteRelativeTime) && ( + + )} + { + setEnableAbsoluteTime(!enableAbsoluteTime); + }} + /> +
+ + Enable Absolute Time +
+ + {(selectedTime === 'custom' || !isValidteRelativeTime) && ( +
+ Please select / enter valid relative time to toggle. +
+ )} + +
+ + {currentUrl} + + +
+
+ ); + }; + return (
{!hasSelectedTimeError && !refreshButtonHidden && ( @@ -426,9 +585,12 @@ function DateTimeSelection({ setHasSelectedTimeError(hasError); }} selectedTime={selectedTime} - onValidCustomDateChange={(dateTime): void => - onCustomDateHandler(dateTime as DateTimeRangeType) - } + onValidCustomDateChange={(dateTime): void => { + onValidCustomDateHandler(dateTime.timeStr as CustomTimeType); + }} + onCustomTimeStatusUpdate={(isValid: boolean): void => { + setIsValidteRelativeTime(isValid); + }} selectedValue={getInputLabel( dayjs(minTime / 1000000), dayjs(maxTime / 1000000), @@ -457,6 +619,22 @@ function DateTimeSelection({
)} + + + + @@ -468,7 +646,7 @@ interface DateTimeSelectionV2Props { } interface DispatchProps { updateTimeInterval: ( - interval: Time, + interval: Time | CustomTimeType, dateTimeRange?: [number, number], ) => (dispatch: Dispatch) => void; globalTimeLoading: () => void; diff --git a/frontend/src/hooks/logs/useCopyLogLink.ts b/frontend/src/hooks/logs/useCopyLogLink.ts index b663aa750c..8dee58c710 100644 --- a/frontend/src/hooks/logs/useCopyLogLink.ts +++ b/frontend/src/hooks/logs/useCopyLogLink.ts @@ -11,8 +11,11 @@ import { useMemo, useState, } from 'react'; +import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { useCopyToClipboard } from 'react-use'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; import { HIGHLIGHTED_DELAY } from './configs'; import { LogTimeRange, UseCopyLogLink } from './types'; @@ -33,15 +36,30 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => { null, ); + const { selectedTime } = useSelector( + (state) => state.globalTime, + ); + const onTimeRangeChange = useCallback( (newTimeRange: LogTimeRange | null): void => { urlQuery.set(QueryParams.timeRange, JSON.stringify(newTimeRange)); - urlQuery.set(QueryParams.startTime, newTimeRange?.start.toString() || ''); - urlQuery.set(QueryParams.endTime, newTimeRange?.end.toString() || ''); + + if (selectedTime !== 'custom') { + urlQuery.delete(QueryParams.startTime); + urlQuery.delete(QueryParams.endTime); + + urlQuery.set(QueryParams.relativeTime, selectedTime); + } else { + urlQuery.set(QueryParams.startTime, newTimeRange?.start.toString() || ''); + urlQuery.set(QueryParams.endTime, newTimeRange?.end.toString() || ''); + + urlQuery.delete(QueryParams.relativeTime); + } + const generatedUrl = `${pathname}?${urlQuery.toString()}`; history.replace(generatedUrl); }, - [pathname, urlQuery], + [pathname, urlQuery, selectedTime], ); const isActiveLog = useMemo(() => activeLogId === logId, [activeLogId, logId]); diff --git a/frontend/src/hooks/useQueryService.ts b/frontend/src/hooks/useQueryService.ts index c13654c56b..a5c54f2466 100644 --- a/frontend/src/hooks/useQueryService.ts +++ b/frontend/src/hooks/useQueryService.ts @@ -1,7 +1,10 @@ import getService from 'api/metrics/getService'; import { AxiosError } from 'axios'; import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import { QueryKey, useQuery, @@ -27,7 +30,7 @@ export const useQueryService = ({ interface UseQueryServiceProps { minTime: number; maxTime: number; - selectedTime: Time | TimeV2; + selectedTime: Time | TimeV2 | CustomTimeType; selectedTags: Tags[]; options?: UseQueryOptions; } diff --git a/frontend/src/lib/dashboard/getQueryResults.ts b/frontend/src/lib/dashboard/getQueryResults.ts index 64b749e45c..177ae9311b 100644 --- a/frontend/src/lib/dashboard/getQueryResults.ts +++ b/frontend/src/lib/dashboard/getQueryResults.ts @@ -6,7 +6,10 @@ import { getMetricsQueryRange } from 'api/metrics/getQueryRange'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import { Pagination } from 'hooks/queryPagination'; import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld'; import { isEmpty } from 'lodash-es'; @@ -67,7 +70,7 @@ export interface GetQueryResultsProps { query: Query; graphType: PANEL_TYPES; selectedTime: timePreferenceType; - globalSelectedInterval: Time | TimeV2; + globalSelectedInterval: Time | TimeV2 | CustomTimeType; variables?: Record; params?: Record; tableParams?: { diff --git a/frontend/src/lib/getMinMax.ts b/frontend/src/lib/getMinMax.ts index c52436063d..4a5076b066 100644 --- a/frontend/src/lib/getMinMax.ts +++ b/frontend/src/lib/getMinMax.ts @@ -1,63 +1,101 @@ import { Time } from 'container/TopNav/DateTimeSelection/config'; import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { isString } from 'lodash-es'; import { GlobalReducer } from 'types/reducer/globalTime'; import getMinAgo from './getStartAndEndTime/getMinAgo'; +const validCustomTimeRegex = /^(\d+)([mhdw])$/; + +export const isValidTimeFormat = (time: string): boolean => + validCustomTimeRegex.test(time); + +const extractTimeAndUnit = (time: string): { time: number; unit: string } => { + // Match the pattern + const match = /^(\d+)([mhdw])$/.exec(time); + + if (match) { + return { time: parseInt(match[1], 10), unit: match[2] }; + } + + return { + time: 30, + unit: 'm', + }; +}; + +export const getMinTimeForRelativeTimes = ( + time: number, + unit: string, +): number => { + switch (unit) { + case 'm': + return getMinAgo({ minutes: 1 * time }).getTime(); + case 'h': + return getMinAgo({ minutes: 60 * time }).getTime(); + case 'd': + return getMinAgo({ minutes: 24 * 60 * time }).getTime(); + case 'w': + return getMinAgo({ minutes: 24 * 60 * 7 * time }).getTime(); + default: + return getMinAgo({ minutes: 1 }).getTime(); + } +}; + const GetMinMax = ( - interval: Time | TimeV2, + interval: Time | TimeV2 | string, dateTimeRange?: [number, number], // eslint-disable-next-line sonarjs/cognitive-complexity ): GetMinMaxPayload => { let maxTime = new Date().getTime(); let minTime = 0; - if (interval === '1min') { + if (interval === '1m') { const minTimeAgo = getMinAgo({ minutes: 1 }).getTime(); minTime = minTimeAgo; - } else if (interval === '10min') { + } else if (interval === '10m') { const minTimeAgo = getMinAgo({ minutes: 10 }).getTime(); minTime = minTimeAgo; - } else if (interval === '15min') { + } else if (interval === '15m') { const minTimeAgo = getMinAgo({ minutes: 15 }).getTime(); minTime = minTimeAgo; - } else if (interval === '1hr') { + } else if (interval === '1h') { const minTimeAgo = getMinAgo({ minutes: 60 }).getTime(); minTime = minTimeAgo; - } else if (interval === '30min') { + } else if (interval === '30m') { const minTimeAgo = getMinAgo({ minutes: 30 }).getTime(); minTime = minTimeAgo; - } else if (interval === '45min') { + } else if (interval === '45m') { const minTimeAgo = getMinAgo({ minutes: 45 }).getTime(); minTime = minTimeAgo; - } else if (interval === '5min') { + } else if (interval === '5m') { const minTimeAgo = getMinAgo({ minutes: 5 }).getTime(); minTime = minTimeAgo; - } else if (interval === '1day') { + } else if (interval === '1d') { // one day = 24*60(min) const minTimeAgo = getMinAgo({ minutes: 24 * 60 }).getTime(); minTime = minTimeAgo; - } else if (interval === '3days') { + } else if (interval === '3d') { // three day = one day * 3 const minTimeAgo = getMinAgo({ minutes: 24 * 60 * 3 }).getTime(); minTime = minTimeAgo; - } else if (interval === '4days') { + } else if (interval === '4d') { // four day = one day * 4 const minTimeAgo = getMinAgo({ minutes: 24 * 60 * 4 }).getTime(); minTime = minTimeAgo; - } else if (interval === '10days') { + } else if (interval === '10d') { // ten day = one day * 10 const minTimeAgo = getMinAgo({ minutes: 24 * 60 * 10 }).getTime(); minTime = minTimeAgo; - } else if (interval === '1week') { + } else if (interval === '1w') { // one week = one day * 7 const minTimeAgo = getMinAgo({ minutes: 24 * 60 * 7 }).getTime(); minTime = minTimeAgo; - } else if (interval === '2weeks') { + } else if (interval === '2w') { // two week = one day * 14 const minTimeAgo = getMinAgo({ minutes: 24 * 60 * 14 }).getTime(); minTime = minTimeAgo; - } else if (interval === '6weeks') { + } else if (interval === '6w') { // six week = one day * 42 const minTimeAgo = getMinAgo({ minutes: 24 * 60 * 42 }).getTime(); minTime = minTimeAgo; @@ -65,13 +103,17 @@ const GetMinMax = ( // two months = one day * 60 const minTimeAgo = getMinAgo({ minutes: 24 * 60 * 60 }).getTime(); minTime = minTimeAgo; - } else if (['3hr', '4hr', '6hr', '12hr'].includes(interval)) { - const h = parseInt(interval.replace('hr', ''), 10); + } else if (['3h', '4h', '6h', '12h'].includes(interval)) { + const h = parseInt(interval.replace('h', ''), 10); const minTimeAgo = getMinAgo({ minutes: h * 60 }).getTime(); minTime = minTimeAgo; } else if (interval === 'custom') { maxTime = (dateTimeRange || [])[1] || 0; minTime = (dateTimeRange || [])[0] || 0; + } else if (isString(interval) && isValidTimeFormat(interval)) { + const { time, unit } = extractTimeAndUnit(interval); + + minTime = getMinTimeForRelativeTimes(time, unit); } else { throw new Error('invalid time type'); } diff --git a/frontend/src/lib/getStartEndRangeTime.ts b/frontend/src/lib/getStartEndRangeTime.ts index 7fd087fd54..37e057b789 100644 --- a/frontend/src/lib/getStartEndRangeTime.ts +++ b/frontend/src/lib/getStartEndRangeTime.ts @@ -1,7 +1,10 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import store from 'store'; import getMaxMinTime from './getMaxMinTime'; @@ -38,7 +41,7 @@ const getStartEndRangeTime = ({ interface GetStartEndRangeTimesProps { type?: timePreferenceType; graphType?: PANEL_TYPES | null; - interval?: Time | TimeV2; + interval?: Time | TimeV2 | CustomTimeType; } interface GetStartEndRangeTimesPayload { diff --git a/frontend/src/store/actions/global.ts b/frontend/src/store/actions/global.ts index 19c3be2b7b..149572d726 100644 --- a/frontend/src/store/actions/global.ts +++ b/frontend/src/store/actions/global.ts @@ -1,12 +1,15 @@ import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import GetMinMax from 'lib/getMinMax'; import { Dispatch } from 'redux'; import AppActions from 'types/actions'; import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime'; export const UpdateTimeInterval = ( - interval: Time | TimeV2, + interval: Time | TimeV2 | CustomTimeType, dateTimeRange: [number, number] = [0, 0], ): ((dispatch: Dispatch) => void) => ( dispatch: Dispatch, diff --git a/frontend/src/store/actions/trace/util.ts b/frontend/src/store/actions/trace/util.ts index 54cd819da5..df2955bbb8 100644 --- a/frontend/src/store/actions/trace/util.ts +++ b/frontend/src/store/actions/trace/util.ts @@ -88,4 +88,7 @@ export const getFilter = (data: GetFilterPayload): TraceReducer['filter'] => { }; export const stripTimestampsFromQuery = (query: string): string => - query.replace(/(\?|&)startTime=\d+/, '').replace(/&endTime=\d+/, ''); + query + .replace(/(\?|&)startTime=\d+/, '') + .replace(/&endTime=\d+/, '') + .replace(/[?&]relativeTime=[^&]+/g, ''); diff --git a/frontend/src/types/actions/globalTime.ts b/frontend/src/types/actions/globalTime.ts index 858a7c78a0..02243b7e05 100644 --- a/frontend/src/types/actions/globalTime.ts +++ b/frontend/src/types/actions/globalTime.ts @@ -1,5 +1,8 @@ import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import { ResetIdStartAndEnd, SetSearchQueryString } from './logs'; @@ -14,7 +17,7 @@ export type GlobalTime = { }; interface UpdateTime extends GlobalTime { - selectedTime: Time | TimeV2; + selectedTime: Time | TimeV2 | CustomTimeType; } interface UpdateTimeInterval { diff --git a/frontend/src/types/reducer/globalTime.ts b/frontend/src/types/reducer/globalTime.ts index cd7fac1ea8..bc5e4e2d67 100644 --- a/frontend/src/types/reducer/globalTime.ts +++ b/frontend/src/types/reducer/globalTime.ts @@ -1,12 +1,15 @@ import { Time } from 'container/TopNav/DateTimeSelection/config'; -import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config'; +import { + CustomTimeType, + Time as TimeV2, +} from 'container/TopNav/DateTimeSelectionV2/config'; import { GlobalTime } from 'types/actions/globalTime'; export interface GlobalReducer { maxTime: GlobalTime['maxTime']; minTime: GlobalTime['minTime']; loading: boolean; - selectedTime: Time | TimeV2; + selectedTime: Time | TimeV2 | CustomTimeType; isAutoRefreshDisabled: boolean; selectedAutoRefreshInterval: string; }