Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch domain workflows when a query is provided #742

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { createElement } from 'react';

import FormattedDate from '@/components/formatted-date/formatted-date';
import Link from '@/components/link/link';
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',
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 DomainWorkflowsQueryTableConfig;

export default domainWorkflowsQueryTableConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -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 domainWorkflowsSearchTableConfig = [
{
name: 'Workflow ID',
id: 'WorkflowID',
Expand Down Expand Up @@ -57,4 +57,4 @@ const domainWorkflowsTableConfig = [
},
] as const satisfies TableConfig<DomainWorkflow>;

export default domainWorkflowsTableConfig;
export default domainWorkflowsSearchTableConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,36 @@ 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(<DomainWorkflowsHeader />);
render(
<DomainWorkflowsHeader domain="mock_domain" cluster="mock_cluster" />
);

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(<DomainWorkflowsHeader />);
render(
<DomainWorkflowsHeader domain="mock_domain" cluster="mock_cluster" />
);

expect(await screen.findByText('Filter search')).toBeInTheDocument();
expect(await screen.findByText('Filter toggle')).toBeInTheDocument();
});

it('renders page filters when filter toggle is clicked', async () => {
const user = userEvent.setup();
render(<DomainWorkflowsHeader />);
render(
<DomainWorkflowsHeader domain="mock_domain" cluster="mock_cluster" />
);

const filterToggle = await screen.findByText('Filter toggle');
await user.click(filterToggle);
Expand All @@ -77,14 +89,18 @@ describe(DomainWorkflowsHeader.name, () => {
setQueryParams: mockSetQueryParams,
});

render(<DomainWorkflowsHeader />);
render(
<DomainWorkflowsHeader domain="mock_domain" cluster="mock_cluster" />
);

expect(await screen.findByText('Query')).toBeInTheDocument();
});

it('toggles input type when segmented control is used', async () => {
const user = userEvent.setup();
render(<DomainWorkflowsHeader />);
render(
<DomainWorkflowsHeader domain="mock_domain" cluster="mock_cluster" />
);

const queryButton = await screen.findByText('Search');
await user.click(queryButton);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 } =
Expand All @@ -23,6 +25,8 @@ export default function DomainWorkflowsHeader() {
pageQueryParamsConfig: domainPageQueryParamsConfig,
});

const { refetch } = useListWorkflows({ domain, cluster });

return (
<styled.HeaderContainer>
<styled.InputContainer>
Expand Down Expand Up @@ -54,6 +58,7 @@ export default function DomainWorkflowsHeader() {
<DomainWorkflowsQueryInput
value={queryParams.query}
setValue={(v) => setQueryParams({ query: v })}
refetchQuery={refetch}
/>
) : (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ import { type ListWorkflowsRequestQueryParams } from '@/route-handlers/list-work

export type DomainWorkflowsHeaderInputType =
ListWorkflowsRequestQueryParams['inputType'];

export type Props = {
domain: string;
cluster: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<DomainWorkflowsQueryInput
value={startValue ?? ''}
setValue={mockSetValue}
refetchQuery={mockRefetch}
/>
);

return { mockSetValue, user };
return { mockSetValue, mockRefetch, user };
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>('');

useEffect(() => {
Expand All @@ -30,7 +34,9 @@ export default function DomainWorkflowsQueryInput({ value, setValue }: Props) {
clearOnEscape
/>
<Button
onClick={() => setValue(queryText || undefined)}
onClick={() =>
isQueryUnchanged ? refetchQuery() : setValue(queryText || undefined)
}
overrides={overrides.runButton}
startEnhancer={isQueryUnchanged ? <MdRefresh /> : <MdPlayArrow />}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type Props = {
value: string;
setValue: (v: string | undefined) => void;
refetchQuery: () => void;
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { HttpResponse } from 'msw';

import { render, screen, userEvent } from '@/test-utils/rtl';
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';

Expand All @@ -14,9 +16,26 @@ jest.mock('@/components/error-panel/error-panel', () =>
);

jest.mock('../helpers/get-workflows-error-panel-props', () =>
jest.fn().mockImplementation(({ error }) => ({
message: error ? 'Error loading workflows' : 'No workflows found',
}))
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(
Expand All @@ -41,6 +60,10 @@ jest.mock('@/hooks/use-page-query-params/use-page-query-params', () =>
);

describe(DomainWorkflowsTable.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});

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

Expand Down Expand Up @@ -69,12 +92,30 @@ describe(DomainWorkflowsTable.name, () => {
).toBeInTheDocument();
});

it('renders error panel if no workflows are found', async () => {
it('renders error panel in search mode if no workflows are found', async () => {
setup({ errorCase: 'no-workflows' });

expect(await screen.findByText('No workflows found')).toBeInTheDocument();
});

it('renders empty table in query mode if no workflows are found', async () => {
jest
.spyOn(usePageQueryParamsModule, 'default')
.mockReturnValue([
{ ...mockDomainWorkflowsQueryParamsValues, inputType: 'query' },
mockSetQueryParams,
]);

setup({ errorCase: 'no-workflows' });
const progressbar = await screen.findByRole('progressbar');

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' });

Expand Down Expand Up @@ -123,7 +164,10 @@ function setup({

switch (errorCase) {
case 'no-workflows':
return HttpResponse.json({ workflows: [], nextPage: undefined });
return HttpResponse.json({
workflows: [],
nextPage: undefined,
});
case 'initial-fetch-error':
return HttpResponse.json(
{ message: 'Request failed' },
Expand Down
Loading
Loading