Skip to content

Commit

Permalink
Remove useSuspenseQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
adhityamamallan committed Nov 12, 2024
1 parent f26d165 commit 52dc521
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 152 deletions.
7 changes: 4 additions & 3 deletions src/views/domain-page/config/domain-page-tabs-error.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import getDomainWorkflowsErrorConfig from '@/views/domain-workflows/helpers/get-domain-workflows-error-config';

import { type DomainPageTabsErrorConfig } from '../domain-page-tabs-error/domain-page-tabs-error.types';

const domainPageTabsErrorConfig: DomainPageTabsErrorConfig = {
workflows: getDomainWorkflowsErrorConfig,
workflows: () => ({
message: 'Failed to load workflows',
actions: [{ kind: 'retry', label: 'Retry' }],
}),
metadata: () => ({
message: 'Failed to load metadata',
actions: [{ kind: 'retry', label: 'Retry' }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ export default function DomainPageContent(props: Props) {
}

return (
<section>
<TabContent
domain={decodedParams.domain}
cluster={decodedParams.cluster}
/>
</section>
<TabContent domain={decodedParams.domain} cluster={decodedParams.cluster} />
);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { Suspense } from 'react';
import { HttpResponse } from 'msw';

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

import { type ListWorkflowsResponse } from '@/route-handlers/list-workflows/list-workflows.types';
import * as requestModule from '@/utils/request';

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 }) => <div>{message}</div>)
);

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-end-message/domain-workflows-table-end-message',
() =>
Expand All @@ -20,19 +30,19 @@ jest.mock(
);

jest.mock('query-string', () => ({
stringifyUrl: jest.fn(() => 'mock-stringified-api-url'),
stringifyUrl: jest.fn(
() => '/api/domains/mock-domain/mock-cluster/workflows'
),
}));

jest.mock('@/utils/request');

const mockSetQueryParams = jest.fn();
jest.mock('@/hooks/use-page-query-params/use-page-query-params', () =>
jest.fn(() => [mockDomainWorkflowsQueryParamsValues, mockSetQueryParams])
);

describe(DomainWorkflowsTable.name, () => {
it('renders workflows without error', async () => {
await setup({});
const { user } = setup({});

expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
Array(10).forEach((_, index) => {
Expand All @@ -41,9 +51,7 @@ describe(DomainWorkflowsTable.name, () => {
).toBeInTheDocument();
});

act(() => {
fireEvent.click(screen.getByTestId('mock-end-message'));
});
await user.click(screen.getByTestId('mock-end-message'));

expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
Array(10).forEach((_, index) => {
Expand All @@ -53,23 +61,22 @@ describe(DomainWorkflowsTable.name, () => {
});
});

it('does not render if the initial call fails', async () => {
let renderErrorMessage;
try {
await act(async () => {
await setup({ errorCase: 'initial-fetch-error' });
});
} catch (error) {
if (error instanceof Error) {
renderErrorMessage = error.message;
}
}

expect(renderErrorMessage).toEqual('Request failed');
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 () => {
await setup({ errorCase: 'subsequent-fetch-error' });
const { user } = setup({ errorCase: 'subsequent-fetch-error' });

expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
Array(10).forEach((_, index) => {
Expand All @@ -78,17 +85,13 @@ describe(DomainWorkflowsTable.name, () => {
).toBeInTheDocument();
});

act(() => {
fireEvent.click(screen.getByTestId('mock-end-message'));
});
await user.click(screen.getByTestId('mock-end-message'));

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

act(() => {
fireEvent.click(screen.getByTestId('mock-end-message'));
});
await user.click(screen.getByTestId('mock-end-message'));

expect(await screen.findByText('Mock end message: OK')).toBeInTheDocument();
Array(10).forEach((_, index) => {
Expand All @@ -99,41 +102,57 @@ describe(DomainWorkflowsTable.name, () => {
});
});

async function setup({
function setup({
errorCase,
}: {
errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error';
errorCase?: 'initial-fetch-error' | 'subsequent-fetch-error' | 'no-workflows';
}) {
// TODO: @adhitya.mamallan - This is not type-safe, explore using a library such as nock or msw
const requestMock = jest.spyOn(requestModule, 'default') as jest.Mock;
const pages = generateWorkflowPages(2);
let currentEventIndex = 0;
const user = userEvent.setup();

render(<DomainWorkflowsTable domain="mock-domain" cluster="mock-cluster" />, {
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'],
});

if (errorCase === 'subsequent-fetch-error') {
requestMock
.mockResolvedValueOnce({
json: () => Promise.resolve(pages[0]),
})
.mockRejectedValueOnce(new Error('Request failed'))
.mockResolvedValueOnce({
json: () => Promise.resolve(pages[1]),
});
} else if (errorCase === 'initial-fetch-error') {
requestMock.mockRejectedValueOnce(new Error('Request failed'));
} else {
requestMock
.mockResolvedValueOnce({
json: () => Promise.resolve(pages[0]),
})
.mockResolvedValueOnce({
json: () => Promise.resolve(pages[1]),
});
}

render(
<Suspense>
<DomainWorkflowsTable domain="mock-domain" cluster="mock-cluster" />
</Suspense>
);
return { user };
}

// TODO @adhitya.mamallan - Explore using fakerjs.dev for cases like this
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export const PAGE_SIZE = 10;

export const NO_WORKFLOWS_ERROR_MESSAGE = 'Domain has no workflows';
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { styled as createStyled } from 'baseui';
import { styled as createStyled, withStyle } from 'baseui';

import PageSection from '@/components/page-section/page-section';

export const styled = {
TableContainer: createStyled('div', {
overflowX: 'auto',
}),
PageSection: withStyle(PageSection, () => ({
display: 'flex',
flexDirection: 'column',
flex: 1,
})),
ErrorPanelContainer: createStyled('div', ({ $theme }) => ({
padding: `${$theme.sizing.scale1200} 0px`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
})),
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use client';
import React, { useMemo } from 'react';
import React from 'react';

import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
import { useInfiniteQuery } from '@tanstack/react-query';
import queryString from 'query-string';

import PageSection from '@/components/page-section/page-section';
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 {
Expand All @@ -18,12 +19,10 @@ 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 {
NO_WORKFLOWS_ERROR_MESSAGE,
PAGE_SIZE,
} from './domain-workflows-table.constants';
import { PAGE_SIZE } from './domain-workflows-table.constants';
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 [queryParams, setQueryParams] = usePageQueryParams(
Expand All @@ -47,7 +46,8 @@ export default function DomainWorkflowsTable(props: Props) {
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = useSuspenseInfiniteQuery<ListWorkflowsResponse>({
refetch,
} = useInfiniteQuery<ListWorkflowsResponse>({
queryKey: ['listWorkflows', { ...props, ...requestQueryParams }],
queryFn: async ({ pageParam }) =>
request(
Expand All @@ -66,23 +66,33 @@ export default function DomainWorkflowsTable(props: Props) {
},
});

const workflows = useMemo(
() => data.pages.flatMap((page) => page.workflows ?? []),
[data]
);
if (isLoading) {
return <SectionLoadingIndicator />;
}

const workflows = data?.pages.flatMap((page) => page.workflows ?? []) ?? [];

if (workflows.length === 0) {
const errorPanelProps = getWorkflowsErrorPanelProps({
error,
areSearchParamsAbsent:
!queryParams.search &&
!queryParams.status &&
!queryParams.timeRangeStart &&
!queryParams.timeRangeEnd,
});

if (
!queryParams.search &&
!queryParams.status &&
!queryParams.timeRangeStart &&
!queryParams.timeRangeEnd &&
workflows.length === 0
) {
throw new Error(NO_WORKFLOWS_ERROR_MESSAGE);
if (errorPanelProps) {
return (
<styled.PageSection>
<ErrorPanel {...errorPanelProps} reset={refetch} />
</styled.PageSection>
);
}
}

return (
<PageSection>
<styled.PageSection>
<styled.TableContainer>
<Table
data={workflows}
Expand Down Expand Up @@ -111,6 +121,6 @@ export default function DomainWorkflowsTable(props: Props) {
}
/>
</styled.TableContainer>
</PageSection>
</styled.PageSection>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import getWorkflowsErrorPanelProps from '../get-workflows-error-panel-props';

describe(getWorkflowsErrorPanelProps.name, () => {
it('returns default error panel props for regular error', () => {
expect(
getWorkflowsErrorPanelProps({
error: new Error('Test error'),
areSearchParamsAbsent: false,
})
).toEqual({
message: 'Failed to fetch workflows',
actions: [{ kind: 'retry', label: 'Retry' }],
});
});

it('returns "not found" error panel props when search params are absent', () => {
expect(
getWorkflowsErrorPanelProps({
error: null,
areSearchParamsAbsent: true,
})
).toEqual({
message: 'No workflows found for this domain',
omitLogging: true,
actions: [
{
kind: 'link-external',
label: 'Get started on workflows',
link: 'https://cadenceworkflow.io/docs/concepts/workflows',
},
],
});
});

it('returns undefined in all other cases', () => {
expect(
getWorkflowsErrorPanelProps({
error: null,
areSearchParamsAbsent: false,
})
).toBeUndefined();
});
});
Loading

0 comments on commit 52dc521

Please sign in to comment.