diff --git a/src/components/page-filters/page-filters.types.ts b/src/components/page-filters/page-filters.types.ts
index 322cb168..84d99002 100644
--- a/src/components/page-filters/page-filters.types.ts
+++ b/src/components/page-filters/page-filters.types.ts
@@ -18,7 +18,12 @@ export type PageFilterConfig<
> = {
id: string;
getValue: (queryParamsValues: PageQueryParamValues
) => V;
- formatValue: (value: V) => Partial>;
+ formatValue: (
+ value: V
+ ) => Pick<
+ PageQueryParamSetterValues,
+ keyof V extends string ? keyof V : never
+ >;
component: React.ComponentType>;
};
diff --git a/src/views/domain-page/config/domain-page-tabs.config.ts b/src/views/domain-page/config/domain-page-tabs.config.ts
index 0a9d91cb..f97f62fe 100644
--- a/src/views/domain-page/config/domain-page-tabs.config.ts
+++ b/src/views/domain-page/config/domain-page-tabs.config.ts
@@ -1,4 +1,4 @@
-import { MdListAlt, MdSettings, MdSort } from 'react-icons/md';
+import { MdArchive, MdListAlt, MdSettings, MdSort } from 'react-icons/md';
import type { DomainPageTabs } from '../domain-page-tabs/domain-page-tabs.types';
@@ -21,7 +21,7 @@ const domainPageTabsConfig = [
{
key: 'archival',
title: 'Archival',
- artwork: MdSort,
+ artwork: MdArchive,
},
] as const satisfies DomainPageTabs;
diff --git a/src/views/domain-workflows/__fixtures__/domain-workflows-query-params.ts b/src/views/domain-workflows/__fixtures__/domain-workflows-query-params.ts
index 38ecf037..bf4ce307 100644
--- a/src/views/domain-workflows/__fixtures__/domain-workflows-query-params.ts
+++ b/src/views/domain-workflows/__fixtures__/domain-workflows-query-params.ts
@@ -15,9 +15,22 @@ export const mockDomainWorkflowsQueryParamsValues: PageQueryParamValues<
workflowId: '',
workflowType: '',
statusBasic: undefined,
+ inputTypeArchival: 'search',
+ searchArchival: '',
+ statusArchival: undefined,
+ timeRangeStartArchival: undefined,
+ timeRangeEndArchival: undefined,
+ sortColumnArchival: 'startTime',
+ sortOrderArchival: 'DESC',
+ queryArchival: '',
};
export const mockDateOverrides = {
timeRangeStart: new Date(1684800000000), // 23 May 2023 00:00
timeRangeEnd: new Date(1684886400000), // 24 May 2023 00:00
};
+
+export const mockDateOverridesArchival = {
+ timeRangeStartArchival: new Date(1684800000000), // 23 May 2023 00:00
+ timeRangeEndArchival: new Date(1684886400000), // 24 May 2023 00:00
+};
diff --git a/src/views/domain-workflows/config/domain-workflows-archival-filters.config.ts b/src/views/domain-workflows/config/domain-workflows-archival-filters.config.ts
index 25b5f3a5..49c53e27 100644
--- a/src/views/domain-workflows/config/domain-workflows-archival-filters.config.ts
+++ b/src/views/domain-workflows/config/domain-workflows-archival-filters.config.ts
@@ -1,38 +1,38 @@
import { type PageFilterConfig } from '@/components/page-filters/page-filters.types';
import type domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config';
-import DomainWorkflowsFiltersDates from '../domain-workflows-filters-dates/domain-workflows-filters-dates';
-import { type DomainWorkflowsFiltersDatesValue } from '../domain-workflows-filters-dates/domain-workflows-filters-dates.types';
-import DomainWorkflowsFiltersStatus from '../domain-workflows-filters-status/domain-workflows-filters-status';
-import { type DomainWorkflowsFiltersStatusValue } from '../domain-workflows-filters-status/domain-workflows-filters-status.types';
+import DomainWorkflowsArchivalFiltersDates from '../domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates';
+import { type DomainWorkflowsArchivalFiltersDatesValue } from '../domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.types';
+import DomainWorkflowsArchivalFiltersStatus from '../domain-workflows-archival-filters-status/domain-workflows-archival-filters-status';
+import { type DomainWorkflowsArchivalFiltersStatusValue } from '../domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.types';
const domainWorkflowsArchivalFiltersConfig: [
PageFilterConfig<
typeof domainPageQueryParamsConfig,
- DomainWorkflowsFiltersStatusValue
+ DomainWorkflowsArchivalFiltersStatusValue
>,
PageFilterConfig<
typeof domainPageQueryParamsConfig,
- DomainWorkflowsFiltersDatesValue
+ DomainWorkflowsArchivalFiltersDatesValue
>,
] = [
{
id: 'status',
- getValue: (v) => ({ status: v.statusArchival }),
- formatValue: (v) => ({ statusArchival: v.status }),
- component: DomainWorkflowsFiltersStatus,
+ getValue: (v) => ({ statusArchival: v.statusArchival }),
+ formatValue: (v) => ({ statusArchival: v.statusArchival }),
+ component: DomainWorkflowsArchivalFiltersStatus,
},
{
id: 'dates',
getValue: (v) => ({
- timeRangeStart: v.timeRangeStartArchival,
- timeRangeEnd: v.timeRangeEndArchival,
+ timeRangeStartArchival: v.timeRangeStartArchival,
+ timeRangeEndArchival: v.timeRangeEndArchival,
}),
formatValue: (v) => ({
- timeRangeStartArchival: v.timeRangeStart?.toISOString(),
- timeRangeEndArchival: v.timeRangeEnd?.toISOString(),
+ timeRangeStartArchival: v.timeRangeStartArchival?.toISOString(),
+ timeRangeEndArchival: v.timeRangeEndArchival?.toISOString(),
}),
- component: DomainWorkflowsFiltersDates,
+ component: DomainWorkflowsArchivalFiltersDates,
},
] as const;
diff --git a/src/views/domain-workflows/domain-workflows-archival-filters-dates/__tests__/domain-workflows-archival-filters-dates.test.tsx b/src/views/domain-workflows/domain-workflows-archival-filters-dates/__tests__/domain-workflows-archival-filters-dates.test.tsx
new file mode 100644
index 00000000..5fc9a3d4
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-archival-filters-dates/__tests__/domain-workflows-archival-filters-dates.test.tsx
@@ -0,0 +1,154 @@
+import React from 'react';
+
+import { render, screen, act, fireEvent } from '@/test-utils/rtl';
+
+import {
+ mockDomainWorkflowsQueryParamsValues,
+ mockDateOverridesArchival,
+} from '../../__fixtures__/domain-workflows-query-params';
+import DomainWorkflowsArchivalFiltersDates from '../domain-workflows-archival-filters-dates';
+import { type DomainWorkflowsArchivalFiltersDatesValue } from '../domain-workflows-archival-filters-dates.types';
+
+jest.useFakeTimers().setSystemTime(new Date('2023-05-25'));
+
+jest.mock(
+ '../../domain-workflows-filters-dates/domain-workflows-filters-dates.constants',
+ () => ({
+ ...jest.requireActual(
+ '../../domain-workflows-filters-dates/domain-workflows-filters-dates.constants'
+ ),
+ DATE_FORMAT: 'dd MMM yyyy, HH:mm x',
+ })
+);
+
+describe('DomainWorkflowsArchivalFiltersDates', () => {
+ it('displays the date picker component', () => {
+ setup({});
+ expect(
+ screen.getByPlaceholderText('Select time range')
+ ).toBeInTheDocument();
+ });
+
+ it('renders without errors when dates are already provided in query params', () => {
+ setup({
+ overrides: mockDateOverridesArchival,
+ });
+ expect(
+ // TODO - set timezone config for unit tests to UTC
+ screen.getByDisplayValue(
+ '23 May 2023, 00:00 +00 – 24 May 2023, 00:00 +00'
+ )
+ ).toBeInTheDocument();
+ });
+
+ it('sets query params when date is set', () => {
+ const { mockSetValue } = setup({});
+ const datePicker = screen.getByPlaceholderText('Select time range');
+ act(() => {
+ fireEvent.change(datePicker, {
+ target: { value: '13 May 2023, 00:00 +00 – 14 May 2023, 00:00 +00' },
+ });
+ });
+
+ expect(mockSetValue).toHaveBeenCalledWith({
+ timeRangeStartArchival: new Date('2023-05-13T00:00:00.000Z'),
+ timeRangeEndArchival: new Date('2023-05-14T00:00:00.000Z'),
+ });
+ });
+
+ it('resets to previous date when one date is selected and then the modal is closed', () => {
+ const { mockSetValue } = setup({
+ overrides: mockDateOverridesArchival,
+ });
+ const datePicker = screen.getByPlaceholderText('Select time range');
+
+ act(() => {
+ fireEvent.focus(datePicker);
+ });
+
+ const timeRangeStartLabel = screen.getByLabelText(
+ "Choose Saturday, May 13th 2023. It's available."
+ );
+
+ act(() => {
+ fireEvent.click(timeRangeStartLabel);
+ });
+
+ screen.getByText(
+ 'Selected date is 13 May 2023, 00:00 +00. Select the second date.'
+ );
+
+ act(() => {
+ fireEvent.keyDown(datePicker, { keyCode: 9 });
+ });
+
+ expect(datePicker).toHaveValue(
+ '23 May 2023, 00:00 +00 – 24 May 2023, 00:00 +00'
+ );
+ expect(mockSetValue).not.toHaveBeenCalled();
+ });
+
+ it('resets to empty state when one date is selected and then the modal is closed', () => {
+ const { mockSetValue } = setup({});
+ const datePicker = screen.getByPlaceholderText('Select time range');
+
+ act(() => {
+ fireEvent.focus(datePicker);
+ });
+
+ const timeRangeStartLabel = screen.getByLabelText(
+ "Choose Saturday, May 13th 2023. It's available."
+ );
+
+ act(() => {
+ fireEvent.click(timeRangeStartLabel);
+ });
+
+ screen.getByText(
+ 'Selected date is 13 May 2023, 00:00 +00. Select the second date.'
+ );
+
+ act(() => {
+ fireEvent.keyDown(datePicker, { keyCode: 9 });
+ });
+
+ expect(datePicker).toHaveValue('');
+ expect(mockSetValue).not.toHaveBeenCalled();
+ });
+
+ it('clears the date when the clear button is clicked', () => {
+ const { mockSetValue } = setup({
+ overrides: mockDateOverridesArchival,
+ });
+ const clearButton = screen.getByLabelText('Clear value');
+ act(() => {
+ fireEvent.click(clearButton);
+ });
+
+ expect(mockSetValue).toHaveBeenCalledWith({
+ timeRangeStartArchival: undefined,
+ timeRangeEndArchival: undefined,
+ });
+ });
+});
+
+function setup({
+ overrides,
+}: {
+ overrides?: DomainWorkflowsArchivalFiltersDatesValue;
+}) {
+ const mockSetValue = jest.fn();
+ render(
+
+ );
+
+ return { mockSetValue };
+}
diff --git a/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.styles.ts b/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.styles.ts
new file mode 100644
index 00000000..47848d09
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.styles.ts
@@ -0,0 +1,18 @@
+import { type Theme } from 'baseui';
+import type { FormControlOverrides } from 'baseui/form-control/types';
+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,
+};
diff --git a/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.tsx b/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.tsx
new file mode 100644
index 00000000..f86b292a
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.tsx
@@ -0,0 +1,76 @@
+'use client';
+import * as React from 'react';
+
+import { DatePicker } from 'baseui/datepicker';
+import { FormControl } from 'baseui/form-control';
+import { SIZE } from 'baseui/input';
+
+import { type PageFilterComponentProps } from '@/components/page-filters/page-filters.types';
+
+import { DATE_FORMAT } from '../domain-workflows-filters-dates/domain-workflows-filters-dates.constants';
+
+import { overrides } from './domain-workflows-archival-filters-dates.styles';
+import { type DomainWorkflowsArchivalFiltersDatesValue } from './domain-workflows-archival-filters-dates.types';
+
+export default function DomainWorkflowsArchivalFiltersDates({
+ value,
+ setValue,
+}: PageFilterComponentProps) {
+ const [dates, setDates] = React.useState>([]);
+
+ React.useEffect(() => {
+ setDates(
+ Boolean(value.timeRangeStartArchival) &&
+ Boolean(value.timeRangeEndArchival)
+ ? [value.timeRangeStartArchival, value.timeRangeEndArchival]
+ : []
+ );
+ }, [value]);
+
+ return (
+
+ {
+ if (!date || !Array.isArray(date)) {
+ return;
+ }
+ setDates(date);
+ if (date.length === 0) {
+ setValue({
+ timeRangeStartArchival: undefined,
+ timeRangeEndArchival: undefined,
+ });
+ } else if (date.length === 2) {
+ const [start, end] = date;
+ if (!start || !end) {
+ return;
+ }
+ setValue({
+ timeRangeStartArchival: start,
+ timeRangeEndArchival: end,
+ });
+ }
+ }}
+ onClose={() => {
+ if (dates.length !== 2 || dates.some((date) => !date)) {
+ setDates(
+ Boolean(value.timeRangeStartArchival) &&
+ Boolean(value.timeRangeEndArchival)
+ ? [value.timeRangeStartArchival, value.timeRangeEndArchival]
+ : []
+ );
+ }
+ }}
+ placeholder="Select time range"
+ formatString={DATE_FORMAT}
+ size={SIZE.compact}
+ quickSelect
+ range
+ clearable
+ timeSelectStart
+ timeSelectEnd
+ />
+
+ );
+}
diff --git a/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.types.ts b/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.types.ts
new file mode 100644
index 00000000..7a5588f8
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-archival-filters-dates/domain-workflows-archival-filters-dates.types.ts
@@ -0,0 +1,4 @@
+export type DomainWorkflowsArchivalFiltersDatesValue = {
+ timeRangeStartArchival: Date | undefined;
+ timeRangeEndArchival: Date | undefined;
+};
diff --git a/src/views/domain-workflows/domain-workflows-archival-filters-status/__tests__/domain-workflows-archival-filters-status.test.tsx b/src/views/domain-workflows/domain-workflows-archival-filters-status/__tests__/domain-workflows-archival-filters-status.test.tsx
new file mode 100644
index 00000000..3b310a76
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-archival-filters-status/__tests__/domain-workflows-archival-filters-status.test.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+
+import { render, screen, fireEvent, act } from '@/test-utils/rtl';
+
+import { WORKFLOW_STATUS_NAMES } from '@/views/shared/workflow-status-tag/workflow-status-tag.constants';
+
+import { mockDomainWorkflowsQueryParamsValues } from '../../__fixtures__/domain-workflows-query-params';
+import DomainWorkflowsArchivalFiltersStatus from '../domain-workflows-archival-filters-status';
+import { type DomainWorkflowsArchivalFiltersStatusValue } from '../domain-workflows-archival-filters-status.types';
+
+describe('DomainWorkflowsArchivalFiltersStatus', () => {
+ it('renders without errors', () => {
+ setup({});
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
+ });
+
+ it('displays all the options in the select component', () => {
+ setup({});
+ const selectFilter = screen.getByRole('combobox');
+ act(() => {
+ fireEvent.click(selectFilter);
+ });
+ Object.entries(WORKFLOW_STATUS_NAMES).forEach(([_, value]) =>
+ expect(screen.getByText(value)).toBeInTheDocument()
+ );
+ });
+
+ it('calls the setQueryParams function when an option is selected', () => {
+ const { mockSetValue } = setup({});
+ const selectFilter = screen.getByRole('combobox');
+ act(() => {
+ fireEvent.click(selectFilter);
+ });
+ const runningOption = screen.getByText('Running');
+ act(() => {
+ fireEvent.click(runningOption);
+ });
+ expect(mockSetValue).toHaveBeenCalledWith({
+ statusArchival: 'WORKFLOW_EXECUTION_CLOSE_STATUS_INVALID',
+ });
+ });
+
+ it('calls the setQueryParams function when the filter is cleared', () => {
+ const { mockSetValue } = setup({
+ overrides: {
+ statusArchival: 'WORKFLOW_EXECUTION_CLOSE_STATUS_FAILED',
+ },
+ });
+ const clearButton = screen.getByLabelText('Clear value');
+ act(() => {
+ fireEvent.click(clearButton);
+ });
+ expect(mockSetValue).toHaveBeenCalledWith({ statusArchival: undefined });
+ });
+});
+
+function setup({
+ overrides,
+}: {
+ overrides?: DomainWorkflowsArchivalFiltersStatusValue;
+}) {
+ const mockSetValue = jest.fn();
+ render(
+
+ );
+
+ return { mockSetValue };
+}
diff --git a/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.styles.ts b/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.styles.ts
new file mode 100644
index 00000000..d9a2ff7d
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.styles.ts
@@ -0,0 +1,18 @@
+import { type Theme } from 'baseui';
+import type { FormControlOverrides } from 'baseui/form-control/types';
+import { type StyleObject } from 'styletron-react';
+
+export const overrides = {
+ selectFormControl: {
+ Label: {
+ style: ({ $theme }: { $theme: Theme }): StyleObject => ({
+ ...$theme.typography.LabelXSmall,
+ }),
+ },
+ ControlContainer: {
+ style: (): StyleObject => ({
+ margin: '0px',
+ }),
+ },
+ } satisfies FormControlOverrides,
+};
diff --git a/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.tsx b/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.tsx
new file mode 100644
index 00000000..71c2d7c7
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.tsx
@@ -0,0 +1,42 @@
+'use client';
+import React from 'react';
+
+import { FormControl } from 'baseui/form-control';
+import { Select, SIZE } from 'baseui/select';
+
+import { type PageFilterComponentProps } from '@/components/page-filters/page-filters.types';
+import { type WorkflowStatus } from '@/views/shared/workflow-status-tag/workflow-status-tag.types';
+
+import { WORKFLOW_STATUS_OPTIONS } from '../domain-workflows-filters-status/domain-workflows-filters-status.constants';
+
+import { overrides } from './domain-workflows-archival-filters-status.styles';
+import { type DomainWorkflowsArchivalFiltersStatusValue } from './domain-workflows-archival-filters-status.types';
+
+export default function DomainWorkflowsArchivalFiltersStatus({
+ value,
+ setValue,
+}: PageFilterComponentProps) {
+ const statusOptionValue = WORKFLOW_STATUS_OPTIONS.filter(
+ (option) => option.id === value.statusArchival
+ );
+
+ return (
+
+
+ );
+}
diff --git a/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.types.ts b/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.types.ts
new file mode 100644
index 00000000..3b76d6eb
--- /dev/null
+++ b/src/views/domain-workflows/domain-workflows-archival-filters-status/domain-workflows-archival-filters-status.types.ts
@@ -0,0 +1,5 @@
+import { type WorkflowStatus } from '@/views/shared/workflow-status-tag/workflow-status-tag.types';
+
+export type DomainWorkflowsArchivalFiltersStatusValue = {
+ statusArchival: WorkflowStatus | undefined;
+};
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 46c27751..3a567f4d 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,6 +9,7 @@ import PageFiltersSearch from '@/components/page-filters/page-filters-search/pag
import PageFiltersToggle from '@/components/page-filters/page-filters-toggle/page-filters-toggle';
import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config';
+import domainWorkflowsArchivalFiltersConfig from '../config/domain-workflows-archival-filters.config';
import domainWorkflowsFiltersConfig from '../config/domain-workflows-filters.config';
import DOMAIN_WORKFLOWS_SEARCH_DEBOUNCE_MS from '../config/domain-workflows-search-debounce-ms.config';
import DomainWorkflowsQueryInput from '../domain-workflows-query-input/domain-workflows-query-input';
@@ -32,6 +33,9 @@ export default function DomainWorkflowsHeader({
pageQueryParamsConfig: domainPageQueryParamsConfig,
});
+ // TODO @adhitya.mamallan - see if there's a better way to separate the domain workflows view
+ // from directly depending on query params, since using the isArchival flag in multiple places
+ // can get unmaintainable
const { inputType, query } = getDomainWorkflowsQueryParamsValues({
queryParams,
isArchival,
@@ -40,6 +44,7 @@ export default function DomainWorkflowsHeader({
const { refetch, isFetching } = useListWorkflows({
domain,
cluster,
+ isArchival,
});
return (
@@ -50,7 +55,7 @@ export default function DomainWorkflowsHeader({
onChange={({ activeKey }) => {
setQueryParams(
{
- [isArchival ? 'inputType' : 'inputTypeArchival']:
+ [isArchival ? 'inputTypeArchival' : 'inputType']:
activeKey === 'query' ? 'query' : 'search',
},
{ replace: false, pageRerender: true }
@@ -74,7 +79,7 @@ export default function DomainWorkflowsHeader({
value={query}
setValue={(v) =>
setQueryParams({
- [isArchival ? 'query' : 'queryArchival']: v,
+ [isArchival ? 'queryArchival' : 'query']: v,
})
}
refetchQuery={refetch}
@@ -84,7 +89,7 @@ export default function DomainWorkflowsHeader({
@@ -100,7 +105,11 @@ export default function DomainWorkflowsHeader({
{inputType === 'search' && areFiltersShown && (
;