Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,22 +1,207 @@
import { render, screen } from '@/test-utils/rtl';
import { HttpResponse } from 'msw';

import { render, screen, userEvent } from '@/test-utils/rtl';

import { getMockWorkflowListItem } from '@/route-handlers/list-workflows/__fixtures__/mock-workflow-list-items';
import { type ListWorkflowsResponse } from '@/route-handlers/list-workflows/list-workflows.types';
import { type WorkflowsHeaderInputType } from '@/views/shared/workflows-header/workflows-header.types';
import { type Props as WorkflowsListProps } from '@/views/shared/workflows-list/workflows-list.types';

import type { Props as MSWMocksHandlersProps } from '../../../../test-utils/msw-mock-handlers/msw-mock-handlers.types';
import { mockDomainPageQueryParamsValues } from '../../../domain-page/__fixtures__/domain-page-query-params';
import DomainWorkflowsArchivalList from '../domain-workflows-archival-list';

jest.mock('@/components/error-panel/error-panel', () =>
jest.fn(({ message }: { message: string }) => <div>{message}</div>)
);

jest.mock(
'../../domain-workflows-archival-table/helpers/get-archival-error-panel-props',
() =>
jest
.fn()
.mockImplementation(
({
error,
inputType,
}: {
error: Error;
inputType: WorkflowsHeaderInputType;
}) => {
if (inputType === 'query') {
return {
message: error ? error.message : undefined,
};
}
return {
message: error ? 'Error loading workflows' : 'No workflows found',
};
}
)
);

jest.mock('@/views/shared/workflows-list/workflows-list', () =>
jest.fn(() => <div>Mock workflows list</div>)
jest.fn((props: WorkflowsListProps) => (
<div>
{props.workflows.map((wf) => (
<div key={wf.workflowID}>{wf.workflowID}</div>
))}
<button data-testid="mock-loader" onClick={props.fetchNextPage}>
Mock end message: {props.error ? 'Error' : 'OK'}
</button>
</div>
))
);

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(() => [mockDomainPageQueryParamsValues, mockSetQueryParams])
);

jest.mock('../../hooks/use-archival-input-type', () =>
jest.fn(() => ({
forceQueryInputOnly: false,
inputType: mockDomainPageQueryParamsValues.inputTypeArchival,
}))
);

describe(DomainWorkflowsArchivalList.name, () => {
it('renders workflows list', () => {
render(
<DomainWorkflowsArchivalList
domain="mock-domain"
cluster="mock-cluster"
timeRangeStart="mock-time-range-start"
timeRangeEnd="mock-time-range-end"
/>
);

expect(screen.getByText('Mock workflows list')).toBeInTheDocument();
beforeEach(() => {
jest.clearAllMocks();
});

it('renders workflows without error', async () => {
const { user } = setup({});

expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
for (let i = 0; i < 10; i++) {
expect(screen.getByText(`mock-workflow-id-0-${i}`)).toBeInTheDocument();
}

await user.click(screen.getByTestId('mock-loader'));

expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
for (let i = 0; i < 10; i++) {
expect(screen.getByText(`mock-workflow-id-1-${i}`)).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 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();
for (let i = 0; i < 10; i++) {
expect(screen.getByText(`mock-workflow-id-0-${i}`)).toBeInTheDocument();
}

await user.click(screen.getByTestId('mock-loader'));

expect(
await screen.findByText('Mock end message: Error')
).toBeInTheDocument();

await user.click(screen.getByTestId('mock-loader'));

expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
for (let i = 0; i < 10; i++) {
expect(screen.getByText(`mock-workflow-id-1-${i}`)).toBeInTheDocument();
}
});
});

function setup({
errorCase,
}: {
errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error';
}) {
const pages = generateWorkflowPages(2);
let currentEventIndex = 0;
const user = userEvent.setup();

render(
<DomainWorkflowsArchivalList
domain="mock-domain"
cluster="mock-cluster"
visibleColumns={[]}
timeRangeStart="mock-time-range-start"
timeRangeEnd="mock-time-range-end"
/>,
{
endpointsMocks: [
{
path: '/api/domains/:domain/:cluster/workflows',
httpMethod: 'GET',
mockOnce: false,
httpResolver: async () => {
const index = currentEventIndex;
currentEventIndex++;

switch (errorCase) {
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<ListWorkflowsResponse> {
const pages = Array.from(
{ length: count },
(_, pageIndex): ListWorkflowsResponse => ({
workflows: Array.from({ length: 10 }, (_, index) =>
getMockWorkflowListItem({
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;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,79 @@
'use client';

import ErrorPanel from '@/components/error-panel/error-panel';
import PanelSection from '@/components/panel-section/panel-section';
import SectionLoadingIndicator from '@/components/section-loading-indicator/section-loading-indicator';
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 useListWorkflows from '@/views/shared/hooks/use-list-workflows';
import WorkflowsList from '@/views/shared/workflows-list/workflows-list';

import DOMAIN_WORKFLOWS_ARCHIVAL_PAGE_SIZE from '../config/domain-workflows-archival-page-size.config';
import getArchivalErrorPanelProps from '../domain-workflows-archival-table/helpers/get-archival-error-panel-props';
import useArchivalInputType from '../hooks/use-archival-input-type';

import { type Props } from './domain-workflows-archival-list.types';

export default function DomainWorkflowsArchivalList(_props: Props) {
return <WorkflowsList />;
export default function DomainWorkflowsArchivalList({
domain,
cluster,
visibleColumns,
timeRangeStart,
timeRangeEnd,
}: Props) {
const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig);
const { inputType } = useArchivalInputType();

const {
workflows,
error,
isLoading,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
refetch,
} = useListWorkflows({
domain,
cluster,
listType: 'archived',
pageSize: DOMAIN_WORKFLOWS_ARCHIVAL_PAGE_SIZE,
inputType,
search: queryParams.searchArchival,
statuses: queryParams.statusesArchival,
timeRangeStart,
timeRangeEnd,
sortColumn: queryParams.sortColumnArchival,
sortOrder: queryParams.sortOrderArchival,
query: queryParams.queryArchival,
});

if (isLoading) {
return <SectionLoadingIndicator />;
}

if (workflows.length === 0 && error) {
return (
<PanelSection>
<ErrorPanel
{...getArchivalErrorPanelProps({
inputType,
error,
queryString: queryParams.queryArchival,
})}
reset={refetch}
/>
</PanelSection>
);
}

return (
<WorkflowsList
workflows={workflows}
columns={visibleColumns}
error={error}
hasNextPage={hasNextPage}
fetchNextPage={fetchNextPage}
isFetchingNextPage={isFetchingNextPage}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { type WorkflowsListColumn } from '@/views/shared/workflows-list/workflows-list.types';

export type Props = {
domain: string;
cluster: string;
visibleColumns: Array<WorkflowsListColumn>;
timeRangeStart: string;
timeRangeEnd: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import getDayjsFromDateFilterValue from '@/components/date-filter/helpers/get-da
import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value';
import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params';
import { type DomainPageTabContentProps } from '@/views/domain-page/domain-page-content/domain-page-content.types';
import useWorkflowsListColumns from '@/views/shared/workflows-list/hooks/use-workflows-list-columns';

import domainPageQueryParamsConfig from '../domain-page/config/domain-page-query-params.config';
import useSuspenseDomainDescription from '../shared/hooks/use-domain-description/use-suspense-domain-description';
Expand All @@ -24,6 +25,10 @@ export default function DomainWorkflowsArchival(

const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig);

const { visibleColumns } = useWorkflowsListColumns({
cluster: props.cluster,
});

const { data: isNewWorkflowsListEnabled } = useSuspenseConfigValue(
'WORKFLOWS_LIST_ENABLED'
);
Expand Down Expand Up @@ -62,6 +67,7 @@ export default function DomainWorkflowsArchival(
<DomainWorkflowsArchivalList
domain={props.domain}
cluster={props.cluster}
visibleColumns={visibleColumns}
{...timeRangeParams}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import getDayjsFromDateFilterValue from '@/components/date-filter/helpers/get-da
import usePageQueryParams from '@/hooks/use-page-query-params/use-page-query-params';
import dayjs from '@/utils/datetime/dayjs';
import domainPageQueryParamsConfig from '@/views/domain-page/config/domain-page-query-params.config';
import useWorkflowsListColumns from '@/views/shared/workflows-list/hooks/use-workflows-list-columns';

import DomainWorkflowsHeader from '../domain-workflows-header/domain-workflows-header';
import DomainWorkflowsList from '../domain-workflows-list/domain-workflows-list';
Expand All @@ -18,6 +19,8 @@ export default function DomainWorkflowsAdvanced({
}: Props) {
const [queryParams] = usePageQueryParams(domainPageQueryParamsConfig);

const { visibleColumns } = useWorkflowsListColumns({ cluster });

const timeRangeParams = useMemo(() => {
const now = dayjs();

Expand Down Expand Up @@ -47,6 +50,7 @@ export default function DomainWorkflowsAdvanced({
<DomainWorkflowsList
domain={domain}
cluster={cluster}
visibleColumns={visibleColumns}
{...timeRangeParams}
/>
</>
Expand Down
Loading
Loading