From d4ad3a8356f358a60db43a572db9074b02f540fe Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Fri, 22 Nov 2024 19:18:33 +0530 Subject: [PATCH 1/6] Add changes to table --- .../domain-workflows-table-query.test.tsx | 193 ++++++++++++++++++ .../domain-workflows-table-query.styles.ts | 10 + .../domain-workflows-table-query.tsx | 69 +++++++ .../get-query-error-panel-props.test.ts | 26 +++ .../helpers/get-query-error-panel-props.ts | 19 ++ .../domain-workflows-table-search.test.tsx | 193 ++++++++++++++++++ .../domain-workflows-table-search.styles.ts | 10 + .../domain-workflows-table-search.tsx | 95 +++++++++ .../get-search-error-panel-props.test.ts} | 14 +- .../helpers/get-search-error-panel-props.ts} | 5 +- .../__tests__/domain-workflows-table.test.tsx | 180 +++------------- .../domain-workflows-table.styles.ts | 6 - .../domain-workflows-table.tsx | 86 +------- .../domain-workflows-table.types.ts | 1 + 14 files changed, 660 insertions(+), 247 deletions(-) create mode 100644 src/views/domain-workflows/domain-workflows-table-query/__tests__/domain-workflows-table-query.test.tsx create mode 100644 src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.styles.ts create mode 100644 src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.tsx create mode 100644 src/views/domain-workflows/domain-workflows-table-query/helpers/__tests__/get-query-error-panel-props.test.ts create mode 100644 src/views/domain-workflows/domain-workflows-table-query/helpers/get-query-error-panel-props.ts create mode 100644 src/views/domain-workflows/domain-workflows-table-search/__tests__/domain-workflows-table-search.test.tsx create mode 100644 src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.styles.ts create mode 100644 src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.tsx rename src/views/domain-workflows/{domain-workflows-table/helpers/__tests__/get-workflows-error-panel-props.test.ts => domain-workflows-table-search/helpers/__tests__/get-search-error-panel-props.test.ts} (72%) rename src/views/domain-workflows/{domain-workflows-table/helpers/get-workflows-error-panel-props.ts => domain-workflows-table-search/helpers/get-search-error-panel-props.ts} (82%) diff --git a/src/views/domain-workflows/domain-workflows-table-query/__tests__/domain-workflows-table-query.test.tsx b/src/views/domain-workflows/domain-workflows-table-query/__tests__/domain-workflows-table-query.test.tsx new file mode 100644 index 000000000..bcddefea3 --- /dev/null +++ b/src/views/domain-workflows/domain-workflows-table-query/__tests__/domain-workflows-table-query.test.tsx @@ -0,0 +1,193 @@ +import { HttpResponse } from 'msw'; + +import { render, screen, userEvent } from '@/test-utils/rtl'; + +import { type ListWorkflowsResponse } from '@/route-handlers/list-workflows/list-workflows.types'; + +import type { Props as MSWMocksHandlersProps } from '../../../../test-utils/msw-mock-handlers/msw-mock-handlers.types'; +import { mockDomainWorkflowsQueryParamsValues } from '../../__fixtures__/domain-workflows-query-params'; +import { type Props as EndMessageProps } from '../../domain-workflows-table-end-message/domain-workflows-table-end-message.types'; +import DomainWorkflowsTableQuery from '../domain-workflows-table-query'; + +jest.mock('@/components/error-panel/error-panel', () => + jest.fn(({ message }: { message: string }) =>
{message}
) +); + +jest.mock( + '@/components/section-loading-indicator/section-loading-indicator', + () => jest.fn(() =>
Loading...
) +); + +jest.mock('../helpers/get-query-error-panel-props', () => + jest.fn().mockImplementation(() => ({ + message: 'Error loading workflows', + })) +); + +jest.mock( + '../../domain-workflows-table-end-message/domain-workflows-table-end-message', + () => + jest.fn((props: EndMessageProps) => + props.hasNextPage ? ( + + ) : ( +
No workflows
+ ) + ) +); + +jest.mock('query-string', () => ({ + stringifyUrl: jest.fn( + () => '/api/domains/mock-domain/mock-cluster/workflows' + ), +})); + +const mockSetQueryParams = jest.fn(); +jest.mock('@/hooks/use-page-query-params/use-page-query-params', () => + jest.fn(() => [mockDomainWorkflowsQueryParamsValues, mockSetQueryParams]) +); + +describe(DomainWorkflowsTableQuery.name, () => { + it('renders workflows without error', async () => { + const { user } = setup({}); + expect(await screen.findByText('Loading...')).toBeInTheDocument(); + + expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-0-${index}`) + ).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('mock-end-message')); + + expect(await screen.findByText('No workflows')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-1-${index}`) + ).toBeInTheDocument(); + }); + }); + + it('renders error panel if the initial call fails', async () => { + setup({ errorCase: 'initial-fetch-error' }); + + expect( + await screen.findByText('Error loading workflows') + ).toBeInTheDocument(); + }); + + it('renders empty table if no workflows are found', async () => { + setup({ errorCase: 'no-workflows' }); + + expect(await screen.findByText('No workflows')).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(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-0-${index}`) + ).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('mock-end-message')); + + expect( + await screen.findByText('Mock end message: Error') + ).toBeInTheDocument(); + + await user.click(screen.getByTestId('mock-end-message')); + + expect(await screen.findByText('No workflows')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-1-${index}`) + ).toBeInTheDocument(); + }); + }); +}); + +function setup({ + errorCase, +}: { + errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error' | 'no-workflows'; +}) { + const pages = generateWorkflowPages(2); + 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) { + 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'], + } + ); + + return { user }; +} + +// TODO @adhitya.mamallan - Explore using fakerjs.dev for cases like this +function generateWorkflowPages(count: number): Array { + const pages = Array.from( + { length: count }, + (_, pageIndex): ListWorkflowsResponse => ({ + workflows: Array.from({ length: 10 }, (_, index) => ({ + workflowID: `mock-workflow-id-${pageIndex}-${index}`, + runID: `mock-run-id-${pageIndex}-${index}`, + workflowName: `mock-workflow-name-${pageIndex}-${index}`, + status: 'WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED', + startTime: 1684800000000, + closeTime: count > 5 ? 1684886400000 : undefined, + })), + nextPage: `${pageIndex + 1}`, + }) + ); + + pages[pages.length - 1].nextPage = ''; + return pages; +} diff --git a/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.styles.ts b/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.styles.ts new file mode 100644 index 000000000..ee5a55dcc --- /dev/null +++ b/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.styles.ts @@ -0,0 +1,10 @@ +import { styled as createStyled } from 'baseui'; + +export const styled = { + ErrorPanelContainer: createStyled('div', ({ $theme }) => ({ + padding: `${$theme.sizing.scale1200} 0px`, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + })), +}; diff --git a/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.tsx b/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.tsx new file mode 100644 index 000000000..ba8388303 --- /dev/null +++ b/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.tsx @@ -0,0 +1,69 @@ +'use client'; +import React from 'react'; + +import ErrorPanel from '@/components/error-panel/error-panel'; +import SectionLoadingIndicator from '@/components/section-loading-indicator/section-loading-indicator'; +import Table from '@/components/table/table'; +import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params'; +import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config'; + +import domainWorkflowsTableConfig from '../config/domain-workflows-table.config'; +import { type Props } from '../domain-workflows-table/domain-workflows-table.types'; +import DomainWorkflowsTableEndMessage from '../domain-workflows-table-end-message/domain-workflows-table-end-message'; +import useListWorkflows from '../hooks/use-list-workflows'; + +import { styled } from './domain-workflows-table-query.styles'; +import getQueryErrorPanelProps from './helpers/get-query-error-panel-props'; + +export default function DomainWorkflowsTableQuery({ domain, cluster }: Props) { + const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig); + + const { + workflows, + error, + isLoading, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + refetch, + } = useListWorkflows({ + domain, + cluster, + inputType: queryParams.inputType, + query: queryParams.query, + }); + + if (isLoading) { + return ; + } + + if (error && workflows.length === 0) { + return ( + + + + ); + } + + return ( + 0} + columns={domainWorkflowsTableConfig.map((columnConfig) => ({ + ...columnConfig, + sortable: false, + }))} + // TODO @adhitya.mamallan - remove this after 3.5 is merged + onSort={() => {}} + endMessage={ + 0} + error={error} + fetchNextPage={fetchNextPage} + hasNextPage={hasNextPage} + isFetchingNextPage={isFetchingNextPage} + /> + } + /> + ); +} diff --git a/src/views/domain-workflows/domain-workflows-table-query/helpers/__tests__/get-query-error-panel-props.test.ts b/src/views/domain-workflows/domain-workflows-table-query/helpers/__tests__/get-query-error-panel-props.test.ts new file mode 100644 index 000000000..eb33113dc --- /dev/null +++ b/src/views/domain-workflows/domain-workflows-table-query/helpers/__tests__/get-query-error-panel-props.test.ts @@ -0,0 +1,26 @@ +import { RequestError } from '@/utils/request/request-error'; + +import getQueryErrorPanelProps from '../get-query-error-panel-props'; + +describe(getQueryErrorPanelProps.name, () => { + it('returns default error panel props for regular error', () => { + expect( + getQueryErrorPanelProps({ + error: new RequestError('Test error', 500), + }) + ).toEqual({ + message: 'Failed to fetch workflows', + actions: [{ kind: 'retry', label: 'Retry' }], + }); + }); + + it('returns query error message directly for bad request error', () => { + expect( + getQueryErrorPanelProps({ + error: new RequestError('Test error', 400), + }) + ).toEqual({ + message: 'Error in query: Test error', + }); + }); +}); diff --git a/src/views/domain-workflows/domain-workflows-table-query/helpers/get-query-error-panel-props.ts b/src/views/domain-workflows/domain-workflows-table-query/helpers/get-query-error-panel-props.ts new file mode 100644 index 000000000..97b75a2a4 --- /dev/null +++ b/src/views/domain-workflows/domain-workflows-table-query/helpers/get-query-error-panel-props.ts @@ -0,0 +1,19 @@ +import { type Props as ErrorPanelProps } from '@/components/error-panel/error-panel.types'; +import { type RequestError } from '@/utils/request/request-error'; + +export default function getQueryErrorPanelProps({ + error, +}: { + error: RequestError; +}): ErrorPanelProps { + if (error.status === 400) { + return { + message: 'Error in query: ' + error.message, + }; + } + + return { + message: 'Failed to fetch workflows', + actions: [{ kind: 'retry', label: 'Retry' }], + }; +} diff --git a/src/views/domain-workflows/domain-workflows-table-search/__tests__/domain-workflows-table-search.test.tsx b/src/views/domain-workflows/domain-workflows-table-search/__tests__/domain-workflows-table-search.test.tsx new file mode 100644 index 000000000..052e73889 --- /dev/null +++ b/src/views/domain-workflows/domain-workflows-table-search/__tests__/domain-workflows-table-search.test.tsx @@ -0,0 +1,193 @@ +import { HttpResponse } from 'msw'; + +import { render, screen, userEvent } from '@/test-utils/rtl'; + +import { type ListWorkflowsResponse } from '@/route-handlers/list-workflows/list-workflows.types'; + +import type { Props as MSWMocksHandlersProps } from '../../../../test-utils/msw-mock-handlers/msw-mock-handlers.types'; +import { mockDomainWorkflowsQueryParamsValues } from '../../__fixtures__/domain-workflows-query-params'; +import { type Props as EndMessageProps } from '../../domain-workflows-table-end-message/domain-workflows-table-end-message.types'; +import DomainWorkflowsTableSearch from '../domain-workflows-table-search'; + +jest.mock('@/components/error-panel/error-panel', () => + jest.fn(({ message }: { message: string }) =>
{message}
) +); + +jest.mock( + '@/components/section-loading-indicator/section-loading-indicator', + () => jest.fn(() =>
Loading...
) +); + +jest.mock('../helpers/get-search-error-panel-props', () => + jest.fn().mockImplementation(({ error }) => ({ + message: error ? 'Error loading workflows' : 'No workflows found', + })) +); + +jest.mock( + '../../domain-workflows-table-end-message/domain-workflows-table-end-message', + () => + jest.fn((props: EndMessageProps) => + props.hasNextPage ? ( + + ) : ( +
No workflows
+ ) + ) +); + +jest.mock('query-string', () => ({ + stringifyUrl: jest.fn( + () => '/api/domains/mock-domain/mock-cluster/workflows' + ), +})); + +const mockSetQueryParams = jest.fn(); +jest.mock('@/hooks/use-page-query-params/use-page-query-params', () => + jest.fn(() => [mockDomainWorkflowsQueryParamsValues, mockSetQueryParams]) +); + +describe(DomainWorkflowsTableSearch.name, () => { + it('renders workflows without error', async () => { + const { user } = setup({}); + expect(await screen.findByText('Loading...')).toBeInTheDocument(); + + expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-0-${index}`) + ).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('mock-end-message')); + + expect(await screen.findByText('No workflows')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-1-${index}`) + ).toBeInTheDocument(); + }); + }); + + it('renders error panel if the initial call fails', async () => { + setup({ errorCase: 'initial-fetch-error' }); + + expect( + await screen.findByText('Error loading workflows') + ).toBeInTheDocument(); + }); + + it('renders error panel if no workflows are found', async () => { + setup({ errorCase: 'no-workflows' }); + + 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(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-0-${index}`) + ).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('mock-end-message')); + + expect( + await screen.findByText('Mock end message: Error') + ).toBeInTheDocument(); + + await user.click(screen.getByTestId('mock-end-message')); + + expect(await screen.findByText('No workflows')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-1-${index}`) + ).toBeInTheDocument(); + }); + }); +}); + +function setup({ + errorCase, +}: { + errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error' | 'no-workflows'; +}) { + const pages = generateWorkflowPages(2); + 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) { + 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'], + } + ); + + return { user }; +} + +// TODO @adhitya.mamallan - Explore using fakerjs.dev for cases like this +function generateWorkflowPages(count: number): Array { + const pages = Array.from( + { length: count }, + (_, pageIndex): ListWorkflowsResponse => ({ + workflows: Array.from({ length: 10 }, (_, index) => ({ + workflowID: `mock-workflow-id-${pageIndex}-${index}`, + runID: `mock-run-id-${pageIndex}-${index}`, + workflowName: `mock-workflow-name-${pageIndex}-${index}`, + status: 'WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED', + startTime: 1684800000000, + closeTime: count > 5 ? 1684886400000 : undefined, + })), + nextPage: `${pageIndex + 1}`, + }) + ); + + pages[pages.length - 1].nextPage = ''; + return pages; +} diff --git a/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.styles.ts b/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.styles.ts new file mode 100644 index 000000000..ee5a55dcc --- /dev/null +++ b/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.styles.ts @@ -0,0 +1,10 @@ +import { styled as createStyled } from 'baseui'; + +export const styled = { + ErrorPanelContainer: createStyled('div', ({ $theme }) => ({ + padding: `${$theme.sizing.scale1200} 0px`, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + })), +}; diff --git a/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.tsx b/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.tsx new file mode 100644 index 000000000..26de7737a --- /dev/null +++ b/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.tsx @@ -0,0 +1,95 @@ +'use client'; +import React from 'react'; + +import ErrorPanel from '@/components/error-panel/error-panel'; +import SectionLoadingIndicator from '@/components/section-loading-indicator/section-loading-indicator'; +import Table from '@/components/table/table'; +import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params'; +import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config'; + +import domainWorkflowsTableConfig from '../config/domain-workflows-table.config'; +import { type Props } from '../domain-workflows-table/domain-workflows-table.types'; +import DomainWorkflowsTableEndMessage from '../domain-workflows-table-end-message/domain-workflows-table-end-message'; +import getNextSortOrder from '../helpers/get-next-sort-order'; +import useListWorkflows from '../hooks/use-list-workflows'; + +import { styled } from './domain-workflows-table-search.styles'; +import getSearchErrorPanelProps from './helpers/get-search-error-panel-props'; + +export default function DomainWorkflowsTableSearch({ domain, cluster }: Props) { + const [queryParams, setQueryParams] = usePageQueryParams( + domainPageQueryParamsConfig + ); + + const { + workflows, + error, + isLoading, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + refetch, + } = useListWorkflows({ + domain, + cluster, + inputType: queryParams.inputType, + search: queryParams.search, + status: queryParams.status, + sortColumn: queryParams.sortColumn, + sortOrder: queryParams.sortOrder, + timeRangeStart: queryParams.timeRangeStart?.toISOString(), + timeRangeEnd: queryParams.timeRangeEnd?.toISOString(), + }); + + if (isLoading) { + return ; + } + + if (workflows.length === 0) { + const errorPanelProps = getSearchErrorPanelProps({ + error, + areSearchParamsAbsent: + !queryParams.search && + !queryParams.status && + !queryParams.timeRangeStart && + !queryParams.timeRangeEnd, + }); + + if (errorPanelProps) { + return ( + + + + ); + } + } + + return ( +
0} + onSort={(column) => { + setQueryParams({ + sortColumn: column, + sortOrder: getNextSortOrder({ + currentColumn: queryParams.sortColumn, + nextColumn: column, + currentSortOrder: queryParams.sortOrder, + }), + }); + }} + sortColumn={queryParams.sortColumn} + sortOrder={queryParams.sortOrder} + endMessage={ + 0} + error={error} + fetchNextPage={fetchNextPage} + hasNextPage={hasNextPage} + isFetchingNextPage={isFetchingNextPage} + /> + } + /> + ); +} diff --git a/src/views/domain-workflows/domain-workflows-table/helpers/__tests__/get-workflows-error-panel-props.test.ts b/src/views/domain-workflows/domain-workflows-table-search/helpers/__tests__/get-search-error-panel-props.test.ts similarity index 72% rename from src/views/domain-workflows/domain-workflows-table/helpers/__tests__/get-workflows-error-panel-props.test.ts rename to src/views/domain-workflows/domain-workflows-table-search/helpers/__tests__/get-search-error-panel-props.test.ts index df7331398..cb45d4098 100644 --- a/src/views/domain-workflows/domain-workflows-table/helpers/__tests__/get-workflows-error-panel-props.test.ts +++ b/src/views/domain-workflows/domain-workflows-table-search/helpers/__tests__/get-search-error-panel-props.test.ts @@ -1,10 +1,12 @@ -import getWorkflowsErrorPanelProps from '../get-workflows-error-panel-props'; +import { RequestError } from '@/utils/request/request-error'; -describe(getWorkflowsErrorPanelProps.name, () => { +import getSearchErrorPanelProps from '../get-search-error-panel-props'; + +describe(getSearchErrorPanelProps.name, () => { it('returns default error panel props for regular error', () => { expect( - getWorkflowsErrorPanelProps({ - error: new Error('Test error'), + getSearchErrorPanelProps({ + error: new RequestError('Test error', 500), areSearchParamsAbsent: false, }) ).toEqual({ @@ -15,7 +17,7 @@ describe(getWorkflowsErrorPanelProps.name, () => { it('returns "not found" error panel props when search params are absent', () => { expect( - getWorkflowsErrorPanelProps({ + getSearchErrorPanelProps({ error: null, areSearchParamsAbsent: true, }) @@ -34,7 +36,7 @@ describe(getWorkflowsErrorPanelProps.name, () => { it('returns undefined in all other cases', () => { expect( - getWorkflowsErrorPanelProps({ + getSearchErrorPanelProps({ error: null, areSearchParamsAbsent: false, }) diff --git a/src/views/domain-workflows/domain-workflows-table/helpers/get-workflows-error-panel-props.ts b/src/views/domain-workflows/domain-workflows-table-search/helpers/get-search-error-panel-props.ts similarity index 82% rename from src/views/domain-workflows/domain-workflows-table/helpers/get-workflows-error-panel-props.ts rename to src/views/domain-workflows/domain-workflows-table-search/helpers/get-search-error-panel-props.ts index ea10e3c17..ebed47328 100644 --- a/src/views/domain-workflows/domain-workflows-table/helpers/get-workflows-error-panel-props.ts +++ b/src/views/domain-workflows/domain-workflows-table-search/helpers/get-search-error-panel-props.ts @@ -1,10 +1,11 @@ import { type Props as ErrorPanelProps } from '@/components/error-panel/error-panel.types'; +import { type RequestError } from '@/utils/request/request-error'; -export default function getWorkflowsErrorPanelProps({ +export default function getSearchErrorPanelProps({ error, areSearchParamsAbsent, }: { - error: Error | null; + error: RequestError | null; areSearchParamsAbsent: boolean; }): ErrorPanelProps | undefined { if (error) { 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 bfe08180b..c2d91e004 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 @@ -1,177 +1,45 @@ -import { HttpResponse } from 'msw'; +import { render, screen } from '@/test-utils/rtl'; -import { render, screen, userEvent } from '@/test-utils/rtl'; +import * as usePageQueryParamsModule from '@/hooks/use-page-query-params/use-page-query-params'; -import { type ListWorkflowsResponse } from '@/route-handlers/list-workflows/list-workflows.types'; - -import type { Props as MSWMocksHandlersProps } from '../../../../test-utils/msw-mock-handlers/msw-mock-handlers.types'; import { mockDomainWorkflowsQueryParamsValues } from '../../__fixtures__/domain-workflows-query-params'; -import { type Props as EndMessageProps } from '../../domain-workflows-table-end-message/domain-workflows-table-end-message.types'; import DomainWorkflowsTable from '../domain-workflows-table'; -jest.mock('@/components/error-panel/error-panel', () => - jest.fn(({ message }: { message: string }) =>
{message}
) -); - -jest.mock('../helpers/get-workflows-error-panel-props', () => - jest.fn().mockImplementation(({ error }) => ({ - message: error ? 'Error loading workflows' : 'No workflows found', - })) +jest.mock( + '../../domain-workflows-table-search/domain-workflows-table-search', + () => jest.fn(() =>
Search results table
) ); jest.mock( - '../../domain-workflows-table-end-message/domain-workflows-table-end-message', - () => - jest.fn((props: EndMessageProps) => ( - - )) + '../../domain-workflows-table-query/domain-workflows-table-query', + () => jest.fn(() =>
Query results table
) ); -jest.mock('query-string', () => ({ - stringifyUrl: jest.fn( - () => '/api/domains/mock-domain/mock-cluster/workflows' - ), -})); - -const mockSetQueryParams = jest.fn(); jest.mock('@/hooks/use-page-query-params/use-page-query-params', () => - jest.fn(() => [mockDomainWorkflowsQueryParamsValues, mockSetQueryParams]) + jest.fn(() => [mockDomainWorkflowsQueryParamsValues, jest.fn()]) ); describe(DomainWorkflowsTable.name, () => { - it('renders workflows without error', async () => { - const { user } = setup({}); - - expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-0-${index}`) - ).toBeInTheDocument(); - }); + it('renders search table by default', async () => { + render( + + ); - await user.click(screen.getByTestId('mock-end-message')); - - expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-1-${index}`) - ).toBeInTheDocument(); - }); + expect(await screen.findByText('Search results table')).toBeInTheDocument(); }); - it('renders error panel if the initial call fails', async () => { - setup({ errorCase: 'initial-fetch-error' }); - - expect( - await screen.findByText('Error loading workflows') - ).toBeInTheDocument(); - }); + it('renders query table if inputType is query', async () => { + jest + .spyOn(usePageQueryParamsModule, 'default') + .mockReturnValue([ + { ...mockDomainWorkflowsQueryParamsValues, inputType: 'query' }, + jest.fn(), + ]); - it('renders error panel if no workflows are found', async () => { - setup({ errorCase: 'no-workflows' }); + render( + + ); - 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(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-0-${index}`) - ).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId('mock-end-message')); - - expect( - await screen.findByText('Mock end message: Error') - ).toBeInTheDocument(); - - await user.click(screen.getByTestId('mock-end-message')); - - expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-1-${index}`) - ).toBeInTheDocument(); - }); + expect(await screen.findByText('Query results table')).toBeInTheDocument(); }); }); - -function setup({ - errorCase, -}: { - errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error' | 'no-workflows'; -}) { - const pages = generateWorkflowPages(2); - 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) { - 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'], - }); - - return { user }; -} - -// TODO @adhitya.mamallan - Explore using fakerjs.dev for cases like this -function generateWorkflowPages(count: number): Array { - const pages = Array.from( - { length: count }, - (_, pageIndex): ListWorkflowsResponse => ({ - workflows: Array.from({ length: 10 }, (_, index) => ({ - workflowID: `mock-workflow-id-${pageIndex}-${index}`, - runID: `mock-run-id-${pageIndex}-${index}`, - workflowName: `mock-workflow-name-${pageIndex}-${index}`, - status: 'WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED', - startTime: 1684800000000, - closeTime: count > 5 ? 1684886400000 : undefined, - })), - nextPage: `${pageIndex + 1}`, - }) - ); - - pages[pages.length - 1].nextPage = ''; - return pages; -} diff --git a/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.styles.ts b/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.styles.ts index d536b2fdf..9f63ba324 100644 --- a/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.styles.ts +++ b/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.styles.ts @@ -4,10 +4,4 @@ export const styled = { TableContainer: createStyled('div', { overflowX: 'auto', }), - ErrorPanelContainer: createStyled('div', ({ $theme }) => ({ - padding: `${$theme.sizing.scale1200} 0px`, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - })), }; 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 7da0e6956..c7d01db28 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 @@ -1,93 +1,25 @@ 'use client'; import React from 'react'; -import ErrorPanel from '@/components/error-panel/error-panel'; -import SectionLoadingIndicator from '@/components/section-loading-indicator/section-loading-indicator'; -import Table from '@/components/table/table'; import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params'; import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config'; -import domainWorkflowsTableConfig from '../config/domain-workflows-table.config'; -import DomainWorkflowsTableEndMessage from '../domain-workflows-table-end-message/domain-workflows-table-end-message'; -import getNextSortOrder from '../helpers/get-next-sort-order'; -import useListWorkflows from '../hooks/use-list-workflows'; +import DomainWorkflowsTableQuery from '../domain-workflows-table-query/domain-workflows-table-query'; +import DomainWorkflowsTableSearch from '../domain-workflows-table-search/domain-workflows-table-search'; import { styled } from './domain-workflows-table.styles'; import { type Props } from './domain-workflows-table.types'; -import getWorkflowsErrorPanelProps from './helpers/get-workflows-error-panel-props'; -export default function DomainWorkflowsTable({ domain, cluster }: Props) { - const [queryParams, setQueryParams] = usePageQueryParams( - domainPageQueryParamsConfig - ); - - const { - workflows, - error, - isLoading, - hasNextPage, - fetchNextPage, - isFetchingNextPage, - refetch, - } = useListWorkflows({ - domain, - cluster, - inputType: queryParams.inputType, - search: queryParams.search, - status: queryParams.status, - sortColumn: queryParams.sortColumn, - sortOrder: queryParams.sortOrder, - timeRangeStart: queryParams.timeRangeStart?.toISOString(), - timeRangeEnd: queryParams.timeRangeEnd?.toISOString(), - }); - - if (isLoading) { - return ; - } - - if (workflows.length === 0) { - const errorPanelProps = getWorkflowsErrorPanelProps({ - error, - areSearchParamsAbsent: - !queryParams.search && - !queryParams.status && - !queryParams.timeRangeStart && - !queryParams.timeRangeEnd, - }); - - if (errorPanelProps) { - return ; - } - } +export default function DomainWorkflowsTable(props: Props) { + const [{ inputType }] = usePageQueryParams(domainPageQueryParamsConfig); return ( -
0} - onSort={(column) => { - setQueryParams({ - sortColumn: column, - sortOrder: getNextSortOrder({ - currentColumn: queryParams.sortColumn, - nextColumn: column, - currentSortOrder: queryParams.sortOrder, - }), - }); - }} - sortColumn={queryParams.sortColumn} - sortOrder={queryParams.sortOrder} - endMessage={ - 0} - error={error} - fetchNextPage={fetchNextPage} - hasNextPage={hasNextPage} - isFetchingNextPage={isFetchingNextPage} - /> - } - /> + {inputType === '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..9ac077ebc 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,5 @@ export type Props = { domain: string; cluster: string; + // TODO @adhitya.mamallan - when working on Archival, add a flag here }; From c10c1603567b417019ae5f72a3b6c6de352393d2 Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Mon, 25 Nov 2024 09:41:56 +0000 Subject: [PATCH 2/6] Change impl --- .../domain-workflows-header.test.tsx | 26 ++- .../domain-workflows-header.tsx | 7 +- .../domain-workflows-header.types.ts | 5 + .../domain-workflows-query-input.test.tsx | 12 +- .../domain-workflows-query-input.tsx | 10 +- .../domain-workflows-query-input.types.ts | 1 + .../domain-workflows-table-query.test.tsx | 193 ---------------- .../domain-workflows-table-query.styles.ts | 10 - .../domain-workflows-table-query.tsx | 69 ------ .../get-query-error-panel-props.test.ts | 26 --- .../helpers/get-query-error-panel-props.ts | 19 -- .../domain-workflows-table-search.test.tsx | 193 ---------------- .../domain-workflows-table-search.styles.ts | 10 - .../domain-workflows-table-search.tsx | 95 -------- .../__tests__/domain-workflows-table.test.tsx | 215 ++++++++++++++++-- .../domain-workflows-table.styles.ts | 6 + .../domain-workflows-table.tsx | 95 +++++++- .../domain-workflows-table.types.ts | 1 - .../get-workflows-error-panel-props.test.ts} | 25 +- .../get-workflows-error-panel-props.ts} | 12 +- .../domain-workflows/domain-workflows.tsx | 2 +- .../domain-workflows.types.ts | 10 +- .../hooks/use-list-workflows.ts | 21 +- 23 files changed, 396 insertions(+), 667 deletions(-) delete mode 100644 src/views/domain-workflows/domain-workflows-table-query/__tests__/domain-workflows-table-query.test.tsx delete mode 100644 src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.styles.ts delete mode 100644 src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.tsx delete mode 100644 src/views/domain-workflows/domain-workflows-table-query/helpers/__tests__/get-query-error-panel-props.test.ts delete mode 100644 src/views/domain-workflows/domain-workflows-table-query/helpers/get-query-error-panel-props.ts delete mode 100644 src/views/domain-workflows/domain-workflows-table-search/__tests__/domain-workflows-table-search.test.tsx delete mode 100644 src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.styles.ts delete mode 100644 src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.tsx rename src/views/domain-workflows/{domain-workflows-table-search/helpers/__tests__/get-search-error-panel-props.test.ts => domain-workflows-table/helpers/__tests__/get-workflows-error-panel-props.test.ts} (60%) rename src/views/domain-workflows/{domain-workflows-table-search/helpers/get-search-error-panel-props.ts => domain-workflows-table/helpers/get-workflows-error-panel-props.ts} (67%) diff --git a/src/views/domain-workflows/domain-workflows-header/__tests__/domain-workflows-header.test.tsx b/src/views/domain-workflows/domain-workflows-header/__tests__/domain-workflows-header.test.tsx index fc587dcde..807b34adc 100644 --- a/src/views/domain-workflows/domain-workflows-header/__tests__/domain-workflows-header.test.tsx +++ b/src/views/domain-workflows/domain-workflows-header/__tests__/domain-workflows-header.test.tsx @@ -41,16 +41,26 @@ jest.mock('@/components/page-filters/hooks/use-page-filters', () => })) ); +jest.mock('../../hooks/use-list-workflows', () => + jest.fn(() => ({ + refetch: jest.fn(), + })) +); + describe(DomainWorkflowsHeader.name, () => { it('renders segmented control', async () => { - render(); + render( + + ); expect(await screen.findByText('Search')).toBeInTheDocument(); expect(await screen.findByText('Query')).toBeInTheDocument(); }); it('renders page search and filters button when input type is search', async () => { - render(); + render( + + ); expect(await screen.findByText('Filter search')).toBeInTheDocument(); expect(await screen.findByText('Filter toggle')).toBeInTheDocument(); @@ -58,7 +68,9 @@ describe(DomainWorkflowsHeader.name, () => { it('renders page filters when filter toggle is clicked', async () => { const user = userEvent.setup(); - render(); + render( + + ); const filterToggle = await screen.findByText('Filter toggle'); await user.click(filterToggle); @@ -77,14 +89,18 @@ describe(DomainWorkflowsHeader.name, () => { setQueryParams: mockSetQueryParams, }); - render(); + render( + + ); expect(await screen.findByText('Query')).toBeInTheDocument(); }); it('toggles input type when segmented control is used', async () => { const user = userEvent.setup(); - render(); + render( + + ); const queryButton = await screen.findByText('Search'); await user.click(queryButton); 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 847fe3b29..da3d9ae5d 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 @@ -11,10 +11,12 @@ import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page- import domainWorkflowsFiltersConfig from '../config/domain-workflows-filters.config'; import DomainWorkflowsQueryInput from '../domain-workflows-query-input/domain-workflows-query-input'; +import useListWorkflows from '../hooks/use-list-workflows'; import { overrides, styled } from './domain-workflows-header.styles'; +import { type Props } from './domain-workflows-header.types'; -export default function DomainWorkflowsHeader() { +export default function DomainWorkflowsHeader({ domain, cluster }: Props) { const [areFiltersShown, setAreFiltersShown] = useState(false); const { resetAllFilters, activeFiltersCount, queryParams, setQueryParams } = @@ -23,6 +25,8 @@ export default function DomainWorkflowsHeader() { pageQueryParamsConfig: domainPageQueryParamsConfig, }); + const { refetch } = useListWorkflows({ domain, cluster }); + return ( @@ -54,6 +58,7 @@ export default function DomainWorkflowsHeader() { setQueryParams({ query: v })} + refetchQuery={refetch} /> ) : ( <> 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 1caec1506..5ab04ad92 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 @@ -2,3 +2,8 @@ import { type ListWorkflowsRequestQueryParams } from '@/route-handlers/list-work export type DomainWorkflowsHeaderInputType = ListWorkflowsRequestQueryParams['inputType']; + +export type Props = { + domain: string; + cluster: string; +}; diff --git a/src/views/domain-workflows/domain-workflows-query-input/__tests__/domain-workflows-query-input.test.tsx b/src/views/domain-workflows/domain-workflows-query-input/__tests__/domain-workflows-query-input.test.tsx index 99bb0a7a7..0f614fd96 100644 --- a/src/views/domain-workflows/domain-workflows-query-input/__tests__/domain-workflows-query-input.test.tsx +++ b/src/views/domain-workflows/domain-workflows-query-input/__tests__/domain-workflows-query-input.test.tsx @@ -29,17 +29,27 @@ describe(DomainWorkflowsQueryInput.name, () => { expect(mockSetValue).toHaveBeenCalledWith('mock_query'); }); + + it('calls refetchQuery when the Rerun Query button is clicked', async () => { + const { mockRefetch, user } = setup({ startValue: 'test_query' }); + + await user.click(await screen.findByText('Rerun Query')); + + expect(mockRefetch).toHaveBeenCalled(); + }); }); function setup({ startValue }: { startValue?: string }) { const mockSetValue = jest.fn(); + const mockRefetch = jest.fn(); const user = userEvent.setup(); render( ); - return { mockSetValue, user }; + return { mockSetValue, mockRefetch, user }; } diff --git a/src/views/domain-workflows/domain-workflows-query-input/domain-workflows-query-input.tsx b/src/views/domain-workflows/domain-workflows-query-input/domain-workflows-query-input.tsx index 3066aadd5..0827ce66a 100644 --- a/src/views/domain-workflows/domain-workflows-query-input/domain-workflows-query-input.tsx +++ b/src/views/domain-workflows/domain-workflows-query-input/domain-workflows-query-input.tsx @@ -7,7 +7,11 @@ import { MdPlayArrow, MdCode, MdRefresh } from 'react-icons/md'; import { overrides } from './domain-workflows-query-input.styles'; import { type Props } from './domain-workflows-query-input.types'; -export default function DomainWorkflowsQueryInput({ value, setValue }: Props) { +export default function DomainWorkflowsQueryInput({ + value, + setValue, + refetchQuery, +}: Props) { const [queryText, setQueryText] = useState(''); useEffect(() => { @@ -30,7 +34,9 @@ export default function DomainWorkflowsQueryInput({ value, setValue }: Props) { clearOnEscape /> - ) : ( -
No workflows
- ) - ) -); - -jest.mock('query-string', () => ({ - stringifyUrl: jest.fn( - () => '/api/domains/mock-domain/mock-cluster/workflows' - ), -})); - -const mockSetQueryParams = jest.fn(); -jest.mock('@/hooks/use-page-query-params/use-page-query-params', () => - jest.fn(() => [mockDomainWorkflowsQueryParamsValues, mockSetQueryParams]) -); - -describe(DomainWorkflowsTableQuery.name, () => { - it('renders workflows without error', async () => { - const { user } = setup({}); - expect(await screen.findByText('Loading...')).toBeInTheDocument(); - - expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-0-${index}`) - ).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId('mock-end-message')); - - expect(await screen.findByText('No workflows')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-1-${index}`) - ).toBeInTheDocument(); - }); - }); - - it('renders error panel if the initial call fails', async () => { - setup({ errorCase: 'initial-fetch-error' }); - - expect( - await screen.findByText('Error loading workflows') - ).toBeInTheDocument(); - }); - - it('renders empty table if no workflows are found', async () => { - setup({ errorCase: 'no-workflows' }); - - expect(await screen.findByText('No workflows')).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(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-0-${index}`) - ).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId('mock-end-message')); - - expect( - await screen.findByText('Mock end message: Error') - ).toBeInTheDocument(); - - await user.click(screen.getByTestId('mock-end-message')); - - expect(await screen.findByText('No workflows')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-1-${index}`) - ).toBeInTheDocument(); - }); - }); -}); - -function setup({ - errorCase, -}: { - errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error' | 'no-workflows'; -}) { - const pages = generateWorkflowPages(2); - 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) { - 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'], - } - ); - - return { user }; -} - -// TODO @adhitya.mamallan - Explore using fakerjs.dev for cases like this -function generateWorkflowPages(count: number): Array { - const pages = Array.from( - { length: count }, - (_, pageIndex): ListWorkflowsResponse => ({ - workflows: Array.from({ length: 10 }, (_, index) => ({ - workflowID: `mock-workflow-id-${pageIndex}-${index}`, - runID: `mock-run-id-${pageIndex}-${index}`, - workflowName: `mock-workflow-name-${pageIndex}-${index}`, - status: 'WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED', - startTime: 1684800000000, - closeTime: count > 5 ? 1684886400000 : undefined, - })), - nextPage: `${pageIndex + 1}`, - }) - ); - - pages[pages.length - 1].nextPage = ''; - return pages; -} diff --git a/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.styles.ts b/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.styles.ts deleted file mode 100644 index ee5a55dcc..000000000 --- a/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.styles.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { styled as createStyled } from 'baseui'; - -export const styled = { - ErrorPanelContainer: createStyled('div', ({ $theme }) => ({ - padding: `${$theme.sizing.scale1200} 0px`, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - })), -}; diff --git a/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.tsx b/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.tsx deleted file mode 100644 index ba8388303..000000000 --- a/src/views/domain-workflows/domain-workflows-table-query/domain-workflows-table-query.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; -import React from 'react'; - -import ErrorPanel from '@/components/error-panel/error-panel'; -import SectionLoadingIndicator from '@/components/section-loading-indicator/section-loading-indicator'; -import Table from '@/components/table/table'; -import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params'; -import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config'; - -import domainWorkflowsTableConfig from '../config/domain-workflows-table.config'; -import { type Props } from '../domain-workflows-table/domain-workflows-table.types'; -import DomainWorkflowsTableEndMessage from '../domain-workflows-table-end-message/domain-workflows-table-end-message'; -import useListWorkflows from '../hooks/use-list-workflows'; - -import { styled } from './domain-workflows-table-query.styles'; -import getQueryErrorPanelProps from './helpers/get-query-error-panel-props'; - -export default function DomainWorkflowsTableQuery({ domain, cluster }: Props) { - const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig); - - const { - workflows, - error, - isLoading, - hasNextPage, - fetchNextPage, - isFetchingNextPage, - refetch, - } = useListWorkflows({ - domain, - cluster, - inputType: queryParams.inputType, - query: queryParams.query, - }); - - if (isLoading) { - return ; - } - - if (error && workflows.length === 0) { - return ( - - - - ); - } - - return ( -
0} - columns={domainWorkflowsTableConfig.map((columnConfig) => ({ - ...columnConfig, - sortable: false, - }))} - // TODO @adhitya.mamallan - remove this after 3.5 is merged - onSort={() => {}} - endMessage={ - 0} - error={error} - fetchNextPage={fetchNextPage} - hasNextPage={hasNextPage} - isFetchingNextPage={isFetchingNextPage} - /> - } - /> - ); -} diff --git a/src/views/domain-workflows/domain-workflows-table-query/helpers/__tests__/get-query-error-panel-props.test.ts b/src/views/domain-workflows/domain-workflows-table-query/helpers/__tests__/get-query-error-panel-props.test.ts deleted file mode 100644 index eb33113dc..000000000 --- a/src/views/domain-workflows/domain-workflows-table-query/helpers/__tests__/get-query-error-panel-props.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { RequestError } from '@/utils/request/request-error'; - -import getQueryErrorPanelProps from '../get-query-error-panel-props'; - -describe(getQueryErrorPanelProps.name, () => { - it('returns default error panel props for regular error', () => { - expect( - getQueryErrorPanelProps({ - error: new RequestError('Test error', 500), - }) - ).toEqual({ - message: 'Failed to fetch workflows', - actions: [{ kind: 'retry', label: 'Retry' }], - }); - }); - - it('returns query error message directly for bad request error', () => { - expect( - getQueryErrorPanelProps({ - error: new RequestError('Test error', 400), - }) - ).toEqual({ - message: 'Error in query: Test error', - }); - }); -}); diff --git a/src/views/domain-workflows/domain-workflows-table-query/helpers/get-query-error-panel-props.ts b/src/views/domain-workflows/domain-workflows-table-query/helpers/get-query-error-panel-props.ts deleted file mode 100644 index 97b75a2a4..000000000 --- a/src/views/domain-workflows/domain-workflows-table-query/helpers/get-query-error-panel-props.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type Props as ErrorPanelProps } from '@/components/error-panel/error-panel.types'; -import { type RequestError } from '@/utils/request/request-error'; - -export default function getQueryErrorPanelProps({ - error, -}: { - error: RequestError; -}): ErrorPanelProps { - if (error.status === 400) { - return { - message: 'Error in query: ' + error.message, - }; - } - - return { - message: 'Failed to fetch workflows', - actions: [{ kind: 'retry', label: 'Retry' }], - }; -} diff --git a/src/views/domain-workflows/domain-workflows-table-search/__tests__/domain-workflows-table-search.test.tsx b/src/views/domain-workflows/domain-workflows-table-search/__tests__/domain-workflows-table-search.test.tsx deleted file mode 100644 index 052e73889..000000000 --- a/src/views/domain-workflows/domain-workflows-table-search/__tests__/domain-workflows-table-search.test.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import { HttpResponse } from 'msw'; - -import { render, screen, userEvent } from '@/test-utils/rtl'; - -import { type ListWorkflowsResponse } from '@/route-handlers/list-workflows/list-workflows.types'; - -import type { Props as MSWMocksHandlersProps } from '../../../../test-utils/msw-mock-handlers/msw-mock-handlers.types'; -import { mockDomainWorkflowsQueryParamsValues } from '../../__fixtures__/domain-workflows-query-params'; -import { type Props as EndMessageProps } from '../../domain-workflows-table-end-message/domain-workflows-table-end-message.types'; -import DomainWorkflowsTableSearch from '../domain-workflows-table-search'; - -jest.mock('@/components/error-panel/error-panel', () => - jest.fn(({ message }: { message: string }) =>
{message}
) -); - -jest.mock( - '@/components/section-loading-indicator/section-loading-indicator', - () => jest.fn(() =>
Loading...
) -); - -jest.mock('../helpers/get-search-error-panel-props', () => - jest.fn().mockImplementation(({ error }) => ({ - message: error ? 'Error loading workflows' : 'No workflows found', - })) -); - -jest.mock( - '../../domain-workflows-table-end-message/domain-workflows-table-end-message', - () => - jest.fn((props: EndMessageProps) => - props.hasNextPage ? ( - - ) : ( -
No workflows
- ) - ) -); - -jest.mock('query-string', () => ({ - stringifyUrl: jest.fn( - () => '/api/domains/mock-domain/mock-cluster/workflows' - ), -})); - -const mockSetQueryParams = jest.fn(); -jest.mock('@/hooks/use-page-query-params/use-page-query-params', () => - jest.fn(() => [mockDomainWorkflowsQueryParamsValues, mockSetQueryParams]) -); - -describe(DomainWorkflowsTableSearch.name, () => { - it('renders workflows without error', async () => { - const { user } = setup({}); - expect(await screen.findByText('Loading...')).toBeInTheDocument(); - - expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-0-${index}`) - ).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId('mock-end-message')); - - expect(await screen.findByText('No workflows')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-1-${index}`) - ).toBeInTheDocument(); - }); - }); - - it('renders error panel if the initial call fails', async () => { - setup({ errorCase: 'initial-fetch-error' }); - - expect( - await screen.findByText('Error loading workflows') - ).toBeInTheDocument(); - }); - - it('renders error panel if no workflows are found', async () => { - setup({ errorCase: 'no-workflows' }); - - 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(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-0-${index}`) - ).toBeInTheDocument(); - }); - - await user.click(screen.getByTestId('mock-end-message')); - - expect( - await screen.findByText('Mock end message: Error') - ).toBeInTheDocument(); - - await user.click(screen.getByTestId('mock-end-message')); - - expect(await screen.findByText('No workflows')).toBeInTheDocument(); - Array(10).forEach((_, index) => { - expect( - screen.getByText(`mock-workflow-id-1-${index}`) - ).toBeInTheDocument(); - }); - }); -}); - -function setup({ - errorCase, -}: { - errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error' | 'no-workflows'; -}) { - const pages = generateWorkflowPages(2); - 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) { - 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'], - } - ); - - return { user }; -} - -// TODO @adhitya.mamallan - Explore using fakerjs.dev for cases like this -function generateWorkflowPages(count: number): Array { - const pages = Array.from( - { length: count }, - (_, pageIndex): ListWorkflowsResponse => ({ - workflows: Array.from({ length: 10 }, (_, index) => ({ - workflowID: `mock-workflow-id-${pageIndex}-${index}`, - runID: `mock-run-id-${pageIndex}-${index}`, - workflowName: `mock-workflow-name-${pageIndex}-${index}`, - status: 'WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED', - startTime: 1684800000000, - closeTime: count > 5 ? 1684886400000 : undefined, - })), - nextPage: `${pageIndex + 1}`, - }) - ); - - pages[pages.length - 1].nextPage = ''; - return pages; -} diff --git a/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.styles.ts b/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.styles.ts deleted file mode 100644 index ee5a55dcc..000000000 --- a/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.styles.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { styled as createStyled } from 'baseui'; - -export const styled = { - ErrorPanelContainer: createStyled('div', ({ $theme }) => ({ - padding: `${$theme.sizing.scale1200} 0px`, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - })), -}; diff --git a/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.tsx b/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.tsx deleted file mode 100644 index 26de7737a..000000000 --- a/src/views/domain-workflows/domain-workflows-table-search/domain-workflows-table-search.tsx +++ /dev/null @@ -1,95 +0,0 @@ -'use client'; -import React from 'react'; - -import ErrorPanel from '@/components/error-panel/error-panel'; -import SectionLoadingIndicator from '@/components/section-loading-indicator/section-loading-indicator'; -import Table from '@/components/table/table'; -import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params'; -import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config'; - -import domainWorkflowsTableConfig from '../config/domain-workflows-table.config'; -import { type Props } from '../domain-workflows-table/domain-workflows-table.types'; -import DomainWorkflowsTableEndMessage from '../domain-workflows-table-end-message/domain-workflows-table-end-message'; -import getNextSortOrder from '../helpers/get-next-sort-order'; -import useListWorkflows from '../hooks/use-list-workflows'; - -import { styled } from './domain-workflows-table-search.styles'; -import getSearchErrorPanelProps from './helpers/get-search-error-panel-props'; - -export default function DomainWorkflowsTableSearch({ domain, cluster }: Props) { - const [queryParams, setQueryParams] = usePageQueryParams( - domainPageQueryParamsConfig - ); - - const { - workflows, - error, - isLoading, - hasNextPage, - fetchNextPage, - isFetchingNextPage, - refetch, - } = useListWorkflows({ - domain, - cluster, - inputType: queryParams.inputType, - search: queryParams.search, - status: queryParams.status, - sortColumn: queryParams.sortColumn, - sortOrder: queryParams.sortOrder, - timeRangeStart: queryParams.timeRangeStart?.toISOString(), - timeRangeEnd: queryParams.timeRangeEnd?.toISOString(), - }); - - if (isLoading) { - return ; - } - - if (workflows.length === 0) { - const errorPanelProps = getSearchErrorPanelProps({ - error, - areSearchParamsAbsent: - !queryParams.search && - !queryParams.status && - !queryParams.timeRangeStart && - !queryParams.timeRangeEnd, - }); - - if (errorPanelProps) { - return ( - - - - ); - } - } - - return ( -
0} - onSort={(column) => { - setQueryParams({ - sortColumn: column, - sortOrder: getNextSortOrder({ - currentColumn: queryParams.sortColumn, - nextColumn: column, - currentSortOrder: queryParams.sortOrder, - }), - }); - }} - sortColumn={queryParams.sortColumn} - sortOrder={queryParams.sortOrder} - endMessage={ - 0} - error={error} - fetchNextPage={fetchNextPage} - hasNextPage={hasNextPage} - isFetchingNextPage={isFetchingNextPage} - /> - } - /> - ); -} 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 c2d91e004..88b21ee1d 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 @@ -1,45 +1,224 @@ -import { render, screen } from '@/test-utils/rtl'; +import { HttpResponse } from 'msw'; + +import { render, screen, userEvent, waitFor } from '@/test-utils/rtl'; import * as usePageQueryParamsModule from '@/hooks/use-page-query-params/use-page-query-params'; +import { type ListWorkflowsResponse } from '@/route-handlers/list-workflows/list-workflows.types'; +import type { Props as MSWMocksHandlersProps } from '../../../../test-utils/msw-mock-handlers/msw-mock-handlers.types'; import { mockDomainWorkflowsQueryParamsValues } from '../../__fixtures__/domain-workflows-query-params'; +import { type DomainWorkflowsHeaderInputType } from '../../domain-workflows-header/domain-workflows-header.types'; +import { type Props as EndMessageProps } from '../../domain-workflows-table-end-message/domain-workflows-table-end-message.types'; import DomainWorkflowsTable from '../domain-workflows-table'; -jest.mock( - '../../domain-workflows-table-search/domain-workflows-table-search', - () => jest.fn(() =>
Search results table
) +jest.mock('@/components/error-panel/error-panel', () => + jest.fn(({ message }: { message: string }) =>
{message}
) +); + +jest.mock('../helpers/get-workflows-error-panel-props', () => + jest + .fn() + .mockImplementation( + ({ + error, + inputType, + }: { + error: Error; + inputType: DomainWorkflowsHeaderInputType; + }) => { + if (inputType === 'query') { + return { + message: error ? error.message : undefined, + }; + } + return { + message: error ? 'Error loading workflows' : 'No workflows found', + }; + } + ) ); jest.mock( - '../../domain-workflows-table-query/domain-workflows-table-query', - () => jest.fn(() =>
Query results table
) + '../../domain-workflows-table-end-message/domain-workflows-table-end-message', + () => + jest.fn((props: EndMessageProps) => ( + + )) ); +jest.mock('query-string', () => ({ + stringifyUrl: jest.fn( + () => '/api/domains/mock-domain/mock-cluster/workflows' + ), +})); + +const mockSetQueryParams = jest.fn(); jest.mock('@/hooks/use-page-query-params/use-page-query-params', () => - jest.fn(() => [mockDomainWorkflowsQueryParamsValues, jest.fn()]) + jest.fn(() => [mockDomainWorkflowsQueryParamsValues, mockSetQueryParams]) ); describe(DomainWorkflowsTable.name, () => { - it('renders search table by default', async () => { - render( - - ); + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders workflows without error', async () => { + const { user } = setup({}); + + expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-0-${index}`) + ).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('mock-end-message')); + + expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-1-${index}`) + ).toBeInTheDocument(); + }); + }); + + it('renders error panel if the initial call fails', async () => { + setup({ errorCase: 'initial-fetch-error' }); + + expect( + await screen.findByText('Error loading workflows') + ).toBeInTheDocument(); + }); + + it('renders error panel in search mode if no workflows are found', async () => { + setup({ errorCase: 'no-workflows' }); - expect(await screen.findByText('Search results table')).toBeInTheDocument(); + expect(await screen.findByText('No workflows found')).toBeInTheDocument(); }); - it('renders query table if inputType is query', async () => { + it('renders empty table in query mode if no workflows are found', async () => { jest .spyOn(usePageQueryParamsModule, 'default') .mockReturnValue([ { ...mockDomainWorkflowsQueryParamsValues, inputType: 'query' }, - jest.fn(), + mockSetQueryParams, ]); - render( - - ); + setup({ errorCase: 'no-workflows' }); + const progressbar = await screen.findByRole('progressbar'); - expect(await screen.findByText('Query results table')).toBeInTheDocument(); + await waitFor(() => { + expect(progressbar).not.toBeInTheDocument(); + }); + + expect(screen.queryByText('No workflows found')).not.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(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-0-${index}`) + ).toBeInTheDocument(); + }); + + await user.click(screen.getByTestId('mock-end-message')); + + expect( + await screen.findByText('Mock end message: Error') + ).toBeInTheDocument(); + + await user.click(screen.getByTestId('mock-end-message')); + + expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument(); + Array(10).forEach((_, index) => { + expect( + screen.getByText(`mock-workflow-id-1-${index}`) + ).toBeInTheDocument(); + }); }); }); + +function setup({ + errorCase, +}: { + errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error' | 'no-workflows'; +}) { + const pages = generateWorkflowPages(2); + let currentEventIndex = 0; + const user = userEvent.setup(); + + const { debug } = 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) { + 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'], + } + ); + + return { user, debug }; +} + +// TODO @adhitya.mamallan - Explore using fakerjs.dev for cases like this +function generateWorkflowPages(count: number): Array { + const pages = Array.from( + { length: count }, + (_, pageIndex): ListWorkflowsResponse => ({ + workflows: Array.from({ length: 10 }, (_, index) => ({ + workflowID: `mock-workflow-id-${pageIndex}-${index}`, + runID: `mock-run-id-${pageIndex}-${index}`, + workflowName: `mock-workflow-name-${pageIndex}-${index}`, + status: 'WORKFLOW_EXECUTION_CLOSE_STATUS_COMPLETED', + startTime: 1684800000000, + closeTime: count > 5 ? 1684886400000 : undefined, + })), + nextPage: `${pageIndex + 1}`, + }) + ); + + pages[pages.length - 1].nextPage = ''; + return pages; +} diff --git a/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.styles.ts b/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.styles.ts index 9f63ba324..d536b2fdf 100644 --- a/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.styles.ts +++ b/src/views/domain-workflows/domain-workflows-table/domain-workflows-table.styles.ts @@ -4,4 +4,10 @@ export const styled = { TableContainer: createStyled('div', { overflowX: 'auto', }), + ErrorPanelContainer: createStyled('div', ({ $theme }) => ({ + padding: `${$theme.sizing.scale1200} 0px`, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + })), }; 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 c7d01db28..4e9b3b1f5 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 @@ -1,25 +1,100 @@ 'use client'; import React from 'react'; +import ErrorPanel from '@/components/error-panel/error-panel'; +import SectionLoadingIndicator from '@/components/section-loading-indicator/section-loading-indicator'; +import Table from '@/components/table/table'; import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params'; import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config'; -import DomainWorkflowsTableQuery from '../domain-workflows-table-query/domain-workflows-table-query'; -import DomainWorkflowsTableSearch from '../domain-workflows-table-search/domain-workflows-table-search'; +import domainWorkflowsTableConfig from '../config/domain-workflows-table.config'; +import { type Props } from '../domain-workflows-table/domain-workflows-table.types'; +import DomainWorkflowsTableEndMessage from '../domain-workflows-table-end-message/domain-workflows-table-end-message'; +import getNextSortOrder from '../helpers/get-next-sort-order'; +import useListWorkflows from '../hooks/use-list-workflows'; import { styled } from './domain-workflows-table.styles'; -import { type Props } from './domain-workflows-table.types'; +import getWorkflowsErrorPanelProps from './helpers/get-workflows-error-panel-props'; -export default function DomainWorkflowsTable(props: Props) { - const [{ inputType }] = usePageQueryParams(domainPageQueryParamsConfig); +export default function DomainWorkflowsTable({ domain, cluster }: Props) { + const [queryParams, setQueryParams] = usePageQueryParams( + domainPageQueryParamsConfig + ); + + const inputType = queryParams.inputType; + + const { + workflows, + error, + isLoading, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + refetch, + } = useListWorkflows({ domain, cluster }); + + if (isLoading) { + return ; + } + + if (workflows.length === 0) { + const errorPanelProps = getWorkflowsErrorPanelProps({ + inputType, + error, + areSearchParamsAbsent: + !queryParams.search && + !queryParams.status && + !queryParams.timeRangeStart && + !queryParams.timeRangeEnd, + }); + + if (errorPanelProps) { + return ( + + + + ); + } + } return ( - {inputType === 'query' ? ( - - ) : ( - - )} +
0} + endMessage={ + 0} + error={error} + fetchNextPage={fetchNextPage} + hasNextPage={hasNextPage} + isFetchingNextPage={isFetchingNextPage} + /> + } + {...(inputType === 'query' + ? { + columns: domainWorkflowsTableConfig.map((columnConfig) => ({ + ...columnConfig, + sortable: false, + })), + onSort: () => {}, + } + : { + columns: domainWorkflowsTableConfig, + onSort: (column) => { + setQueryParams({ + sortColumn: column, + sortOrder: getNextSortOrder({ + currentColumn: queryParams.sortColumn, + nextColumn: column, + currentSortOrder: queryParams.sortOrder, + }), + }); + }, + sortColumn: queryParams.sortColumn, + sortOrder: queryParams.sortOrder, + })} + /> ); } 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 9ac077ebc..2adbd04c7 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,5 +1,4 @@ export type Props = { domain: string; cluster: string; - // TODO @adhitya.mamallan - when working on Archival, add a flag here }; diff --git a/src/views/domain-workflows/domain-workflows-table-search/helpers/__tests__/get-search-error-panel-props.test.ts b/src/views/domain-workflows/domain-workflows-table/helpers/__tests__/get-workflows-error-panel-props.test.ts similarity index 60% rename from src/views/domain-workflows/domain-workflows-table-search/helpers/__tests__/get-search-error-panel-props.test.ts rename to src/views/domain-workflows/domain-workflows-table/helpers/__tests__/get-workflows-error-panel-props.test.ts index cb45d4098..8956fd76a 100644 --- a/src/views/domain-workflows/domain-workflows-table-search/helpers/__tests__/get-search-error-panel-props.test.ts +++ b/src/views/domain-workflows/domain-workflows-table/helpers/__tests__/get-workflows-error-panel-props.test.ts @@ -1,11 +1,12 @@ import { RequestError } from '@/utils/request/request-error'; -import getSearchErrorPanelProps from '../get-search-error-panel-props'; +import getWorkflowsErrorPanelProps from '../get-workflows-error-panel-props'; -describe(getSearchErrorPanelProps.name, () => { +describe(getWorkflowsErrorPanelProps.name, () => { it('returns default error panel props for regular error', () => { expect( - getSearchErrorPanelProps({ + getWorkflowsErrorPanelProps({ + inputType: 'search', error: new RequestError('Test error', 500), areSearchParamsAbsent: false, }) @@ -15,9 +16,22 @@ describe(getSearchErrorPanelProps.name, () => { }); }); + it('returns error message directly for bad request error for queries', () => { + expect( + getWorkflowsErrorPanelProps({ + inputType: 'query', + error: new RequestError('Incorrect query', 400), + areSearchParamsAbsent: false, + }) + ).toEqual({ + message: 'Error in query: Incorrect query', + }); + }); + it('returns "not found" error panel props when search params are absent', () => { expect( - getSearchErrorPanelProps({ + getWorkflowsErrorPanelProps({ + inputType: 'search', error: null, areSearchParamsAbsent: true, }) @@ -36,7 +50,8 @@ describe(getSearchErrorPanelProps.name, () => { it('returns undefined in all other cases', () => { expect( - getSearchErrorPanelProps({ + getWorkflowsErrorPanelProps({ + inputType: 'search', error: null, areSearchParamsAbsent: false, }) diff --git a/src/views/domain-workflows/domain-workflows-table-search/helpers/get-search-error-panel-props.ts b/src/views/domain-workflows/domain-workflows-table/helpers/get-workflows-error-panel-props.ts similarity index 67% rename from src/views/domain-workflows/domain-workflows-table-search/helpers/get-search-error-panel-props.ts rename to src/views/domain-workflows/domain-workflows-table/helpers/get-workflows-error-panel-props.ts index ebed47328..972d9306c 100644 --- a/src/views/domain-workflows/domain-workflows-table-search/helpers/get-search-error-panel-props.ts +++ b/src/views/domain-workflows/domain-workflows-table/helpers/get-workflows-error-panel-props.ts @@ -1,14 +1,24 @@ import { type Props as ErrorPanelProps } from '@/components/error-panel/error-panel.types'; import { type RequestError } from '@/utils/request/request-error'; -export default function getSearchErrorPanelProps({ +import { type DomainWorkflowsHeaderInputType } from '../../domain-workflows-header/domain-workflows-header.types'; + +export default function getWorkflowsErrorPanelProps({ + inputType, error, areSearchParamsAbsent, }: { + inputType: DomainWorkflowsHeaderInputType; error: RequestError | null; areSearchParamsAbsent: boolean; }): ErrorPanelProps | undefined { if (error) { + if (inputType === 'query' && error.status === 400) { + return { + message: 'Error in query: ' + error.message, + }; + } + return { message: 'Failed to fetch workflows', actions: [{ kind: 'retry', label: 'Retry' }], diff --git a/src/views/domain-workflows/domain-workflows.tsx b/src/views/domain-workflows/domain-workflows.tsx index dbf5c5e7d..6888bbd36 100644 --- a/src/views/domain-workflows/domain-workflows.tsx +++ b/src/views/domain-workflows/domain-workflows.tsx @@ -8,7 +8,7 @@ import DomainWorkflowsTable from './domain-workflows-table/domain-workflows-tabl export default function DomainWorkflows(props: DomainPageTabContentProps) { return ( <> - + ); diff --git a/src/views/domain-workflows/domain-workflows.types.ts b/src/views/domain-workflows/domain-workflows.types.ts index 055124e9e..b85ce1255 100644 --- a/src/views/domain-workflows/domain-workflows.types.ts +++ b/src/views/domain-workflows/domain-workflows.types.ts @@ -1,7 +1,5 @@ -import { - type RouteParams as ListWorkflowsRouteParams, - type ListWorkflowsRequestQueryParams, -} from '@/route-handlers/list-workflows/list-workflows.types'; +import { type RouteParams as ListWorkflowsRouteParams } from '@/route-handlers/list-workflows/list-workflows.types'; -export type UseListWorkflowsParams = ListWorkflowsRouteParams & - Omit & { pageSize?: number }; +export type UseListWorkflowsParams = ListWorkflowsRouteParams & { + pageSize?: number; +}; diff --git a/src/views/domain-workflows/hooks/use-list-workflows.ts b/src/views/domain-workflows/hooks/use-list-workflows.ts index 7f942a7bc..564929824 100644 --- a/src/views/domain-workflows/hooks/use-list-workflows.ts +++ b/src/views/domain-workflows/hooks/use-list-workflows.ts @@ -4,12 +4,14 @@ import { useMemo } from 'react'; import { useInfiniteQuery } from '@tanstack/react-query'; import queryString from 'query-string'; +import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params'; import { type ListWorkflowsResponse, type ListWorkflowsRequestQueryParams, } from '@/route-handlers/list-workflows/list-workflows.types'; import request from '@/utils/request'; import { type RequestError } from '@/utils/request/request-error'; +import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config'; import DOMAIN_WORKFLOWS_PAGE_SIZE from '../config/domain-workflows-page-size.config'; import { type UseListWorkflowsParams } from '../domain-workflows.types'; @@ -18,8 +20,25 @@ export default function useListWorkflows({ domain, cluster, pageSize = DOMAIN_WORKFLOWS_PAGE_SIZE, - ...requestQueryParams }: UseListWorkflowsParams) { + const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig); + + const requestQueryParams = { + inputType: queryParams.inputType, + ...(queryParams.inputType === 'query' + ? { + query: queryParams.query, + } + : { + search: queryParams.search, + status: queryParams.status, + sortColumn: queryParams.sortColumn, + sortOrder: queryParams.sortOrder, + timeRangeStart: queryParams.timeRangeStart?.toISOString(), + timeRangeEnd: queryParams.timeRangeEnd?.toISOString(), + }), + }; + const queryResult = useInfiniteQuery({ queryKey: [ 'listWorkflows', From e93a7fa67170a22133baed1f78fa320e401d02e9 Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Mon, 25 Nov 2024 10:04:05 +0000 Subject: [PATCH 3/6] Fix tests and type checks --- .../config/domain-workflows-table.config.ts | 4 +- .../__tests__/domain-workflows-table.test.tsx | 81 +++++++++---------- .../domain-workflows-table.tsx | 1 - 3 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/views/domain-workflows/config/domain-workflows-table.config.ts b/src/views/domain-workflows/config/domain-workflows-table.config.ts index 09dbd8a69..d287c6670 100644 --- a/src/views/domain-workflows/config/domain-workflows-table.config.ts +++ b/src/views/domain-workflows/config/domain-workflows-table.config.ts @@ -6,7 +6,7 @@ import { type TableConfig } from '@/components/table/table.types'; import { type DomainWorkflow } from '@/views/domain-page/domain-page.types'; import WorkflowStatusTag from '@/views/shared/workflow-status-tag/workflow-status-tag'; -const domainWorkflowsTableConfig = [ +const domainWorkflowsTableConfig: TableConfig = [ { name: 'Workflow ID', id: 'WorkflowID', @@ -55,6 +55,6 @@ const domainWorkflowsTableConfig = [ width: '12.5%', sortable: true, }, -] as const satisfies TableConfig; +]; export default domainWorkflowsTableConfig; 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 88b21ee1d..d61ada399 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,54 +152,51 @@ function setup({ let currentEventIndex = 0; const user = userEvent.setup(); - const { debug } = 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': + 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) { return HttpResponse.json( { message: 'Request failed' }, { status: 500 } ); - 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]); - } - } - }, + } 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, debug }; + return { user }; } // TODO @adhitya.mamallan - Explore using fakerjs.dev for cases like this 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 4e9b3b1f5..53c56a207 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 @@ -77,7 +77,6 @@ export default function DomainWorkflowsTable({ domain, cluster }: Props) { ...columnConfig, sortable: false, })), - onSort: () => {}, } : { columns: domainWorkflowsTableConfig, From ade76947011e24ff3c0c20cf57b6e21a26575681 Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Mon, 25 Nov 2024 16:00:07 +0530 Subject: [PATCH 4/6] make changes to type --- .../domain-workflows-table.tsx | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) 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 53c56a207..a107f1253 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 @@ -61,6 +61,7 @@ export default function DomainWorkflowsTable({ domain, cluster }: Props) {
0} endMessage={ } - {...(inputType === 'query' - ? { - columns: domainWorkflowsTableConfig.map((columnConfig) => ({ - ...columnConfig, - sortable: false, - })), - } - : { - columns: domainWorkflowsTableConfig, - onSort: (column) => { - setQueryParams({ - sortColumn: column, - sortOrder: getNextSortOrder({ - currentColumn: queryParams.sortColumn, - nextColumn: column, - currentSortOrder: queryParams.sortOrder, - }), - }); - }, - sortColumn: queryParams.sortColumn, - sortOrder: queryParams.sortOrder, - })} + // Query input - if onSort isn't passed all columns are rendered unsortable by default + {...(inputType === 'search' && { + onSort: (column) => { + setQueryParams({ + sortColumn: column, + sortOrder: getNextSortOrder({ + currentColumn: queryParams.sortColumn, + nextColumn: column, + currentSortOrder: queryParams.sortOrder, + }), + }); + }, + sortColumn: queryParams.sortColumn, + sortOrder: queryParams.sortOrder, + })} /> ); From ac3dc140eb9351b058a141bfdba32a96278e8874 Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Mon, 25 Nov 2024 18:21:07 +0530 Subject: [PATCH 5/6] Split search and query table configs --- .../domain-workflows-query-table.config.ts | 58 +++++++++++++++++++ ...> domain-workflows-search-table.config.ts} | 6 +- .../domain-workflows-table.tsx | 38 ++++++------ 3 files changed, 82 insertions(+), 20 deletions(-) create mode 100644 src/views/domain-workflows/config/domain-workflows-query-table.config.ts rename src/views/domain-workflows/config/{domain-workflows-table.config.ts => domain-workflows-search-table.config.ts} (91%) diff --git a/src/views/domain-workflows/config/domain-workflows-query-table.config.ts b/src/views/domain-workflows/config/domain-workflows-query-table.config.ts new file mode 100644 index 000000000..762660dd3 --- /dev/null +++ b/src/views/domain-workflows/config/domain-workflows-query-table.config.ts @@ -0,0 +1,58 @@ +import { createElement } from 'react'; + +import FormattedDate from '@/components/formatted-date/formatted-date'; +import Link from '@/components/link/link'; +import { type TableConfig } from '@/components/table/table.types'; +import { type DomainWorkflow } from '@/views/domain-page/domain-page.types'; +import WorkflowStatusTag from '@/views/shared/workflow-status-tag/workflow-status-tag'; + +const domainWorkflowsQueryTableConfig = [ + { + name: 'Workflow ID', + id: 'WorkflowID', + renderCell: (row: DomainWorkflow) => row.workflowID, + width: '25.5%', + }, + { + name: 'Status', + id: 'CloseStatus', + renderCell: (row: DomainWorkflow) => + createElement(WorkflowStatusTag, { status: row.status }), + width: '7.5%', + }, + { + name: 'Run ID', + id: 'RunID', + renderCell: (row: DomainWorkflow) => + createElement( + Link, + { + href: `workflows/${encodeURIComponent(row.workflowID)}/${encodeURIComponent(row.runID)}`, + }, + row.runID + ), + width: '22%', + }, + { + name: 'Workflow type', + id: 'WorkflowType', + renderCell: (row: DomainWorkflow) => row.workflowName, + width: '20%', + }, + { + name: 'Started', + id: 'StartTime', + renderCell: (row: DomainWorkflow) => + createElement(FormattedDate, { timestampMs: row.startTime }), + width: '12.5%', + }, + { + name: 'Ended', + id: 'CloseTime', + renderCell: (row: DomainWorkflow) => + createElement(FormattedDate, { timestampMs: row.closeTime }), + width: '12.5%', + }, +] as const satisfies TableConfig; + +export default domainWorkflowsQueryTableConfig; diff --git a/src/views/domain-workflows/config/domain-workflows-table.config.ts b/src/views/domain-workflows/config/domain-workflows-search-table.config.ts similarity index 91% rename from src/views/domain-workflows/config/domain-workflows-table.config.ts rename to src/views/domain-workflows/config/domain-workflows-search-table.config.ts index d287c6670..49f13e5e2 100644 --- a/src/views/domain-workflows/config/domain-workflows-table.config.ts +++ b/src/views/domain-workflows/config/domain-workflows-search-table.config.ts @@ -6,7 +6,7 @@ import { type TableConfig } from '@/components/table/table.types'; import { type DomainWorkflow } from '@/views/domain-page/domain-page.types'; import WorkflowStatusTag from '@/views/shared/workflow-status-tag/workflow-status-tag'; -const domainWorkflowsTableConfig: TableConfig = [ +const domainWorkflowsSearchTableConfig = [ { name: 'Workflow ID', id: 'WorkflowID', @@ -55,6 +55,6 @@ const domainWorkflowsTableConfig: TableConfig = [ width: '12.5%', sortable: true, }, -]; +] as const satisfies TableConfig; -export default domainWorkflowsTableConfig; +export default domainWorkflowsSearchTableConfig; 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 a107f1253..797a68dfd 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 @@ -7,7 +7,8 @@ import Table from '@/components/table/table'; import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params'; import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config'; -import domainWorkflowsTableConfig from '../config/domain-workflows-table.config'; +import domainWorkflowsQueryTableConfig from '../config/domain-workflows-query-table.config'; +import domainWorkflowsSearchTableConfig from '../config/domain-workflows-search-table.config'; import { type Props } from '../domain-workflows-table/domain-workflows-table.types'; import DomainWorkflowsTableEndMessage from '../domain-workflows-table-end-message/domain-workflows-table-end-message'; import getNextSortOrder from '../helpers/get-next-sort-order'; @@ -61,7 +62,6 @@ export default function DomainWorkflowsTable({ domain, cluster }: Props) {
0} endMessage={ } - // Query input - if onSort isn't passed all columns are rendered unsortable by default - {...(inputType === 'search' && { - onSort: (column) => { - setQueryParams({ - sortColumn: column, - sortOrder: getNextSortOrder({ - currentColumn: queryParams.sortColumn, - nextColumn: column, - currentSortOrder: queryParams.sortOrder, - }), - }); - }, - sortColumn: queryParams.sortColumn, - sortOrder: queryParams.sortOrder, - })} + {...(inputType === 'query' + ? { + columns: domainWorkflowsQueryTableConfig, + } + : { + columns: domainWorkflowsSearchTableConfig, + onSort: (column) => { + setQueryParams({ + sortColumn: column, + sortOrder: getNextSortOrder({ + currentColumn: queryParams.sortColumn, + nextColumn: column, + currentSortOrder: queryParams.sortOrder, + }), + }); + }, + sortColumn: queryParams.sortColumn, + sortOrder: queryParams.sortOrder, + })} /> ); From aac0479bbeadb1a8f0d07e61b432d34b64c56f1a Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Mon, 25 Nov 2024 13:35:11 +0000 Subject: [PATCH 6/6] Add type for domain workflows query config --- .../config/domain-workflows-query-table.config.ts | 5 +++-- .../domain-workflows-table/domain-workflows-table.types.ts | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/views/domain-workflows/config/domain-workflows-query-table.config.ts b/src/views/domain-workflows/config/domain-workflows-query-table.config.ts index 762660dd3..f8be5a532 100644 --- a/src/views/domain-workflows/config/domain-workflows-query-table.config.ts +++ b/src/views/domain-workflows/config/domain-workflows-query-table.config.ts @@ -2,10 +2,11 @@ import { createElement } from 'react'; import FormattedDate from '@/components/formatted-date/formatted-date'; import Link from '@/components/link/link'; -import { type TableConfig } from '@/components/table/table.types'; import { type DomainWorkflow } from '@/views/domain-page/domain-page.types'; import WorkflowStatusTag from '@/views/shared/workflow-status-tag/workflow-status-tag'; +import { type DomainWorkflowsQueryTableConfig } from '../domain-workflows-table/domain-workflows-table.types'; + const domainWorkflowsQueryTableConfig = [ { name: 'Workflow ID', @@ -53,6 +54,6 @@ const domainWorkflowsQueryTableConfig = [ createElement(FormattedDate, { timestampMs: row.closeTime }), width: '12.5%', }, -] as const satisfies TableConfig; +] as const satisfies DomainWorkflowsQueryTableConfig; export default domainWorkflowsQueryTableConfig; 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..8e08ad671 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,11 @@ +import { type TableColumn } from '@/components/table/table.types'; +import { type DomainWorkflow } from '@/views/domain-page/domain-page.types'; + export type Props = { domain: string; cluster: string; }; + +export type DomainWorkflowsQueryTableConfig = Array< + Omit, 'sortable'> +>;