Skip to content

Commit b1dee1e

Browse files
authored
[TSPS-634] Add filtering controls to Teaspoons Job History page (#5449)
1 parent bec7ec3 commit b1dee1e

31 files changed

+724
-108
lines changed

src/libs/ajax/teaspoons/Teaspoons.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
StartPipelineResponse,
1313
UserPipelineQuotaDetails,
1414
} from 'src/libs/ajax/teaspoons/teaspoons-models';
15+
import { FilterValues } from 'src/pages/scientificServices/pipelines/tabs/history/controls/TableFilters';
1516

1617
export const Teaspoons = (signal?: AbortSignal) => ({
1718
/* Lists all pipelines available to the user */
@@ -40,17 +41,25 @@ export const Teaspoons = (signal?: AbortSignal) => ({
4041
pageSize: number,
4142
pageNumber: number,
4243
sortProperty?: string,
43-
sortDirection?: string
44+
sortDirection?: string,
45+
filters?: FilterValues
4446
): Promise<GetPipelineRunsResponse> => {
47+
const { status, description, jobId, pipelineName } = filters || {};
48+
4549
const queryString = qs.stringify(
4650
{
4751
pageSize,
4852
pageNumber,
4953
...(sortProperty ? { sortProperty } : {}),
5054
...(sortDirection ? { sortDirection } : {}),
55+
...(status ? { status } : {}),
56+
...(description ? { description } : {}),
57+
...(jobId ? { jobId } : {}),
58+
...(pipelineName ? { pipelineName } : {}),
5159
},
5260
{ addQueryPrefix: true }
5361
);
62+
5463
const res = await fetchTeaspoons(`pipelineruns/v2/pipelineruns${queryString}`, _.merge(authOpts(), { signal }));
5564
return res.json();
5665
},

src/libs/ajax/teaspoons/teaspoons-models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export interface PipelineRun {
7171

7272
export interface GetPipelineRunsResponse {
7373
totalResults: number;
74+
totalFilteredResults: number;
7475
pageToken: string;
7576
results: PipelineRun[];
7677
}

src/pages/scientificServices/NavPaths.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { CliAuth } from 'src/pages/scientificServices/cli-auth/CliAuth';
2-
import { About } from 'src/pages/scientificServices/pipelines/views/About';
3-
import { JobHistory } from 'src/pages/scientificServices/pipelines/views/JobHistory';
4-
import { RunJob } from 'src/pages/scientificServices/pipelines/views/RunJob';
2+
import { About } from 'src/pages/scientificServices/pipelines/tabs/about/About';
3+
import { JobHistory } from 'src/pages/scientificServices/pipelines/tabs/history/JobHistory';
4+
import { RunJob } from 'src/pages/scientificServices/pipelines/tabs/run/RunJob';
55

66
export const navPaths = [
77
// For now, redirect /pipelines to the Imputation home page
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useEffect, useState } from 'react';
2+
import { Teaspoons } from 'src/libs/ajax/teaspoons/Teaspoons';
3+
import { Pipeline } from 'src/libs/ajax/teaspoons/teaspoons-models';
4+
import { notify } from 'src/libs/notifications';
5+
import { useCancellation } from 'src/libs/react-utils';
6+
7+
export interface UsePipelinesListResult {
8+
pipelines: Pipeline[];
9+
isLoading: boolean;
10+
error: Error | undefined;
11+
}
12+
13+
export const usePipelinesList = (): UsePipelinesListResult => {
14+
const signal = useCancellation();
15+
const [pipelines, setPipelines] = useState<Pipeline[]>([]);
16+
const [isLoading, setIsLoading] = useState<boolean>(true);
17+
const [error, setError] = useState<Error | undefined>(undefined);
18+
19+
const fetchPipelines = async () => {
20+
setIsLoading(true);
21+
setError(undefined);
22+
23+
try {
24+
const response = await Teaspoons(signal).getPipelines();
25+
setPipelines(response.results);
26+
} catch (err) {
27+
const error = err instanceof Error ? err : new Error('Failed to fetch pipelines');
28+
setError(error);
29+
notify('error', error.message);
30+
setPipelines([]);
31+
} finally {
32+
setIsLoading(false);
33+
}
34+
};
35+
36+
useEffect(() => {
37+
fetchPipelines();
38+
}, [signal]); // eslint-disable-line react-hooks/exhaustive-deps
39+
40+
return {
41+
pipelines,
42+
isLoading,
43+
error,
44+
};
45+
};

src/pages/scientificServices/pipelines/views/About.tsx renamed to src/pages/scientificServices/pipelines/tabs/about/About.tsx

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,12 @@
11
import { Spinner } from '@terra-ui-packages/components';
2-
import React, { useEffect, useState } from 'react';
2+
import React from 'react';
33
import FooterWrapper from 'src/components/FooterWrapper';
4-
import { Teaspoons } from 'src/libs/ajax/teaspoons/Teaspoons';
5-
import { PipelineList } from 'src/libs/ajax/teaspoons/teaspoons-models';
64
import { pipelinesTopBar } from 'src/pages/scientificServices/pipelines/common/scientific-services-common';
5+
import { usePipelinesList } from 'src/pages/scientificServices/pipelines/hooks/usePipelinesList';
76
import { AoUStylizedString } from 'src/pages/scientificServices/pipelines/utils/AoUStylizedString';
87

98
export const About = () => {
10-
const [pipelines, setPipelines] = useState<PipelineList | null>(null);
11-
const [loading, setLoading] = useState(true);
12-
const [error, setError] = useState<string | null>(null);
13-
14-
useEffect(() => {
15-
const fetchPipelines = async () => {
16-
try {
17-
const pipelineData = await Teaspoons().getPipelines();
18-
setPipelines(pipelineData);
19-
setLoading(false);
20-
} catch (e) {
21-
setError('Failed to load pipeline information');
22-
setLoading(false);
23-
}
24-
};
25-
26-
fetchPipelines();
27-
}, []);
9+
const { isLoading, error, pipelines } = usePipelinesList();
2810

2911
return (
3012
<FooterWrapper alwaysShow>
@@ -33,25 +15,24 @@ export const About = () => {
3315
<h1>Scientific Services from the Broad Data Sciences Platform</h1>
3416
<h2 style={{ marginTop: '2rem' }}>Pipelines</h2>
3517

36-
{loading && <Spinner />}
37-
38-
{error && <div style={{ color: 'red' }}>{error}</div>}
39-
40-
{pipelines &&
41-
pipelines.results.map((pipeline) => (
42-
<div key={pipeline.pipelineName}>
43-
<h3>
44-
<AoUStylizedString text={pipeline.displayName} />
45-
</h3>
46-
<div style={{ width: '50%' }}>
47-
{pipeline.description ? (
48-
<AoUStylizedString text={pipeline.description} />
49-
) : (
50-
<em>No description available</em>
51-
)}
52-
</div>
18+
{isLoading && <Spinner />}
19+
20+
{error && <div style={{ color: 'red' }}>{error.message}</div>}
21+
22+
{pipelines?.map((pipeline) => (
23+
<div key={pipeline.pipelineName}>
24+
<h3>
25+
<AoUStylizedString text={pipeline.displayName} />
26+
</h3>
27+
<div style={{ width: '50%' }}>
28+
{pipeline.description ? (
29+
<AoUStylizedString text={pipeline.description} />
30+
) : (
31+
<em>No description available</em>
32+
)}
5333
</div>
54-
))}
34+
</div>
35+
))}
5536

5637
<h2 style={{ marginTop: '2rem' }}>User Documentation</h2>
5738
<div style={{ marginTop: '1rem' }}>

src/pages/scientificServices/pipelines/views/JobHistory.test.tsx renamed to src/pages/scientificServices/pipelines/tabs/history/JobHistory.test.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ import userEvent from '@testing-library/user-event';
44
import React from 'react';
55
import { Teaspoons, TeaspoonsContract } from 'src/libs/ajax/teaspoons/Teaspoons';
66
import { PipelineRun } from 'src/libs/ajax/teaspoons/teaspoons-models';
7-
import { mockPipelineRun } from 'src/pages/scientificServices/pipelines/utils/mock-utils';
8-
import { PREPARING_JOB_CUTOFF_HOURS } from 'src/pages/scientificServices/pipelines/views/JobHistory';
7+
import { usePipelinesList } from 'src/pages/scientificServices/pipelines/hooks/usePipelinesList';
8+
import { mockPipeline, mockPipelineRun } from 'src/pages/scientificServices/pipelines/utils/mock-utils';
99
import { asMockedFn, partial, renderWithAppContexts as render } from 'src/testing/test-utils';
1010

11-
import { JobHistory } from './JobHistory';
11+
import { JobHistory, PREPARING_JOB_CUTOFF_HOURS } from './JobHistory';
1212

1313
jest.mock('src/libs/ajax/teaspoons/Teaspoons');
1414

15+
jest.mock('src/pages/scientificServices/pipelines/hooks/usePipelinesList');
16+
17+
const mockUsePipelinesList = usePipelinesList as jest.MockedFunction<typeof usePipelinesList>;
18+
1519
jest.mock('react-virtualized', () => {
1620
const actual = jest.requireActual('react-virtualized');
1721

@@ -44,6 +48,11 @@ beforeEach(() => {
4448
get: jest.fn().mockReturnValue('1234'),
4549
},
4650
} as any);
51+
mockUsePipelinesList.mockReturnValue({
52+
pipelines: [mockPipeline('array_imputation')],
53+
isLoading: false,
54+
error: undefined,
55+
});
4756
});
4857

4958
describe('job history table', () => {
@@ -99,15 +108,15 @@ describe('job history table', () => {
99108
expect(screen.queryAllByText(/Test Job/)).toHaveLength(2);
100109

101110
// Verify initial call to API without sort parameters defaults to (created, desc)
102-
expect(Teaspoons().getAllPipelineRuns).toHaveBeenNthCalledWith(1, 10, 1, 'created', 'desc');
111+
expect(Teaspoons().getAllPipelineRuns).toHaveBeenNthCalledWith(1, 10, 1, 'created', 'desc', {});
103112

104113
// Click Quota Used header to sort ascending
105114
const jobIdHeader = screen.getByText('Quota Used');
106115
expect(jobIdHeader).toBeInTheDocument();
107116
await userEvent.click(jobIdHeader);
108117

109118
// Verify that API was called with correct sort parameters (quotaConsumed, asc)
110-
expect(Teaspoons().getAllPipelineRuns).toHaveBeenNthCalledWith(2, 10, 1, 'quotaConsumed', 'asc');
119+
expect(Teaspoons().getAllPipelineRuns).toHaveBeenNthCalledWith(2, 10, 1, 'quotaConsumed', 'asc', {});
111120
});
112121

113122
it('displays pipeline name without version when version is not available', async () => {

0 commit comments

Comments
 (0)