diff --git a/package.json b/package.json
index 078f42599..e906c7c51 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,10 @@
"generate:idl": "mkdir -p src/__generated__/idl && npm run generate:idl:proto && npm run generate:idl:proto:types",
"generate:idl:proto": "rm -rf src/__generated__/idl/proto && cp -R node_modules/cadence-idl/proto src/__generated__/idl/proto",
"generate:idl:proto:types": "rm -rf src/__generated__/proto-ts && ./node_modules/.bin/proto-loader-gen-types --includeDirs=src/__generated__/idl/proto/ --enums=String --longs=String --bytes=String --defaults --inputTemplate='%s__Input' --outputTemplate='%s' --oneofs --grpcLib=@grpc/grpc-js --outDir=src/__generated__/proto-ts/ $(npx glob --all --nodir --cwd=src/__generated__/idl/proto **/*.proto) ",
- "test": "jest --config jest.config.ts && npm run test:types",
+ "test": "TZ=UTC jest --config jest.config.ts && npm run test:types",
"test:unit": "npm run test:unit:browser && npm run test:unit:node",
- "test:unit:browser": "jest --config jest/browser/jest.config.ts",
- "test:unit:node": "jest --config jest/node/jest.config.ts",
+ "test:unit:browser": "TZ=UTC jest --config jest/browser/jest.config.ts",
+ "test:unit:node": "TZ=UTC jest --config jest/node/jest.config.ts",
"test:types": "tstyche"
},
"engines": {
diff --git a/src/components/date-filter-v2/__tests__/date-filter-v2.test.tsx b/src/components/date-filter-v2/__tests__/date-filter-v2.test.tsx
new file mode 100644
index 000000000..0a3b54e8d
--- /dev/null
+++ b/src/components/date-filter-v2/__tests__/date-filter-v2.test.tsx
@@ -0,0 +1,265 @@
+import React, { useState } from 'react';
+
+import { type StatefulPopoverProps } from 'baseui/popover';
+import { type TimePickerProps } from 'baseui/timepicker';
+
+import { render, screen, fireEvent, act, waitFor } from '@/test-utils/rtl';
+
+import dayjs from '@/utils/datetime/dayjs';
+
+import DateFilterV2 from '../date-filter-v2';
+import { type DateFilterRange } from '../date-filter-v2.types';
+
+jest.useFakeTimers().setSystemTime(new Date('2023-05-25'));
+
+// Mock StatefulPopover to render content immediately in tests
+jest.mock('baseui/popover', () => {
+ const originalModule = jest.requireActual('baseui/popover');
+ return {
+ ...originalModule,
+ StatefulPopover: ({ content, children }: StatefulPopoverProps) => {
+ const [isShown, setIsShown] = useState(false);
+
+ return (
+
setIsShown(true)}>
+ {children}
+ {isShown ? (
+
+ {typeof content === 'function' &&
+ content({ close: () => setIsShown(false) })}
+
+ ) : null}
+
+ );
+ },
+ };
+});
+
+jest.mock('baseui/timepicker', () => ({
+ TimePicker: jest.fn(
+ ({ value, onChange, disabled }: TimePickerProps) =>
+ onChange && (
+ onChange(new Date(e.target.value))}
+ disabled={disabled}
+ />
+ )
+ ),
+}));
+
+const mockDateOverrides: DateFilterRange = {
+ start: dayjs('2023-05-23T00:00:00.000Z'),
+ end: dayjs('2023-05-24T00:00:00.000Z'),
+};
+
+describe(DateFilterV2.name, () => {
+ it('displays the date filter component with placeholder when no dates are provided', () => {
+ setup({});
+ expect(screen.getByPlaceholderText('Mock placeholder')).toBeInTheDocument();
+ });
+
+ it('renders without errors when dates are already provided', () => {
+ setup({
+ overrides: mockDateOverrides,
+ });
+
+ expect(
+ screen.getByDisplayValue('23 May, 00:00:00 UTC - 24 May, 00:00:00 UTC')
+ ).toBeInTheDocument();
+ });
+
+ it('opens a popover when clicked', () => {
+ setup({});
+ const datePicker = screen.getByPlaceholderText('Mock placeholder');
+
+ act(() => {
+ fireEvent.click(datePicker);
+ });
+
+ // Check for elements in the popover
+ expect(screen.getByText('Quick Range')).toBeInTheDocument();
+ expect(screen.getByText('Custom Range')).toBeInTheDocument();
+ expect(screen.getByText('Last 5 minutes')).toBeInTheDocument();
+ });
+
+ it('selects a relative time range when clicking a quick range button', () => {
+ const { mockOnChangeDates } = setup({});
+ const datePicker = screen.getByPlaceholderText('Mock placeholder');
+
+ act(() => {
+ fireEvent.click(datePicker);
+ });
+
+ const lastFiveMinutesButton = screen.getByText('Last 5 minutes');
+
+ act(() => {
+ fireEvent.click(lastFiveMinutesButton);
+ });
+
+ expect(mockOnChangeDates).toHaveBeenCalledWith({
+ start: 'now-5m',
+ end: 'now',
+ });
+ });
+
+ it('allows selecting a custom date range via the calendar', () => {
+ const { mockOnChangeDates } = setup({});
+ const datePicker = screen.getByPlaceholderText('Mock placeholder');
+
+ act(() => {
+ fireEvent.click(datePicker);
+ });
+
+ const saveButton = screen.getByText('Save');
+ const timePickers = screen.getAllByTestId('time-picker');
+
+ act(() => {
+ fireEvent.click(screen.getByLabelText(/May 13th 2023/));
+ });
+
+ expect(saveButton).toBeDisabled();
+ timePickers.forEach((picker) => {
+ expect(picker).toBeDisabled();
+ });
+
+ act(() => {
+ fireEvent.click(screen.getByLabelText(/May 14th 2023/));
+ });
+
+ expect(saveButton).not.toBeDisabled();
+ timePickers.forEach((picker) => {
+ expect(picker).not.toBeDisabled();
+ });
+
+ act(() => {
+ fireEvent.click(saveButton);
+ });
+
+ expect(mockOnChangeDates).toHaveBeenCalledWith({
+ start: dayjs('2023-05-13'),
+ end: dayjs('2023-05-14'),
+ });
+ });
+
+ it('handles single date selection (same start and end date)', () => {
+ const { mockOnChangeDates } = setup({});
+ const datePicker = screen.getByPlaceholderText('Mock placeholder');
+
+ act(() => {
+ fireEvent.click(datePicker);
+ });
+
+ act(() => {
+ fireEvent.click(screen.getByLabelText(/May 13th 2023/));
+ });
+
+ act(() => {
+ fireEvent.click(screen.getByLabelText(/May 13th 2023/));
+ });
+
+ const saveButton = screen.getByText('Save');
+ act(() => {
+ fireEvent.click(saveButton);
+ });
+
+ expect(mockOnChangeDates).toHaveBeenCalledWith({
+ start: dayjs('2023-05-13'),
+ end: dayjs('2023-05-13').endOf('day'),
+ });
+ });
+
+ it('allows time adjustment after date selection', () => {
+ const { mockOnChangeDates } = setup({});
+ const datePicker = screen.getByPlaceholderText('Mock placeholder');
+
+ act(() => {
+ fireEvent.click(datePicker);
+ });
+
+ act(() => {
+ fireEvent.click(screen.getByLabelText(/May 13th 2023/));
+ });
+
+ act(() => {
+ fireEvent.click(screen.getByLabelText(/May 14th 2023/));
+ });
+
+ const timePickers = screen.getAllByTestId('time-picker');
+ expect(timePickers).toHaveLength(2);
+
+ act(() => {
+ fireEvent.change(timePickers[0], {
+ target: { value: '2023-05-13 11:45' },
+ });
+ fireEvent.change(timePickers[1], {
+ target: { value: '2023-05-14 15:45' },
+ });
+ });
+
+ const saveButton = screen.getByText('Save');
+ act(() => {
+ fireEvent.click(saveButton);
+ });
+
+ expect(mockOnChangeDates).toHaveBeenCalledWith(
+ expect.objectContaining({
+ start: dayjs('2023-05-13 11:45'),
+ end: dayjs('2023-05-14 15:45'),
+ })
+ );
+ });
+
+ it('displays the correct format when using relative date values', () => {
+ setup({
+ overrides: {
+ start: 'now-1h',
+ end: 'now',
+ },
+ });
+
+ expect(screen.getByDisplayValue('Last 1 hour')).toBeInTheDocument();
+ });
+
+ it('closes popover when clicking the close button', () => {
+ setup({});
+ const datePicker = screen.getByPlaceholderText('Mock placeholder');
+
+ act(() => {
+ fireEvent.click(datePicker);
+ });
+
+ const quickRangeHeader = screen.getByText('Quick Range');
+ expect(quickRangeHeader).toBeInTheDocument();
+
+ const closeButton = screen.getByTestId('close-button');
+
+ act(() => {
+ fireEvent.click(closeButton);
+ });
+
+ waitFor(() => {
+ expect(quickRangeHeader).not.toBeInTheDocument();
+ });
+ });
+});
+
+function setup({ overrides }: { overrides?: Partial }) {
+ const mockOnChangeDates = jest.fn();
+
+ const result = render(
+
+ );
+
+ return { mockOnChangeDates, ...result };
+}
diff --git a/src/components/date-filter-v2/date-filter-v2.constants.ts b/src/components/date-filter-v2/date-filter-v2.constants.ts
new file mode 100644
index 000000000..54e8637c4
--- /dev/null
+++ b/src/components/date-filter-v2/date-filter-v2.constants.ts
@@ -0,0 +1,12 @@
+import { type RelativeDurationConfig } from './date-filter-v2.types';
+
+export const DATE_FILTER_RELATIVE_VALUES = {
+ 'now-5m': { label: 'Last 5 minutes', durationSeconds: 5 * 60 },
+ 'now-15m': { label: 'Last 15 minutes', durationSeconds: 15 * 60 },
+ 'now-1h': { label: 'Last 1 hour', durationSeconds: 1 * 60 * 60 },
+ 'now-6h': { label: 'Last 6 hours', durationSeconds: 6 * 60 * 60 },
+ 'now-12h': { label: 'Last 12 hours', durationSeconds: 12 * 60 * 60 },
+ 'now-1d': { label: 'Last 1 day', durationSeconds: 1 * 24 * 60 * 60 },
+ 'now-7d': { label: 'Last 7 days', durationSeconds: 7 * 24 * 60 * 60 },
+ 'now-30d': { label: 'Last 30 days', durationSeconds: 30 * 24 * 60 * 60 },
+} as const satisfies Record<`now-${string}`, RelativeDurationConfig>;
diff --git a/src/components/date-filter-v2/date-filter-v2.styles.ts b/src/components/date-filter-v2/date-filter-v2.styles.ts
new file mode 100644
index 000000000..0f120fbe9
--- /dev/null
+++ b/src/components/date-filter-v2/date-filter-v2.styles.ts
@@ -0,0 +1,115 @@
+import { styled as createStyled, type Theme } from 'baseui';
+import { type ButtonOverrides } from 'baseui/button';
+import { type DatepickerOverrides } from 'baseui/datepicker';
+import type { FormControlOverrides } from 'baseui/form-control/types';
+import { type PopoverOverrides } from 'baseui/popover';
+import { type StyleObject } from 'styletron-react';
+
+export const overrides = {
+ dateFormControl: {
+ Label: {
+ style: ({ $theme }: { $theme: Theme }): StyleObject => ({
+ ...$theme.typography.LabelXSmall,
+ }),
+ },
+ ControlContainer: {
+ style: (): StyleObject => ({
+ margin: '0px',
+ }),
+ },
+ } satisfies FormControlOverrides,
+ timeFormControl: {
+ Label: {
+ style: ({ $theme }: { $theme: Theme }): StyleObject => ({
+ ...$theme.typography.LabelXSmall,
+ }),
+ },
+ ControlContainer: {
+ style: (): StyleObject => ({
+ margin: '0px',
+ display: 'flex',
+ flexDirection: 'column',
+ }),
+ },
+ } satisfies FormControlOverrides,
+ popover: {
+ Inner: {
+ style: ({ $theme }: { $theme: Theme }): StyleObject => ({
+ ...$theme.typography.LabelSmall,
+ borderRadius: $theme.borders.radius400,
+ background: $theme.colors.backgroundPrimary,
+ boxShadow: $theme.lighting.shallowBelow,
+ }),
+ },
+ } satisfies PopoverOverrides,
+ menuItemButton: {
+ BaseButton: {
+ style: ({ $theme }: { $theme: Theme }): StyleObject => ({
+ ...$theme.typography.LabelSmall,
+ justifyContent: 'flex-start',
+ whiteSpace: 'nowrap',
+ }),
+ },
+ } satisfies ButtonOverrides,
+ calendar: {
+ Root: {
+ style: {
+ paddingLeft: 0,
+ paddingRight: 0,
+ paddingTop: 0,
+ paddingBottom: 0,
+ },
+ },
+ } satisfies DatepickerOverrides,
+};
+
+export const styled = {
+ PopoverContentContainer: createStyled('div', {
+ display: 'flex',
+ flexDirection: 'row',
+ position: 'relative',
+ }),
+ CloseButtonContainer: createStyled('div', ({ $theme }) => ({
+ position: 'absolute',
+ top: $theme.sizing.scale400,
+ right: $theme.sizing.scale400,
+ })),
+ ContentHeader: createStyled('div', ({ $theme }) => ({
+ ...$theme.typography.LabelMedium,
+ paddingLeft: $theme.sizing.scale500,
+ })),
+ ContentColumn: createStyled('div', ({ $theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: $theme.sizing.scale600,
+ paddingTop: $theme.sizing.scale700,
+ paddingBottom: $theme.sizing.scale700,
+ paddingLeft: $theme.sizing.scale600,
+ paddingRight: $theme.sizing.scale600,
+ ':not(:last-child)': {
+ borderRight: `1px solid ${$theme.colors.borderOpaque}`,
+ },
+ })),
+ MenuContainer: createStyled('div', {
+ display: 'flex',
+ flexDirection: 'column',
+ flexGrow: 1,
+ justifyContent: 'space-between',
+ }),
+ MenuItemsContainer: createStyled('div', ({ $theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: $theme.sizing.scale100,
+ })),
+ TimeInputsContainer: createStyled('div', ({ $theme }) => ({
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-evenly',
+ gap: $theme.sizing.scale300,
+ })),
+ TimeInputContainer: createStyled('div', {
+ display: 'flex',
+ flexDirection: 'column',
+ flexBasis: '50%',
+ }),
+};
diff --git a/src/components/date-filter-v2/date-filter-v2.tsx b/src/components/date-filter-v2/date-filter-v2.tsx
new file mode 100644
index 000000000..a90a4492f
--- /dev/null
+++ b/src/components/date-filter-v2/date-filter-v2.tsx
@@ -0,0 +1,211 @@
+import { useCallback, useEffect, useMemo, useState } from 'react';
+
+import { Button } from 'baseui/button';
+import { StatefulCalendar, TimePicker } from 'baseui/datepicker';
+import { FormControl } from 'baseui/form-control';
+import { Input } from 'baseui/input';
+import { StatefulPopover } from 'baseui/popover';
+import { MdClose } from 'react-icons/md';
+
+import dayjs from '@/utils/datetime/dayjs';
+
+import { DATE_FILTER_RELATIVE_VALUES } from './date-filter-v2.constants';
+import { overrides, styled } from './date-filter-v2.styles';
+import {
+ type Props,
+ type DateFilterRange,
+ type RelativeDateFilterValue,
+} from './date-filter-v2.types';
+import isRelativeDateFilterValue from './helpers/is-relative-date-filter-value';
+import stringifyDateFilterValue from './helpers/stringify-date-filter-value';
+
+export default function DateFilterV2({
+ label,
+ placeholder,
+ dates,
+ onChangeDates,
+}: Props) {
+ const [tempDates, setTempDates] = useState<{
+ start: dayjs.Dayjs | undefined;
+ end: dayjs.Dayjs | undefined;
+ }>({
+ start: undefined,
+ end: undefined,
+ });
+ const areTempDatesInvalid = useMemo(
+ () => Boolean(tempDates.end?.isBefore(tempDates.start)),
+ [tempDates]
+ );
+
+ useEffect(() => {
+ if (dayjs.isDayjs(dates.start) && dayjs.isDayjs(dates.end))
+ setTempDates({
+ start: dates.start,
+ end: dates.end,
+ });
+ }, [dates]);
+
+ const [canSaveDate, setCanSaveDate] = useState(false);
+
+ const saveDates = useCallback(
+ (dates: DateFilterRange) => {
+ onChangeDates(dates);
+ setCanSaveDate(false);
+ },
+ [onChangeDates]
+ );
+
+ const displayValue = useMemo(() => {
+ if (!dates.end || !dates.start) return 'Unknown';
+ if (dates.end === 'now' && isRelativeDateFilterValue(dates.start))
+ return DATE_FILTER_RELATIVE_VALUES[dates.start].label;
+
+ return `${stringifyDateFilterValue(dates.start, 'pretty')} - ${stringifyDateFilterValue(dates.end, 'pretty')}`;
+ }, [dates]);
+
+ return (
+
+ (
+
+
+ Quick Range
+
+
+ {Object.entries(DATE_FILTER_RELATIVE_VALUES).map(
+ ([key, { label }]) => (
+
+ )
+ )}
+
+
+
+
+
+ Custom Range
+ {
+ if (!newDates || !Array.isArray(newDates)) {
+ return;
+ }
+
+ if (newDates.length === 2 && newDates[0] && newDates[1]) {
+ setTempDates({
+ start: dayjs(newDates[0]),
+ end:
+ newDates[0].getTime() === newDates[1].getTime()
+ ? dayjs(newDates[1]).endOf('day')
+ : dayjs(newDates[1]),
+ });
+ setCanSaveDate(true);
+ }
+ }}
+ range
+ />
+
+
+
+
+ setTempDates((oldDates) => ({
+ ...oldDates,
+ start: dayjs(newStart),
+ }))
+ }
+ />
+
+
+
+
+
+ setTempDates((oldDates) => ({
+ ...oldDates,
+ end: dayjs(newEnd),
+ }))
+ }
+ />
+
+
+
+
+
+
+
+
+ )}
+ >
+
+
+
+
+
+ );
+}
diff --git a/src/components/date-filter-v2/date-filter-v2.types.ts b/src/components/date-filter-v2/date-filter-v2.types.ts
new file mode 100644
index 000000000..0e57b3393
--- /dev/null
+++ b/src/components/date-filter-v2/date-filter-v2.types.ts
@@ -0,0 +1,24 @@
+import { type Dayjs } from 'dayjs';
+
+import { type DATE_FILTER_RELATIVE_VALUES } from './date-filter-v2.constants';
+
+export type RelativeDurationConfig = {
+ label: string;
+ durationSeconds: number;
+};
+
+export type RelativeDateFilterValue = keyof typeof DATE_FILTER_RELATIVE_VALUES;
+
+export type DateFilterValue = Dayjs | 'now' | RelativeDateFilterValue;
+
+export type DateFilterRange = {
+ start: DateFilterValue | undefined;
+ end: DateFilterValue | undefined;
+};
+
+export type Props = {
+ label: string;
+ placeholder: string;
+ dates: DateFilterRange;
+ onChangeDates: (v: DateFilterRange) => void;
+};
diff --git a/src/components/date-filter-v2/helpers/__tests__/get-dayjs-from-date-filter-value.test.ts b/src/components/date-filter-v2/helpers/__tests__/get-dayjs-from-date-filter-value.test.ts
new file mode 100644
index 000000000..d2be06217
--- /dev/null
+++ b/src/components/date-filter-v2/helpers/__tests__/get-dayjs-from-date-filter-value.test.ts
@@ -0,0 +1,50 @@
+import dayjs from '@/utils/datetime/dayjs';
+
+import { DATE_FILTER_RELATIVE_VALUES } from '../../date-filter-v2.constants';
+import { type RelativeDateFilterValue } from '../../date-filter-v2.types';
+import getDayjsFromDateFilterValue from '../get-dayjs-from-date-filter-value';
+
+describe('getDayjsFromDateFilterValue', () => {
+ const now = dayjs('2023-05-25T12:00:00.000Z');
+
+ it('returns the same dayjs object when input is a dayjs object', () => {
+ const date = dayjs('2023-05-23T10:30:00.000Z');
+ const result = getDayjsFromDateFilterValue(date, now);
+ expect(result).toBe(date);
+ });
+
+ it('returns the "now" value when input is "now"', () => {
+ const result = getDayjsFromDateFilterValue('now', now);
+ expect(result).toBe(now);
+ });
+
+ it('calculates correct relative times for relative date values', () => {
+ // Test each relative date value
+ Object.entries(DATE_FILTER_RELATIVE_VALUES).forEach(
+ ([key, { durationSeconds }]) => {
+ const result = getDayjsFromDateFilterValue(
+ key as RelativeDateFilterValue,
+ now
+ );
+
+ const expected = now.subtract(durationSeconds, 'seconds');
+
+ expect(result.unix()).toBe(expected.unix());
+ }
+ );
+ });
+
+ it('correctly calculates specific relative date examples', () => {
+ // 5 minutes ago
+ const fiveMinAgo = getDayjsFromDateFilterValue('now-5m', now);
+ expect(fiveMinAgo.unix()).toBe(now.subtract(5, 'minutes').unix());
+
+ // 1 hour ago
+ const oneHourAgo = getDayjsFromDateFilterValue('now-1h', now);
+ expect(oneHourAgo.unix()).toBe(now.subtract(1, 'hour').unix());
+
+ // 1 day ago
+ const oneDayAgo = getDayjsFromDateFilterValue('now-1d', now);
+ expect(oneDayAgo.unix()).toBe(now.subtract(1, 'day').unix());
+ });
+});
diff --git a/src/components/date-filter-v2/helpers/__tests__/is-relative-date-filter-value.test.ts b/src/components/date-filter-v2/helpers/__tests__/is-relative-date-filter-value.test.ts
new file mode 100644
index 000000000..95773ee05
--- /dev/null
+++ b/src/components/date-filter-v2/helpers/__tests__/is-relative-date-filter-value.test.ts
@@ -0,0 +1,26 @@
+import { DATE_FILTER_RELATIVE_VALUES } from '../../date-filter-v2.constants';
+import isRelativeDateFilterValue from '../is-relative-date-filter-value';
+
+describe('isRelativeDateFilterValue', () => {
+ it('returns true for valid relative date filter values', () => {
+ // Test all keys from the constants
+ Object.keys(DATE_FILTER_RELATIVE_VALUES).forEach((key) => {
+ expect(isRelativeDateFilterValue(key)).toBe(true);
+ });
+ });
+
+ it('returns false for invalid relative date filter values', () => {
+ expect(isRelativeDateFilterValue('invalid-value')).toBe(false);
+ expect(isRelativeDateFilterValue('now-invalid')).toBe(false);
+ expect(isRelativeDateFilterValue('now')).toBe(false);
+ expect(isRelativeDateFilterValue('')).toBe(false);
+ });
+
+ it('returns false for non-string values', () => {
+ expect(isRelativeDateFilterValue(123)).toBe(false);
+ expect(isRelativeDateFilterValue(null)).toBe(false);
+ expect(isRelativeDateFilterValue(undefined)).toBe(false);
+ expect(isRelativeDateFilterValue({})).toBe(false);
+ expect(isRelativeDateFilterValue([])).toBe(false);
+ });
+});
diff --git a/src/components/date-filter-v2/helpers/__tests__/parse-date-filter-value.test.ts b/src/components/date-filter-v2/helpers/__tests__/parse-date-filter-value.test.ts
new file mode 100644
index 000000000..bfe8d776e
--- /dev/null
+++ b/src/components/date-filter-v2/helpers/__tests__/parse-date-filter-value.test.ts
@@ -0,0 +1,36 @@
+import dayjs from '@/utils/datetime/dayjs';
+
+import { type DateFilterValue } from '../../date-filter-v2.types';
+import parseDateFilterValue from '../parse-date-filter-value';
+
+// Mock is-relative-date-filter-value
+jest.mock('../is-relative-date-filter-value', () => ({
+ __esModule: true,
+ default: (v: string) => v.startsWith('now-'),
+}));
+
+describe('parseDateFilterValue', () => {
+ it('returns the value as is for relative date values', () => {
+ const fallback: DateFilterValue = 'now';
+ expect(parseDateFilterValue('now-5m', fallback)).toBe('now-5m');
+ expect(parseDateFilterValue('now-1h', fallback)).toBe('now-1h');
+ });
+
+ it('parses string dates into dayjs objects when format is valid', () => {
+ const fallback: DateFilterValue = 'now';
+ const result = parseDateFilterValue('2023-05-23T10:30:00.000Z', fallback);
+ expect(dayjs.isDayjs(result)).toBe(true);
+ expect((result as dayjs.Dayjs).format('YYYY-MM-DD')).toBe('2023-05-23');
+ });
+
+ it('returns the fallback when date format is invalid', () => {
+ const fallback = dayjs('2023-01-01');
+ const result = parseDateFilterValue('invalid-date', fallback);
+ expect(result).toBe(fallback);
+ });
+
+ it('returns the fallback for empty strings', () => {
+ const fallback: DateFilterValue = 'now';
+ expect(parseDateFilterValue('', fallback)).toBe(fallback);
+ });
+});
diff --git a/src/components/date-filter-v2/helpers/__tests__/stringify-date-filter-value.test.ts b/src/components/date-filter-v2/helpers/__tests__/stringify-date-filter-value.test.ts
new file mode 100644
index 000000000..f80db36ea
--- /dev/null
+++ b/src/components/date-filter-v2/helpers/__tests__/stringify-date-filter-value.test.ts
@@ -0,0 +1,32 @@
+import dayjs from '@/utils/datetime/dayjs';
+
+import stringifyDateFilterValue from '../stringify-date-filter-value';
+
+// Mock the current date to be fixed
+jest.useFakeTimers().setSystemTime(new Date('2023-05-25'));
+
+describe('stringifyDateFilterValue', () => {
+ it('returns the value as is for string values', () => {
+ expect(stringifyDateFilterValue('now')).toBe('now');
+ expect(stringifyDateFilterValue('now-5m')).toBe('now-5m');
+ });
+
+ it('returns ISO string for dayjs objects when prettyPrint is not specified', () => {
+ const date = dayjs('2023-05-23T10:30:00.000Z');
+ expect(stringifyDateFilterValue(date)).toBe('2023-05-23T10:30:00.000Z');
+ });
+
+ it('formats dates from current year with just month and day when prettyPrint=pretty', () => {
+ const date = dayjs('2023-05-23T10:30:00.000Z');
+ expect(stringifyDateFilterValue(date, 'pretty')).toBe(
+ '23 May, 10:30:00 UTC'
+ );
+ });
+
+ it('includes year for dates not in current year when prettyPrint=pretty', () => {
+ const date = dayjs('2022-05-23T10:30:00.000Z');
+ expect(stringifyDateFilterValue(date, 'pretty')).toBe(
+ '23 May 2022, 10:30:00 UTC'
+ );
+ });
+});
diff --git a/src/components/date-filter-v2/helpers/get-dayjs-from-date-filter-value.ts b/src/components/date-filter-v2/helpers/get-dayjs-from-date-filter-value.ts
new file mode 100644
index 000000000..d03793892
--- /dev/null
+++ b/src/components/date-filter-v2/helpers/get-dayjs-from-date-filter-value.ts
@@ -0,0 +1,22 @@
+import dayjs from '@/utils/datetime/dayjs';
+
+import { DATE_FILTER_RELATIVE_VALUES } from '../date-filter-v2.constants';
+import { type DateFilterValue } from '../date-filter-v2.types';
+
+export default function getDayjsFromDateFilterValue(
+ v: DateFilterValue,
+ now: dayjs.Dayjs
+) {
+ if (dayjs.isDayjs(v)) {
+ return v;
+ }
+
+ if (v === 'now') {
+ return now;
+ }
+
+ return now.subtract(
+ DATE_FILTER_RELATIVE_VALUES[v].durationSeconds,
+ 'seconds'
+ );
+}
diff --git a/src/components/date-filter-v2/helpers/is-relative-date-filter-value.ts b/src/components/date-filter-v2/helpers/is-relative-date-filter-value.ts
new file mode 100644
index 000000000..e70cc1626
--- /dev/null
+++ b/src/components/date-filter-v2/helpers/is-relative-date-filter-value.ts
@@ -0,0 +1,8 @@
+import { DATE_FILTER_RELATIVE_VALUES } from '../date-filter-v2.constants';
+import { type RelativeDateFilterValue } from '../date-filter-v2.types';
+
+export default function isRelativeDateFilterValue(
+ v: any
+): v is RelativeDateFilterValue {
+ return Object.hasOwn(DATE_FILTER_RELATIVE_VALUES, v);
+}
diff --git a/src/components/date-filter-v2/helpers/parse-date-filter-value.ts b/src/components/date-filter-v2/helpers/parse-date-filter-value.ts
new file mode 100644
index 000000000..9b3f6f826
--- /dev/null
+++ b/src/components/date-filter-v2/helpers/parse-date-filter-value.ts
@@ -0,0 +1,14 @@
+import dayjs from '@/utils/datetime/dayjs';
+
+import { type DateFilterValue } from '../date-filter-v2.types';
+
+import isRelativeDateFilterValue from './is-relative-date-filter-value';
+
+export default function parseDateFilterValue(
+ v: string,
+ fallback: DateFilterValue
+): DateFilterValue {
+ if (isRelativeDateFilterValue(v)) return v;
+ const day = dayjs(v);
+ return day.isValid() ? day : fallback;
+}
diff --git a/src/components/date-filter-v2/helpers/stringify-date-filter-value.ts b/src/components/date-filter-v2/helpers/stringify-date-filter-value.ts
new file mode 100644
index 000000000..2e31302c8
--- /dev/null
+++ b/src/components/date-filter-v2/helpers/stringify-date-filter-value.ts
@@ -0,0 +1,22 @@
+import dayjs from '@/utils/datetime/dayjs';
+
+import { type DateFilterValue } from '../date-filter-v2.types';
+
+export default function stringifyDateFilterValue(
+ v: DateFilterValue,
+ prettyPrint?: 'pretty'
+): string {
+ const now = dayjs();
+
+ if (dayjs.isDayjs(v)) {
+ return prettyPrint === 'pretty'
+ ? v.format(
+ v.isSame(now, 'year')
+ ? 'DD MMM, HH:mm:ss z'
+ : 'DD MMM YYYY, HH:mm:ss z'
+ )
+ : v.toISOString();
+ }
+
+ return v;
+}
diff --git a/src/hooks/use-merged-infinite-queries/__tests__/use-merged-infinite-queries.test.ts b/src/hooks/use-merged-infinite-queries/__tests__/use-merged-infinite-queries.test.ts
index 1faa35bed..711db8db1 100644
--- a/src/hooks/use-merged-infinite-queries/__tests__/use-merged-infinite-queries.test.ts
+++ b/src/hooks/use-merged-infinite-queries/__tests__/use-merged-infinite-queries.test.ts
@@ -12,7 +12,7 @@ type MockAPIResponse = {
};
const MOCK_QUERY_CONFIG: Array<
- SingleInfiniteQueryOptions
+ SingleInfiniteQueryOptions
> = [
{
queryKey: ['even-numbers'],
@@ -35,7 +35,7 @@ const MOCK_QUERY_CONFIG: Array<
];
const MOCK_QUERY_CONFIG_WITH_ERROR: Array<
- SingleInfiniteQueryOptions
+ SingleInfiniteQueryOptions
> = [
{
queryKey: ['even-numbers'],
diff --git a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.ts b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.ts
index 5ced67c71..9b22d8fe1 100644
--- a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.ts
+++ b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.ts
@@ -1,6 +1,10 @@
import { useMemo, useState, useEffect, useCallback } from 'react';
-import { InfiniteQueryObserver, useQueryClient } from '@tanstack/react-query';
+import {
+ InfiniteQueryObserver,
+ type QueryKey,
+ useQueryClient,
+} from '@tanstack/react-query';
import mergeSortedArrays from '@/utils/merge-sorted-arrays';
@@ -35,12 +39,17 @@ import {
* - `mergedQueryResults`: The merged and sorted results from all queries.
* - `queryResults`: An array containing individual results from each query.
*/
-export default function useMergedInfiniteQueries({
+export default function useMergedInfiniteQueries<
+ TData,
+ TResponse,
+ TPageParam,
+ TQueryKey extends QueryKey,
+>({
queries,
pageSize,
flattenResponse,
compare,
-}: Props): [
+}: Props): [
MergedQueriesResults,
Array>,
] {
diff --git a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.types.ts b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.types.ts
index bd711b6c7..5c2f36210 100644
--- a/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.types.ts
+++ b/src/hooks/use-merged-infinite-queries/use-merged-infinite-queries.types.ts
@@ -21,22 +21,25 @@ export type MergedQueriesResults = {
refetch: () => void;
};
-export type SingleInfiniteQueryOptions =
- UseInfiniteQueryOptions<
- TResponse,
- Error,
- InfiniteData,
- TResponse,
- QueryKey,
- TPageParam
- >;
+export type SingleInfiniteQueryOptions<
+ TResponse,
+ TPageParam,
+ TQueryKey extends QueryKey,
+> = UseInfiniteQueryOptions<
+ TResponse,
+ Error,
+ InfiniteData,
+ TResponse,
+ TQueryKey,
+ TPageParam
+>;
export type SingleInfiniteQueryResult = ReturnType<
typeof useInfiniteQuery
>;
-export type Props = {
- queries: Array>;
+export type Props = {
+ queries: Array>;
pageSize: number;
flattenResponse: (queryResult: TResponse) => Array;
compare: (a: TData, b: TData) => number;
diff --git a/src/views/domain-page/__fixtures__/domain-page-query-params.ts b/src/views/domain-page/__fixtures__/domain-page-query-params.ts
index bc4fe2e56..0c2bafdc5 100644
--- a/src/views/domain-page/__fixtures__/domain-page-query-params.ts
+++ b/src/views/domain-page/__fixtures__/domain-page-query-params.ts
@@ -13,8 +13,8 @@ export const mockDomainPageQueryParamsValues = {
workflowId: '',
workflowType: '',
statusBasic: undefined,
- timeRangeStartBasic: new Date('2024-11-17T03:24:00'),
- timeRangeEndBasic: new Date('2024-12-17T03:24:00'),
+ timeRangeStartBasic: 'now',
+ timeRangeEndBasic: 'now-7d',
inputTypeArchival: 'search',
searchArchival: '',
statusesArchival: undefined,
diff --git a/src/views/domain-page/config/domain-page-query-params.config.ts b/src/views/domain-page/config/domain-page-query-params.config.ts
index f19419a0c..d74799875 100644
--- a/src/views/domain-page/config/domain-page-query-params.config.ts
+++ b/src/views/domain-page/config/domain-page-query-params.config.ts
@@ -1,28 +1,24 @@
+import { type DateFilterValue } from '@/components/date-filter-v2/date-filter-v2.types';
+import parseDateFilterValue from '@/components/date-filter-v2/helpers/parse-date-filter-value';
import {
type PageQueryParamMultiValue,
type PageQueryParam,
} from '@/hooks/use-page-query-params/use-page-query-params.types';
-import dayjs from '@/utils/datetime/dayjs';
-import parseDateQueryParam from '@/utils/datetime/parse-date-query-param';
import { type SortOrder } from '@/utils/sort-by';
-import DOMAIN_WORKFLOWS_ARCHIVAL_START_DAYS_CONFIG from '@/views/domain-workflows-archival/config/domain-workflows-archival-start-days.config';
import { type WorkflowStatusClosed } from '@/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.types';
-import DOMAIN_WORKFLOWS_BASIC_START_DAYS_CONFIG from '@/views/domain-workflows-basic/config/domain-workflows-basic-start-days.config';
import { type WorkflowStatusBasicVisibility } from '@/views/domain-workflows-basic/domain-workflows-basic-filters/domain-workflows-basic-filters.types';
import isWorkflowStatusBasicVisibility from '@/views/domain-workflows-basic/domain-workflows-basic-filters/helpers/is-workflow-status-basic-visibility';
import isWorkflowStatus from '@/views/shared/workflow-status-tag/helpers/is-workflow-status';
import { type WorkflowStatus } from '@/views/shared/workflow-status-tag/workflow-status-tag.types';
import { type WorkflowsHeaderInputType } from '@/views/shared/workflows-header/workflows-header.types';
-const now = dayjs();
-
const domainPageQueryParamsConfig: [
PageQueryParam<'inputType', WorkflowsHeaderInputType>,
// Search input
PageQueryParam<'search', string>,
PageQueryParamMultiValue<'statuses', Array | undefined>,
- PageQueryParam<'timeRangeStart', Date | undefined>,
- PageQueryParam<'timeRangeEnd', Date>,
+ PageQueryParam<'timeRangeStart', DateFilterValue>,
+ PageQueryParam<'timeRangeEnd', DateFilterValue>,
PageQueryParam<'sortColumn', string>,
PageQueryParam<'sortOrder', SortOrder>,
// Query input
@@ -31,8 +27,8 @@ const domainPageQueryParamsConfig: [
PageQueryParam<'workflowId', string>,
PageQueryParam<'workflowType', string>,
PageQueryParam<'statusBasic', WorkflowStatusBasicVisibility | undefined>,
- PageQueryParam<'timeRangeStartBasic', Date>,
- PageQueryParam<'timeRangeEndBasic', Date>,
+ PageQueryParam<'timeRangeStartBasic', DateFilterValue>,
+ PageQueryParam<'timeRangeEndBasic', DateFilterValue>,
// Archival inputs
PageQueryParam<'inputTypeArchival', WorkflowsHeaderInputType>,
PageQueryParam<'searchArchival', string>,
@@ -40,8 +36,8 @@ const domainPageQueryParamsConfig: [
'statusesArchival',
Array | undefined
>,
- PageQueryParam<'timeRangeStartArchival', Date>,
- PageQueryParam<'timeRangeEndArchival', Date>,
+ PageQueryParam<'timeRangeStartArchival', DateFilterValue>,
+ PageQueryParam<'timeRangeEndArchival', DateFilterValue>,
PageQueryParam<'sortColumnArchival', string>,
PageQueryParam<'sortOrderArchival', SortOrder>,
PageQueryParam<'queryArchival', string>,
@@ -66,13 +62,14 @@ const domainPageQueryParamsConfig: [
{
key: 'timeRangeStart',
queryParamKey: 'start',
- parseValue: parseDateQueryParam,
+ defaultValue: 'now-7d',
+ parseValue: (v) => parseDateFilterValue(v, 'now-7d'),
},
{
key: 'timeRangeEnd',
queryParamKey: 'end',
- defaultValue: now.toDate(),
- parseValue: (v) => parseDateQueryParam(v) ?? now.toDate(),
+ defaultValue: 'now',
+ parseValue: (v) => parseDateFilterValue(v, 'now'),
},
{
key: 'sortColumn',
@@ -106,18 +103,14 @@ const domainPageQueryParamsConfig: [
{
key: 'timeRangeStartBasic',
queryParamKey: 'start',
- defaultValue: now
- .subtract(DOMAIN_WORKFLOWS_BASIC_START_DAYS_CONFIG, 'days')
- .toDate(),
- parseValue: (v) =>
- parseDateQueryParam(v) ??
- now.subtract(DOMAIN_WORKFLOWS_BASIC_START_DAYS_CONFIG, 'days').toDate(),
+ defaultValue: 'now-7d',
+ parseValue: (v) => parseDateFilterValue(v, 'now-7d'),
},
{
key: 'timeRangeEndBasic',
queryParamKey: 'end',
- defaultValue: now.toDate(),
- parseValue: (v) => parseDateQueryParam(v) ?? now.toDate(),
+ defaultValue: 'now',
+ parseValue: (v) => parseDateFilterValue(v, 'now'),
},
{
key: 'inputTypeArchival',
@@ -146,20 +139,14 @@ const domainPageQueryParamsConfig: [
{
key: 'timeRangeStartArchival',
queryParamKey: 'astart',
- defaultValue: now
- .subtract(DOMAIN_WORKFLOWS_ARCHIVAL_START_DAYS_CONFIG, 'days')
- .toDate(),
- parseValue: (v) =>
- parseDateQueryParam(v) ??
- now
- .subtract(DOMAIN_WORKFLOWS_ARCHIVAL_START_DAYS_CONFIG, 'days')
- .toDate(),
+ defaultValue: 'now-7d',
+ parseValue: (v) => parseDateFilterValue(v, 'now-7d'),
},
{
key: 'timeRangeEndArchival',
queryParamKey: 'aend',
- defaultValue: now.toDate(),
- parseValue: (v) => parseDateQueryParam(v) ?? now.toDate(),
+ defaultValue: 'now',
+ parseValue: (v) => parseDateFilterValue(v, 'now'),
},
{
key: 'sortColumnArchival',
diff --git a/src/views/domain-workflows-archival/config/domain-workflows-archival-filters.config.ts b/src/views/domain-workflows-archival/config/domain-workflows-archival-filters.config.ts
index 6eb4fe288..bf4d2fe80 100644
--- a/src/views/domain-workflows-archival/config/domain-workflows-archival-filters.config.ts
+++ b/src/views/domain-workflows-archival/config/domain-workflows-archival-filters.config.ts
@@ -2,7 +2,9 @@ import { createElement } from 'react';
import { omit } from 'lodash';
-import DateFilter from '@/components/date-filter/date-filter';
+import DateFilterV2 from '@/components/date-filter-v2/date-filter-v2';
+import { type DateFilterValue } from '@/components/date-filter-v2/date-filter-v2.types';
+import stringifyDateFilterValue from '@/components/date-filter-v2/helpers/stringify-date-filter-value';
import ListFilterMulti from '@/components/list-filter-multi/list-filter-multi';
import { type PageFilterConfig } from '@/components/page-filters/page-filters.types';
import type domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config';
@@ -18,8 +20,8 @@ const domainWorkflowsArchivalFiltersConfig: [
PageFilterConfig<
typeof domainPageQueryParamsConfig,
{
- timeRangeStartArchival: Date | undefined;
- timeRangeEndArchival: Date | undefined;
+ timeRangeStartArchival: DateFilterValue | undefined;
+ timeRangeEndArchival: DateFilterValue | undefined;
}
>,
] = [
@@ -41,14 +43,21 @@ const domainWorkflowsArchivalFiltersConfig: [
},
{
id: 'dates',
- getValue: (v) => v,
+ getValue: (v) => ({
+ timeRangeStartArchival: v.timeRangeStartArchival,
+ timeRangeEndArchival: v.timeRangeEndArchival,
+ }),
formatValue: (v) => ({
- timeRangeStartArchival: v.timeRangeStartArchival?.toISOString(),
- timeRangeEndArchival: v.timeRangeEndArchival?.toISOString(),
+ timeRangeStartArchival: v.timeRangeStartArchival
+ ? stringifyDateFilterValue(v.timeRangeStartArchival)
+ : undefined,
+ timeRangeEndArchival: v.timeRangeEndArchival
+ ? stringifyDateFilterValue(v.timeRangeEndArchival)
+ : undefined,
}),
component: ({ value, setValue }) =>
- createElement(DateFilter, {
- label: 'Dates',
+ createElement(DateFilterV2, {
+ label: 'Time range',
placeholder: 'Select time range',
dates: {
start: value.timeRangeStartArchival,
@@ -59,7 +68,6 @@ const domainWorkflowsArchivalFiltersConfig: [
timeRangeStartArchival: start,
timeRangeEndArchival: end,
}),
- clearable: false,
}),
},
] as const;
diff --git a/src/views/domain-workflows-archival/domain-workflows-archival-header/__tests__/domain-workflows-archival-header.test.tsx b/src/views/domain-workflows-archival/domain-workflows-archival-header/__tests__/domain-workflows-archival-header.test.tsx
deleted file mode 100644
index 7ef26a2fc..000000000
--- a/src/views/domain-workflows-archival/domain-workflows-archival-header/__tests__/domain-workflows-archival-header.test.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { render, screen } from '@/test-utils/rtl';
-
-import { mockDomainPageQueryParamsValues } from '../../../domain-page/__fixtures__/domain-page-query-params';
-import DomainWorkflowsArchivalHeader from '../domain-workflows-archival-header';
-
-jest.useFakeTimers().setSystemTime(new Date('2023-05-25'));
-
-const mockSetQueryParams = jest.fn();
-jest.mock('@/hooks/use-page-query-params/use-page-query-params', () =>
- jest.fn(() => [mockDomainPageQueryParamsValues, mockSetQueryParams])
-);
-
-jest.mock('@/views/shared/workflows-header/workflows-header', () =>
- jest.fn(() => Workflows Header
)
-);
-
-jest.mock('@/views/shared/hooks/use-list-workflows', () =>
- jest.fn(() => ({
- refetch: jest.fn(),
- isFetching: false,
- }))
-);
-
-describe(DomainWorkflowsArchivalHeader.name, () => {
- it('renders workflows header', async () => {
- render(
-
- );
-
- expect(screen.getByText('Workflows Header')).toBeInTheDocument();
- });
-});
diff --git a/src/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.tsx b/src/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.tsx
index 4515856c7..39b9d041e 100644
--- a/src/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.tsx
+++ b/src/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.tsx
@@ -13,6 +13,8 @@ import { type Props } from './domain-workflows-archival-header.types';
export default function DomainWorkflowsArchivalHeader({
domain,
cluster,
+ timeRangeStart,
+ timeRangeEnd,
}: Props) {
const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig);
@@ -24,8 +26,8 @@ export default function DomainWorkflowsArchivalHeader({
inputType: queryParams.inputTypeArchival,
search: queryParams.searchArchival,
statuses: queryParams.statusesArchival,
- timeRangeStart: queryParams.timeRangeStartArchival,
- timeRangeEnd: queryParams.timeRangeEndArchival,
+ timeRangeStart,
+ timeRangeEnd,
sortColumn: queryParams.sortColumnArchival,
sortOrder: queryParams.sortOrderArchival,
query: queryParams.queryArchival,
diff --git a/src/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.types.ts b/src/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.types.ts
index 042c6d33b..7d2daac02 100644
--- a/src/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.types.ts
+++ b/src/views/domain-workflows-archival/domain-workflows-archival-header/domain-workflows-archival-header.types.ts
@@ -3,6 +3,8 @@ import { type WorkflowStatus } from '@/views/shared/workflow-status-tag/workflow
export type Props = {
domain: string;
cluster: string;
+ timeRangeStart: string;
+ timeRangeEnd: string;
};
export type WorkflowStatusClosed = Exclude<
diff --git a/src/views/domain-workflows-archival/domain-workflows-archival-table/__tests__/domain-workflows-archival-table.test.tsx b/src/views/domain-workflows-archival/domain-workflows-archival-table/__tests__/domain-workflows-archival-table.test.tsx
index 7bddcc303..01b5e9af5 100644
--- a/src/views/domain-workflows-archival/domain-workflows-archival-table/__tests__/domain-workflows-archival-table.test.tsx
+++ b/src/views/domain-workflows-archival/domain-workflows-archival-table/__tests__/domain-workflows-archival-table.test.tsx
@@ -131,6 +131,8 @@ function setup({
,
{
endpointsMocks: [
diff --git a/src/views/domain-workflows-archival/domain-workflows-archival-table/domain-workflows-archival-table.tsx b/src/views/domain-workflows-archival/domain-workflows-archival-table/domain-workflows-archival-table.tsx
index d9cd1895b..ff128a7cc 100644
--- a/src/views/domain-workflows-archival/domain-workflows-archival-table/domain-workflows-archival-table.tsx
+++ b/src/views/domain-workflows-archival/domain-workflows-archival-table/domain-workflows-archival-table.tsx
@@ -18,6 +18,8 @@ import getArchivalErrorPanelProps from './helpers/get-archival-error-panel-props
export default function DomainWorkflowsArchivalTable({
domain,
cluster,
+ timeRangeStart,
+ timeRangeEnd,
}: Props) {
const [queryParams, setQueryParams] = usePageQueryParams(
domainPageQueryParamsConfig
@@ -39,8 +41,8 @@ export default function DomainWorkflowsArchivalTable({
inputType: queryParams.inputTypeArchival,
search: queryParams.searchArchival,
statuses: queryParams.statusesArchival,
- timeRangeStart: queryParams.timeRangeStartArchival,
- timeRangeEnd: queryParams.timeRangeEndArchival,
+ timeRangeStart,
+ timeRangeEnd,
sortColumn: queryParams.sortColumnArchival,
sortOrder: queryParams.sortOrderArchival,
query: queryParams.queryArchival,
diff --git a/src/views/domain-workflows-archival/domain-workflows-archival-table/domain-workflows-archival-table.types.ts b/src/views/domain-workflows-archival/domain-workflows-archival-table/domain-workflows-archival-table.types.ts
index 2adbd04c7..7c3616b74 100644
--- a/src/views/domain-workflows-archival/domain-workflows-archival-table/domain-workflows-archival-table.types.ts
+++ b/src/views/domain-workflows-archival/domain-workflows-archival-table/domain-workflows-archival-table.types.ts
@@ -1,4 +1,6 @@
export type Props = {
domain: string;
cluster: string;
+ timeRangeStart: string;
+ timeRangeEnd: string;
};
diff --git a/src/views/domain-workflows-archival/domain-workflows-archival.tsx b/src/views/domain-workflows-archival/domain-workflows-archival.tsx
index f23ce387f..669dcebee 100644
--- a/src/views/domain-workflows-archival/domain-workflows-archival.tsx
+++ b/src/views/domain-workflows-archival/domain-workflows-archival.tsx
@@ -1,7 +1,12 @@
-import React from 'react';
+import React, { useMemo } from 'react';
+import dayjs from 'dayjs';
+
+import getDayjsFromDateFilterValue from '@/components/date-filter-v2/helpers/get-dayjs-from-date-filter-value';
+import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params';
import { type DomainPageTabContentProps } from '@/views/domain-page/domain-page-content/domain-page-content.types';
+import domainPageQueryParamsConfig from '../domain-page/config/domain-page-query-params.config';
import useSuspenseDomainDescription from '../shared/hooks/use-domain-description/use-suspense-domain-description';
import DomainWorkflowsArchivalDisabledPanel from './domain-workflows-archival-disabled-panel/domain-workflows-archival-disabled-panel';
@@ -15,6 +20,23 @@ export default function DomainWorkflowsArchival(
data: { historyArchivalStatus, visibilityArchivalStatus },
} = useSuspenseDomainDescription(props);
+ const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig);
+
+ const timeRangeParams = useMemo(() => {
+ const now = dayjs();
+
+ return {
+ timeRangeStart: getDayjsFromDateFilterValue(
+ queryParams.timeRangeStartArchival,
+ now
+ ).toISOString(),
+ timeRangeEnd: getDayjsFromDateFilterValue(
+ queryParams.timeRangeEndArchival,
+ now
+ ).toISOString(),
+ };
+ }, [queryParams.timeRangeStartArchival, queryParams.timeRangeEndArchival]);
+
if (
historyArchivalStatus !== 'ARCHIVAL_STATUS_ENABLED' ||
visibilityArchivalStatus !== 'ARCHIVAL_STATUS_ENABLED'
@@ -27,10 +49,12 @@ export default function DomainWorkflowsArchival(
>
);
diff --git a/src/views/domain-workflows-basic/config/domain-workflows-basic-filters.config.ts b/src/views/domain-workflows-basic/config/domain-workflows-basic-filters.config.ts
index 88b171902..2a9424231 100644
--- a/src/views/domain-workflows-basic/config/domain-workflows-basic-filters.config.ts
+++ b/src/views/domain-workflows-basic/config/domain-workflows-basic-filters.config.ts
@@ -1,6 +1,8 @@
import { createElement } from 'react';
-import DateFilter from '@/components/date-filter/date-filter';
+import DateFilterV2 from '@/components/date-filter-v2/date-filter-v2';
+import { type DateFilterValue } from '@/components/date-filter-v2/date-filter-v2.types';
+import stringifyDateFilterValue from '@/components/date-filter-v2/helpers/stringify-date-filter-value';
import ListFilter from '@/components/list-filter/list-filter';
import { type PageFilterConfig } from '@/components/page-filters/page-filters.types';
import type domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config';
@@ -16,8 +18,8 @@ const domainWorkflowsBasicFiltersConfig: [
PageFilterConfig<
typeof domainPageQueryParamsConfig,
{
- timeRangeStartBasic: Date | undefined;
- timeRangeEndBasic: Date | undefined;
+ timeRangeStartBasic: DateFilterValue | undefined;
+ timeRangeEndBasic: DateFilterValue | undefined;
}
>,
] = [
@@ -41,12 +43,16 @@ const domainWorkflowsBasicFiltersConfig: [
timeRangeEndBasic: v.timeRangeEndBasic,
}),
formatValue: (v) => ({
- timeRangeStartBasic: v.timeRangeStartBasic?.toISOString(),
- timeRangeEndBasic: v.timeRangeEndBasic?.toISOString(),
+ timeRangeStartBasic: v.timeRangeStartBasic
+ ? stringifyDateFilterValue(v.timeRangeStartBasic)
+ : undefined,
+ timeRangeEndBasic: v.timeRangeEndBasic
+ ? stringifyDateFilterValue(v.timeRangeEndBasic)
+ : undefined,
}),
component: ({ value, setValue }) =>
- createElement(DateFilter, {
- label: 'Dates',
+ createElement(DateFilterV2, {
+ label: 'Time range',
placeholder: 'Select time range',
dates: {
start: value.timeRangeStartBasic,
@@ -57,7 +63,6 @@ const domainWorkflowsBasicFiltersConfig: [
timeRangeStartBasic: start,
timeRangeEndBasic: end,
}),
- clearable: false,
}),
},
] as const;
diff --git a/src/views/domain-workflows-basic/config/domain-workflows-basic-start-days.config.ts b/src/views/domain-workflows-basic/config/domain-workflows-basic-start-days.config.ts
deleted file mode 100644
index 0719cdfe9..000000000
--- a/src/views/domain-workflows-basic/config/domain-workflows-basic-start-days.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-const DOMAIN_WORKFLOWS_BASIC_START_DAYS_CONFIG = 30;
-
-export default DOMAIN_WORKFLOWS_BASIC_START_DAYS_CONFIG;
diff --git a/src/views/domain-workflows-basic/domain-workflows-basic-table/__tests__/domain-workflows-basic-table.test.tsx b/src/views/domain-workflows-basic/domain-workflows-basic-table/__tests__/domain-workflows-basic-table.test.tsx
index d38799119..555701be3 100644
--- a/src/views/domain-workflows-basic/domain-workflows-basic-table/__tests__/domain-workflows-basic-table.test.tsx
+++ b/src/views/domain-workflows-basic/domain-workflows-basic-table/__tests__/domain-workflows-basic-table.test.tsx
@@ -14,6 +14,11 @@ jest.mock('@/components/error-panel/error-panel', () =>
jest.fn(({ message }: { message: string }) => {message}
)
);
+jest.mock('../../config/domain-workflows-basic-page-size.config', () => ({
+ __esModule: true,
+ default: 5,
+}));
+
jest.mock('../helpers/get-workflows-basic-error-panel-props', () =>
jest.fn().mockImplementation(({ error }: { error: Error }) => {
return {
@@ -52,13 +57,18 @@ describe(DomainWorkflowsBasicTable.name, () => {
it('renders workflows without error', async () => {
const { user } = setup({});
+ // Load 1 page of open workflows first
expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
-
expect(screen.getByText(`mock-workflow-id-0-0-open`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-1-open`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-2-open`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-3-open`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-4-open`)).toBeInTheDocument();
+
+ await user.click(screen.getByTestId('mock-loader'));
+
+ // Load page 1 of closed workflows
+ expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-0-closed`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-1-closed`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-2-closed`)).toBeInTheDocument();
@@ -67,17 +77,12 @@ describe(DomainWorkflowsBasicTable.name, () => {
await user.click(screen.getByTestId('mock-loader'));
- expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-5-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-6-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-7-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-8-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-9-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-5-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-6-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-7-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-8-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-9-closed`)).toBeInTheDocument();
+ // Then load page 2 of closed workflows
+ expect(screen.getByText(`mock-workflow-id-1-0-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-1-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-2-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-3-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-4-closed`)).toBeInTheDocument();
});
it('renders error panel if the initial call fails', async () => {
@@ -94,42 +99,6 @@ describe(DomainWorkflowsBasicTable.name, () => {
expect(await screen.findByText('No workflows found')).toBeInTheDocument();
});
- it('renders workflows and allows the user to try again if there is an error', async () => {
- const { user } = setup({ errorCase: 'subsequent-fetch-error' });
-
- expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-0-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-1-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-2-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-3-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-4-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-0-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-1-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-2-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-3-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-4-closed`)).toBeInTheDocument();
-
- await user.click(screen.getByTestId('mock-loader'));
-
- expect(
- await screen.findByText('Mock end message: Error')
- ).toBeInTheDocument();
-
- await user.click(screen.getByTestId('mock-loader'));
-
- expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-5-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-6-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-7-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-8-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-9-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-5-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-6-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-7-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-8-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-9-closed`)).toBeInTheDocument();
- });
-
it('calls only listOpen if Running status is selected', async () => {
jest.spyOn(usePageQueryParamsModule, 'default').mockReturnValue([
{
@@ -147,11 +116,6 @@ describe(DomainWorkflowsBasicTable.name, () => {
expect(screen.getByText(`mock-workflow-id-0-2-open`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-3-open`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-4-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-5-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-6-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-7-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-8-open`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-9-open`)).toBeInTheDocument();
});
it('calls only listClosed if a close status is selected', async () => {
@@ -172,11 +136,39 @@ describe(DomainWorkflowsBasicTable.name, () => {
expect(screen.getByText(`mock-workflow-id-0-2-closed`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-3-closed`)).toBeInTheDocument();
expect(screen.getByText(`mock-workflow-id-0-4-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-5-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-6-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-7-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-8-closed`)).toBeInTheDocument();
- expect(screen.getByText(`mock-workflow-id-0-9-closed`)).toBeInTheDocument();
+ });
+
+ it('renders workflows and allows the user to try again if there is an error', async () => {
+ jest.spyOn(usePageQueryParamsModule, 'default').mockReturnValue([
+ {
+ ...mockDomainPageQueryParamsValues,
+ statusBasic: 'WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED',
+ },
+ mockSetQueryParams,
+ ]);
+ const { user } = setup({ errorCase: 'subsequent-fetch-error' });
+
+ expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-0-0-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-0-1-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-0-2-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-0-3-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-0-4-closed`)).toBeInTheDocument();
+
+ await user.click(screen.getByTestId('mock-loader'));
+
+ expect(
+ await screen.findByText('Mock end message: Error')
+ ).toBeInTheDocument();
+
+ await user.click(screen.getByTestId('mock-loader'));
+
+ expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-0-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-1-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-2-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-3-closed`)).toBeInTheDocument();
+ expect(screen.getByText(`mock-workflow-id-1-4-closed`)).toBeInTheDocument();
});
});
@@ -185,7 +177,7 @@ function setup({
}: {
errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error' | 'no-workflows';
}) {
- const openPages = generateWorkflowPages(2, true);
+ const openPages = generateWorkflowPages(1, true);
const closedPages = generateWorkflowPages(2);
let currentEventIndexOpen = 0;
@@ -261,7 +253,7 @@ function generateWorkflowPages(
const pages = Array.from(
{ length: count },
(_, pageIndex): ListWorkflowsResponse => ({
- workflows: Array.from({ length: 10 }, (_, index) => ({
+ workflows: Array.from({ length: 5 }, (_, index) => ({
workflowID: `mock-workflow-id-${pageIndex}-${index}-${isOpen ? 'open' : 'closed'}`,
runID: `mock-run-id-${pageIndex}-${index}`,
workflowName: `mock-workflow-name-${pageIndex}-${index}`,
diff --git a/src/views/domain-workflows-basic/hooks/helpers/get-list-workflows-basic-query-options.ts b/src/views/domain-workflows-basic/hooks/helpers/get-list-workflows-basic-query-options.ts
index baaa080f7..eac99947d 100644
--- a/src/views/domain-workflows-basic/hooks/helpers/get-list-workflows-basic-query-options.ts
+++ b/src/views/domain-workflows-basic/hooks/helpers/get-list-workflows-basic-query-options.ts
@@ -15,13 +15,26 @@ export default function getListWorkflowsBasicQueryOptions({
domain: string;
cluster: string;
requestQueryParams: ListWorkflowsBasicRequestQueryParams;
-}): SingleInfiniteQueryOptions {
+}): SingleInfiniteQueryOptions<
+ ListWorkflowsBasicResponse,
+ string | undefined,
+ [
+ string,
+ {
+ domain: string;
+ cluster: string;
+ } & ListWorkflowsBasicRequestQueryParams,
+ ]
+> {
return {
queryKey: [
'listWorkflowsBasic',
{ domain, cluster, ...requestQueryParams },
],
- queryFn: async ({ pageParam }) =>
+ queryFn: async ({
+ pageParam,
+ queryKey: [_, { domain, cluster, ...requestQueryParams }],
+ }) =>
request(
queryString.stringifyUrl({
url: `/api/domains/${domain}/${cluster}/workflows-basic`,
diff --git a/src/views/domain-workflows-basic/hooks/use-list-workflows-basic.ts b/src/views/domain-workflows-basic/hooks/use-list-workflows-basic.ts
index d3274b570..9504122ab 100644
--- a/src/views/domain-workflows-basic/hooks/use-list-workflows-basic.ts
+++ b/src/views/domain-workflows-basic/hooks/use-list-workflows-basic.ts
@@ -2,9 +2,11 @@
import { useMemo } from 'react';
+import getDayjsFromDateFilterValue from '@/components/date-filter-v2/helpers/get-dayjs-from-date-filter-value';
import useMergedInfiniteQueries from '@/hooks/use-merged-infinite-queries/use-merged-infinite-queries';
import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params';
import { type ListWorkflowsBasicRequestQueryParams } from '@/route-handlers/list-workflows-basic/list-workflows-basic.types';
+import dayjs from '@/utils/datetime/dayjs';
import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config';
import DOMAIN_WORKFLOWS_BASIC_PAGE_SIZE from '../config/domain-workflows-basic-page-size.config';
@@ -28,12 +30,26 @@ export default function useListWorkflowsBasic({
queryParams.statusBasic === undefined ||
queryParams.statusBasic !== 'WORKFLOW_EXECUTION_CLOSE_STATUS_INVALID';
+ const timeRangeParams = useMemo(() => {
+ const now = dayjs();
+
+ return {
+ timeRangeStart: getDayjsFromDateFilterValue(
+ queryParams.timeRangeStartBasic,
+ now
+ ).toISOString(),
+ timeRangeEnd: getDayjsFromDateFilterValue(
+ queryParams.timeRangeEndBasic,
+ now
+ ).toISOString(),
+ };
+ }, [queryParams.timeRangeStartBasic, queryParams.timeRangeEndBasic]);
+
const queryConfigs = useMemo(() => {
const requestQueryParamsBase = {
workflowId: queryParams.workflowId,
workflowType: queryParams.workflowType,
- timeRangeStart: queryParams.timeRangeStartBasic.toISOString(),
- timeRangeEnd: queryParams.timeRangeEndBasic.toISOString(),
+ ...timeRangeParams,
pageSize: pageSize.toString(),
...(queryParams.statusBasic !== 'ALL_CLOSED' &&
queryParams.statusBasic !== 'WORKFLOW_EXECUTION_CLOSE_STATUS_INVALID'
@@ -77,8 +93,7 @@ export default function useListWorkflowsBasic({
loadClosedWorkflows,
queryParams.workflowId,
queryParams.workflowType,
- queryParams.timeRangeStartBasic,
- queryParams.timeRangeEndBasic,
+ timeRangeParams,
queryParams.statusBasic,
]);
diff --git a/src/views/domain-workflows/config/domain-workflows-filters.config.ts b/src/views/domain-workflows/config/domain-workflows-filters.config.ts
index ed3743e30..3363b5d23 100644
--- a/src/views/domain-workflows/config/domain-workflows-filters.config.ts
+++ b/src/views/domain-workflows/config/domain-workflows-filters.config.ts
@@ -1,6 +1,8 @@
import { createElement } from 'react';
-import DateFilter from '@/components/date-filter/date-filter';
+import DateFilterV2 from '@/components/date-filter-v2/date-filter-v2';
+import { type DateFilterValue } from '@/components/date-filter-v2/date-filter-v2.types';
+import stringifyDateFilterValue from '@/components/date-filter-v2/helpers/stringify-date-filter-value';
import ListFilterMulti from '@/components/list-filter-multi/list-filter-multi';
import { type PageFilterConfig } from '@/components/page-filters/page-filters.types';
import type domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config';
@@ -14,7 +16,10 @@ const domainWorkflowsFiltersConfig: [
>,
PageFilterConfig<
typeof domainPageQueryParamsConfig,
- { timeRangeStart: Date | undefined; timeRangeEnd: Date | undefined }
+ {
+ timeRangeStart: DateFilterValue | undefined;
+ timeRangeEnd: DateFilterValue | undefined;
+ }
>,
] = [
{
@@ -37,19 +42,26 @@ const domainWorkflowsFiltersConfig: [
timeRangeEnd: v.timeRangeEnd,
}),
formatValue: (v) => ({
- timeRangeStart: v.timeRangeStart?.toISOString(),
- timeRangeEnd: v.timeRangeEnd?.toISOString(),
+ timeRangeStart: v.timeRangeStart
+ ? stringifyDateFilterValue(v.timeRangeStart)
+ : undefined,
+ timeRangeEnd: v.timeRangeEnd
+ ? stringifyDateFilterValue(v.timeRangeEnd)
+ : undefined,
}),
component: ({ value, setValue }) =>
- createElement(DateFilter, {
- label: 'Dates',
+ createElement(DateFilterV2, {
+ label: 'Time range',
placeholder: 'Select time range',
dates: {
start: value.timeRangeStart,
end: value.timeRangeEnd,
},
onChangeDates: ({ start, end }) =>
- setValue({ timeRangeStart: start, timeRangeEnd: end }),
+ setValue({
+ timeRangeStart: start,
+ timeRangeEnd: end,
+ }),
}),
},
] as const;
diff --git a/src/views/domain-workflows/domain-workflows-advanced/domain-workflows-advanced.tsx b/src/views/domain-workflows/domain-workflows-advanced/domain-workflows-advanced.tsx
new file mode 100644
index 000000000..9743945a4
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-advanced/domain-workflows-advanced.tsx
@@ -0,0 +1,45 @@
+import { useMemo } from 'react';
+
+import getDayjsFromDateFilterValue from '@/components/date-filter-v2/helpers/get-dayjs-from-date-filter-value';
+import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params';
+import dayjs from '@/utils/datetime/dayjs';
+import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config';
+
+import DomainWorkflowsHeader from '../domain-workflows-header/domain-workflows-header';
+import DomainWorkflowsTable from '../domain-workflows-table/domain-workflows-table';
+
+import { type Props } from './domain-workflows-advanced.types';
+
+export default function DomainWorkflowsAdvanced({ domain, cluster }: Props) {
+ const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig);
+
+ const timeRangeParams = useMemo(() => {
+ const now = dayjs();
+
+ return {
+ timeRangeStart: getDayjsFromDateFilterValue(
+ queryParams.timeRangeStartBasic,
+ now
+ ).toISOString(),
+ timeRangeEnd: getDayjsFromDateFilterValue(
+ queryParams.timeRangeEndBasic,
+ now
+ ).toISOString(),
+ };
+ }, [queryParams.timeRangeStartBasic, queryParams.timeRangeEndBasic]);
+
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/src/views/domain-workflows/domain-workflows-advanced/domain-workflows-advanced.types.ts b/src/views/domain-workflows/domain-workflows-advanced/domain-workflows-advanced.types.ts
new file mode 100644
index 000000000..2adbd04c7
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-advanced/domain-workflows-advanced.types.ts
@@ -0,0 +1,4 @@
+export type Props = {
+ domain: string;
+ cluster: string;
+};
diff --git a/src/views/domain-workflows/domain-workflows-header/domain-workflows-header.tsx b/src/views/domain-workflows/domain-workflows-header/domain-workflows-header.tsx
index fcffd7217..b6a595fe3 100644
--- a/src/views/domain-workflows/domain-workflows-header/domain-workflows-header.tsx
+++ b/src/views/domain-workflows/domain-workflows-header/domain-workflows-header.tsx
@@ -9,7 +9,12 @@ import DOMAIN_WORKFLOWS_PAGE_SIZE from '../config/domain-workflows-page-size.con
import { type Props } from './domain-workflows-header.types';
-export default function DomainWorkflowsHeader({ domain, cluster }: Props) {
+export default function DomainWorkflowsHeader({
+ domain,
+ cluster,
+ timeRangeStart,
+ timeRangeEnd,
+}: Props) {
const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig);
const { refetch, isFetching } = useListWorkflows({
@@ -20,8 +25,8 @@ export default function DomainWorkflowsHeader({ domain, cluster }: Props) {
inputType: queryParams.inputType,
search: queryParams.search,
statuses: queryParams.statuses,
- timeRangeStart: queryParams.timeRangeStart,
- timeRangeEnd: queryParams.timeRangeEnd,
+ timeRangeStart,
+ timeRangeEnd,
sortColumn: queryParams.sortColumn,
sortOrder: queryParams.sortOrder,
query: queryParams.query,
diff --git a/src/views/domain-workflows/domain-workflows-header/domain-workflows-header.types.ts b/src/views/domain-workflows/domain-workflows-header/domain-workflows-header.types.ts
index 2adbd04c7..7c3616b74 100644
--- a/src/views/domain-workflows/domain-workflows-header/domain-workflows-header.types.ts
+++ b/src/views/domain-workflows/domain-workflows-header/domain-workflows-header.types.ts
@@ -1,4 +1,6 @@
export type Props = {
domain: string;
cluster: string;
+ timeRangeStart: string;
+ timeRangeEnd: string;
};
diff --git a/src/views/domain-workflows/domain-workflows-table/__tests__/domain-workflows-table.test.tsx b/src/views/domain-workflows/domain-workflows-table/__tests__/domain-workflows-table.test.tsx
index ca10350f8..2f923558a 100644
--- a/src/views/domain-workflows/domain-workflows-table/__tests__/domain-workflows-table.test.tsx
+++ b/src/views/domain-workflows/domain-workflows-table/__tests__/domain-workflows-table.test.tsx
@@ -152,49 +152,57 @@ function setup({
let currentEventIndex = 0;
const user = userEvent.setup();
- render(, {
- endpointsMocks: [
- {
- path: '/api/domains/:domain/:cluster/workflows',
- httpMethod: 'GET',
- mockOnce: false,
- httpResolver: async () => {
- const index = currentEventIndex;
- currentEventIndex++;
-
- switch (errorCase) {
- case 'no-workflows':
- return HttpResponse.json({
- workflows: [],
- nextPage: undefined,
- });
- case 'initial-fetch-error':
- return HttpResponse.json(
- { message: 'Request failed' },
- { status: 500 }
- );
- case 'subsequent-fetch-error':
- if (index === 0) {
- return HttpResponse.json(pages[0]);
- } else if (index === 1) {
+ render(
+ ,
+ {
+ endpointsMocks: [
+ {
+ path: '/api/domains/:domain/:cluster/workflows',
+ httpMethod: 'GET',
+ mockOnce: false,
+ httpResolver: async () => {
+ const index = currentEventIndex;
+ currentEventIndex++;
+
+ switch (errorCase) {
+ case 'no-workflows':
+ return HttpResponse.json({
+ workflows: [],
+ nextPage: undefined,
+ });
+ case 'initial-fetch-error':
return HttpResponse.json(
{ message: 'Request failed' },
{ status: 500 }
);
- } else {
- return HttpResponse.json(pages[1]);
- }
- default:
- if (index === 0) {
- return HttpResponse.json(pages[0]);
- } else {
- return HttpResponse.json(pages[1]);
- }
- }
+ case 'subsequent-fetch-error':
+ if (index === 0) {
+ return HttpResponse.json(pages[0]);
+ } else if (index === 1) {
+ return HttpResponse.json(
+ { message: 'Request failed' },
+ { status: 500 }
+ );
+ } else {
+ return HttpResponse.json(pages[1]);
+ }
+ default:
+ if (index === 0) {
+ return HttpResponse.json(pages[0]);
+ } else {
+ return HttpResponse.json(pages[1]);
+ }
+ }
+ },
},
- },
- ] as MSWMocksHandlersProps['endpointsMocks'],
- });
+ ] as MSWMocksHandlersProps['endpointsMocks'],
+ }
+ );
return { user };
}
diff --git a/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.tsx b/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.tsx
index 31f16fb81..bf1ecda09 100644
--- a/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.tsx
+++ b/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.tsx
@@ -15,7 +15,12 @@ import DOMAIN_WORKFLOWS_PAGE_SIZE from '../config/domain-workflows-page-size.con
import { type Props } from './domain-workflows-table.types';
import getWorkflowsErrorPanelProps from './helpers/get-workflows-error-panel-props';
-export default function DomainWorkflowsTable({ domain, cluster }: Props) {
+export default function DomainWorkflowsTable({
+ domain,
+ cluster,
+ timeRangeStart,
+ timeRangeEnd,
+}: Props) {
const [queryParams, setQueryParams] = usePageQueryParams(
domainPageQueryParamsConfig
);
@@ -36,8 +41,8 @@ export default function DomainWorkflowsTable({ domain, cluster }: Props) {
inputType: queryParams.inputType,
search: queryParams.search,
statuses: queryParams.statuses,
- timeRangeStart: queryParams.timeRangeStart,
- timeRangeEnd: queryParams.timeRangeEnd,
+ timeRangeStart,
+ timeRangeEnd,
sortColumn: queryParams.sortColumn,
sortOrder: queryParams.sortOrder,
query: queryParams.query,
diff --git a/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.types.ts b/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.types.ts
index 2adbd04c7..7c3616b74 100644
--- a/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.types.ts
+++ b/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.types.ts
@@ -1,4 +1,6 @@
export type Props = {
domain: string;
cluster: string;
+ timeRangeStart: string;
+ timeRangeEnd: string;
};
diff --git a/src/views/domain-workflows/domain-workflows.tsx b/src/views/domain-workflows/domain-workflows.tsx
index 8eba0835b..06b4ee95d 100644
--- a/src/views/domain-workflows/domain-workflows.tsx
+++ b/src/views/domain-workflows/domain-workflows.tsx
@@ -13,12 +13,8 @@ const DomainWorkflowsBasic = dynamic(
() => import('@/views/domain-workflows-basic/domain-workflows-basic')
);
-const DomainWorkflowsHeader = dynamic(
- () => import('./domain-workflows-header/domain-workflows-header')
-);
-
-const DomainWorkflowsTable = dynamic(
- () => import('./domain-workflows-table/domain-workflows-table')
+const DomainWorkflowsAdvanced = dynamic(
+ () => import('./domain-workflows-advanced/domain-workflows-advanced')
);
export default function DomainWorkflows(props: DomainPageTabContentProps) {
@@ -32,16 +28,11 @@ export default function DomainWorkflows(props: DomainPageTabContentProps) {
return isClusterAdvancedVisibilityEnabled(data);
}, [data]);
- if (!isAdvancedVisibilityEnabled) {
- return (
-
- );
- }
+ const DomainWorkflowsComponent = isAdvancedVisibilityEnabled
+ ? DomainWorkflowsAdvanced
+ : DomainWorkflowsBasic;
return (
- <>
-
-
- >
+
);
}
diff --git a/src/views/shared/hooks/use-list-workflows.ts b/src/views/shared/hooks/use-list-workflows.ts
index 5a2b2dc66..043d75291 100644
--- a/src/views/shared/hooks/use-list-workflows.ts
+++ b/src/views/shared/hooks/use-list-workflows.ts
@@ -42,8 +42,8 @@ export default function useListWorkflows({
statuses,
sortColumn,
sortOrder,
- timeRangeStart: timeRangeStart?.toISOString(),
- timeRangeEnd: timeRangeEnd?.toISOString(),
+ timeRangeStart,
+ timeRangeEnd,
}),
};
diff --git a/src/views/shared/hooks/use-list-workflows.types.ts b/src/views/shared/hooks/use-list-workflows.types.ts
index cc6ff16fe..004b6ef5e 100644
--- a/src/views/shared/hooks/use-list-workflows.types.ts
+++ b/src/views/shared/hooks/use-list-workflows.types.ts
@@ -15,8 +15,8 @@ export type UseListWorkflowsParams = ListWorkflowsRouteParams & {
listType: ListType;
search?: string;
statuses?: Array;
- timeRangeStart?: Date;
- timeRangeEnd?: Date;
+ timeRangeStart?: string;
+ timeRangeEnd?: string;
sortColumn?: string;
sortOrder?: SortOrder;
query?: string;