Skip to content

Commit f2124d4

Browse files
committed
Add test plan test cases pagination
1 parent e149fbd commit f2124d4

File tree

19 files changed

+286
-116
lines changed

19 files changed

+286
-116
lines changed

app/src/common/urls.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,8 @@ export const URLS = {
381381
`${urlBase}project/${projectKey}/tms/test-case/batch/duplicate`,
382382
testCases: (projectKey, query = {}) =>
383383
`${urlBase}project/${projectKey}/tms/test-case${getQueryParams(query)}`,
384-
testPlanTestCases: (projectKey, id) =>
385-
`${urlBase}project/${projectKey}/tms/test-plan/${id}/test-case`,
384+
testPlanTestCases: (projectKey, id, query = {}) =>
385+
`${urlBase}project/${projectKey}/tms/test-plan/${id}/test-case${getQueryParams(query)}`,
386386
testPlan: (projectKey, query = {}) =>
387387
`${urlBase}project/${projectKey}/tms/test-plan${getQueryParams(query)}`,
388388
testPlanTestCasesBatch: (projectKey, testPlanId) =>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2025 EPAM Systems
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { getStorageItem } from 'common/utils/storageUtils';
18+
import { queryParamsType } from 'types/common';
19+
20+
interface UserSettings {
21+
[key: string]: string | number | undefined;
22+
}
23+
24+
type RouterParams = {
25+
limit?: number | string;
26+
offset?: number | string;
27+
};
28+
29+
type GetRouterParamsType = {
30+
state: {
31+
user?: {
32+
info?: {
33+
userId?: string;
34+
};
35+
};
36+
location: {
37+
query?: queryParamsType;
38+
};
39+
};
40+
namespace: string;
41+
defaultParams: queryParamsType;
42+
};
43+
44+
export const getRouterParams = ({
45+
state,
46+
namespace,
47+
defaultParams,
48+
}: GetRouterParamsType): RouterParams => {
49+
const userData = getStorageItem(`${state.user?.info?.userId}_settings`) as
50+
| UserSettings
51+
| undefined;
52+
const savedLimit = userData ? userData[`${namespace}PageSize`] : undefined;
53+
const query = state.location?.query;
54+
const { offset } = query || defaultParams;
55+
const limit = query?.limit || savedLimit || defaultParams.limit;
56+
57+
return { offset, limit };
58+
};

app/src/common/utils/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,4 @@ export { delay, delayedPut } from './delay';
6868
export { createClassnames } from './createClassnames';
6969
export { commonValidators } from './validation';
7070
export { copyToClipboard } from './clipboard';
71+
export { getRouterParams } from './getRouterParams';

app/src/controllers/pages/typed-selectors.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,23 @@
1616

1717
import { isString } from 'es-toolkit';
1818

19-
type LocationInfo = {
19+
export type LocationInfo = {
2020
payload: {
2121
testCasePageRoute: string;
2222
organizationSlug: string;
2323
projectSlug: string;
24+
testPlanId?: number;
2425
};
2526
type?: string;
2627
query?: {
2728
offset: string;
2829
limit: string;
2930
};
31+
prev?: {
32+
payload?: {
33+
testPlanId?: number;
34+
};
35+
};
3036
};
3137

3238
type State = {

app/src/controllers/testPlan/actionCreators.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
import { GET_TEST_PLANS, GET_TEST_PLAN } from './constants';
1818

1919
export interface GetTestPlansParams {
20-
offset?: number;
21-
limit?: number;
20+
offset?: string | number;
21+
limit?: string | number;
2222
}
2323

2424
export interface GetTestPlanParams {
2525
testPlanId: string | number;
26+
offset?: string | number;
27+
limit?: string | number;
2628
}
2729

2830
export const getTestPlansAction = (params?: GetTestPlansParams) => ({

app/src/controllers/testPlan/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export const defaultQueryParams = {
2929
offset: 0,
3030
};
3131
export const defaultSortParam = 'createdDate,desc';
32+
export const defaultTestPlanTestCasesQueryParams = {
33+
limit: 50,
34+
offset: 0,
35+
};
3236

3337
export type TestPlanDto = {
3438
id: number;

app/src/controllers/testPlan/reducer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const reducer = combineReducers({
3535
testPlanFolders: fetchReducer(TEST_PLAN_FOLDERS_NAMESPACE, { initialState: null }),
3636
testPlanTestCases: fetchReducer(TEST_PLAN_TEST_CASES_NAMESPACE, { initialState: null }),
3737
isLoadingActive: loadingReducer(ACTIVE_TEST_PLAN_NAMESPACE),
38+
isLoadingTestPlanTestCases: loadingReducer(TEST_PLAN_TEST_CASES_NAMESPACE),
3839
});
3940

4041
export const testPlanReducer = createPageScopedReducer(reducer, [

app/src/controllers/testPlan/sagas.ts

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import { FETCH_START } from 'controllers/fetch/constants';
2424
import { BaseAppState } from 'types/store';
2525
import { showErrorNotification } from 'controllers/notification';
2626
import { projectKeySelector } from 'controllers/project';
27-
import { PROJECT_TEST_PLANS_PAGE } from 'controllers/pages';
27+
import { locationSelector, PROJECT_TEST_PLANS_PAGE } from 'controllers/pages';
28+
import { LocationInfo } from 'controllers/pages/typed-selectors';
2829

2930
import {
3031
GET_TEST_PLANS,
@@ -37,6 +38,7 @@ import {
3738
ACTIVE_TEST_PLAN_NAMESPACE,
3839
TEST_PLAN_FOLDERS_NAMESPACE,
3940
TEST_PLAN_TEST_CASES_NAMESPACE,
41+
defaultTestPlanTestCasesQueryParams,
4042
} from './constants';
4143
import { GetTestPlansParams, GetTestPlanParams } from './actionCreators';
4244
import { Page } from '../../types/common';
@@ -87,39 +89,62 @@ function* getTestPlans(action: GetTestPlansAction): Generator {
8789
}
8890

8991
function* getTestPlan(action: GetTestPlanAction): Generator {
92+
const projectKey = (yield select(projectKeySelector)) as string;
93+
const location = (yield select(locationSelector)) as LocationInfo;
94+
const { testPlanId, offset, limit } = action.payload;
95+
const params = {
96+
limit: limit || defaultTestPlanTestCasesQueryParams.limit,
97+
offset: offset || defaultTestPlanTestCasesQueryParams.offset,
98+
};
99+
90100
try {
91-
const projectKey = (yield select(projectKeySelector)) as string;
92-
const { testPlanId } = action.payload;
101+
if (
102+
location.query?.offset !== String(offset) ||
103+
location.query?.limit !== String(limit) ||
104+
String(location?.prev?.payload?.testPlanId) !== String(location?.payload?.testPlanId)
105+
) {
106+
yield put({
107+
type: FETCH_START,
108+
payload: { projectKey },
109+
meta: { namespace: ACTIVE_TEST_PLAN_NAMESPACE },
110+
});
111+
112+
const data = (yield call(fetch, URLS.testPlanById(projectKey, testPlanId))) as TestPlanDto;
113+
const planFolders = (yield call(
114+
fetch,
115+
URLS.testFolders(projectKey, { 'filter.eq.testPlanId': testPlanId }),
116+
)) as TestPlanFoldersDto;
117+
118+
yield put(fetchSuccessAction(ACTIVE_TEST_PLAN_NAMESPACE, data));
119+
yield put(fetchSuccessAction(TEST_PLAN_FOLDERS_NAMESPACE, planFolders));
120+
}
121+
} catch (error) {
122+
const locationPayload = (yield select(
123+
(state: BaseAppState) => state.location?.payload,
124+
)) as BaseAppState['location']['payload'];
125+
126+
yield put(fetchErrorAction(ACTIVE_TEST_PLAN_NAMESPACE, error));
127+
yield put({
128+
type: PROJECT_TEST_PLANS_PAGE,
129+
payload: locationPayload,
130+
});
131+
}
93132

133+
try {
94134
yield put({
95135
type: FETCH_START,
96136
payload: { projectKey },
97-
meta: { namespace: ACTIVE_TEST_PLAN_NAMESPACE },
137+
meta: { namespace: TEST_PLAN_TEST_CASES_NAMESPACE },
98138
});
99139

100-
const data = (yield call(fetch, URLS.testPlanById(projectKey, testPlanId))) as TestPlanDto;
101-
const planFolders = (yield call(
102-
fetch,
103-
URLS.testFolders(projectKey, { 'filter.eq.testPlanId': testPlanId }),
104-
)) as TestPlanFoldersDto;
105140
const planTestCases = (yield call(
106141
fetch,
107-
URLS.testPlanTestCases(projectKey, testPlanId),
142+
URLS.testPlanTestCases(projectKey, testPlanId, params),
108143
)) as TestPlanTestCaseDto;
109144

110-
yield put(fetchSuccessAction(ACTIVE_TEST_PLAN_NAMESPACE, data));
111-
yield put(fetchSuccessAction(TEST_PLAN_FOLDERS_NAMESPACE, planFolders));
112145
yield put(fetchSuccessAction(TEST_PLAN_TEST_CASES_NAMESPACE, planTestCases));
113146
} catch (error) {
114-
const locationPayload = (yield select(
115-
(state: BaseAppState) => state.location?.payload,
116-
)) as BaseAppState['location']['payload'];
117-
118-
yield put(fetchErrorAction(ACTIVE_TEST_PLAN_NAMESPACE, error));
119-
yield put({
120-
type: PROJECT_TEST_PLANS_PAGE,
121-
payload: locationPayload,
122-
});
147+
yield put(fetchErrorAction(TEST_PLAN_TEST_CASES_NAMESPACE, error));
123148
}
124149
}
125150

app/src/controllers/testPlan/selectors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface TestPlanState {
3030
testPlanFolders?: TestPlanFoldersDto | null;
3131
testPlanTestCases?: TestPlanTestCaseDto | null;
3232
isLoadingActive?: boolean;
33+
isLoadingTestPlanTestCases?: boolean;
3334
}
3435

3536
interface RootState {
@@ -59,6 +60,9 @@ export const EMPTY_TEST_CASES: ExtendedTestCase[] = [];
5960
export const testPlanTestCasesSelector = (state: RootState) =>
6061
testPlanSelector(state).testPlanTestCases?.content || EMPTY_TEST_CASES;
6162

63+
export const testPlanTestCasesPageSelector = (state: RootState) =>
64+
testPlanSelector(state).testPlanTestCases?.page;
65+
6266
export const testPlanTransformedFoldersSelector = (state: RootState) =>
6367
transformFoldersToDisplay(testPlanFoldersSelector(state));
6468

app/src/hooks/useTypedSelector.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ export const useTestPlanLoading = () =>
6868
export const useActiveTestPlanLoading = () =>
6969
useTestPlanSelector((state) => state.testPlan?.isLoadingActive || false);
7070

71+
export const useTestPlanTestCasesLoading = () =>
72+
useTestPlanSelector((state) => state.testPlan?.isLoadingTestPlanTestCases || false);
73+
7174
export const useTestPlanById = (testPlanId: string) =>
7275
useTestPlanSelector(testPlanByIdSelector(testPlanId));
7376

0 commit comments

Comments
 (0)