Skip to content

Commit 1476e7f

Browse files
authored
feat: accepting sortBy when fetching testing activity data (#2237)
1 parent 3a59468 commit 1476e7f

File tree

11 files changed

+357
-166
lines changed

11 files changed

+357
-166
lines changed

frontend/src/api-service/consep/searchTestingActivitiesAPI.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@ import { PaginatedTestingSearchResponseType, TestCodeType } from '../../types/co
66

77
export const searchTestingActivities = (
88
filter: ActivitySearchRequest,
9-
page: number = 0,
9+
sortBy?: string,
10+
sortDirection: 'asc' | 'desc' = 'asc',
11+
unpaged: boolean = false,
1012
size: number = 20,
11-
unpaged: boolean = false
13+
page: number = 0
1214
) => {
13-
const url = `${ApiConfig.searchTestActivities}/search?page=${page}&size=${size}&unpaged=${unpaged}`;
14-
return api.post(url, filter).then((res): PaginatedTestingSearchResponseType => res.data);
15+
let url = `${ApiConfig.searchTestActivities}/search?page=${page}&size=${size}&unpaged=${unpaged}`;
16+
17+
if (sortBy) {
18+
url += `&sortBy=${encodeURIComponent(sortBy)}&sortDirection=${sortDirection}`;
19+
}
20+
21+
return api.post(url, filter)
22+
.then((res): PaginatedTestingSearchResponseType => res.data);
1523
};
1624

1725
export const getTestTypeCodes = () => {

frontend/src/components/GenericTable/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ type Props<T extends Record<string, any>> = {
2020
pageIndex?: number;
2121
pageSize?: number;
2222
enableSorting?: boolean;
23+
manualSorting?: boolean;
24+
sorting?: { id: string; desc: boolean }[];
25+
onSortingChange?: (sorting: { id: string; desc: boolean }[]) => void;
2326
enableFilters?: boolean;
2427
enableHiding?: boolean;
2528
enableRowSelection?: boolean;
@@ -72,6 +75,9 @@ const GenericTable = <T extends Record<string, any>>({
7275
pageSize = 20,
7376
enableHiding = false,
7477
enableSorting = false,
78+
manualSorting = false,
79+
sorting,
80+
onSortingChange,
7581
enableFilters = false,
7682
enableRowSelection = false,
7783
enableRowActions = false,
@@ -97,7 +103,8 @@ const GenericTable = <T extends Record<string, any>>({
97103
initialState,
98104
state: {
99105
isLoading,
100-
pagination: { pageIndex, pageSize }
106+
pagination: { pageIndex, pageSize },
107+
sorting
101108
},
102109
enablePagination,
103110
manualPagination,
@@ -185,6 +192,13 @@ const GenericTable = <T extends Record<string, any>>({
185192
}
186193
},
187194
enableSorting,
195+
manualSorting,
196+
onSortingChange: (updaterOrValue) => {
197+
const newSorting = typeof updaterOrValue === 'function'
198+
? updaterOrValue(basicTable.getState().sorting)
199+
: updaterOrValue;
200+
onSortingChange?.(newSorting);
201+
},
188202
enableFilters,
189203
enableHiding,
190204
enableColumnFilters,

frontend/src/views/CONSEP/TestingActivities/TestSearch/TestListTable.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ import type {
1313
type TestListTableProp = {
1414
data: TestingSearchResponseType[];
1515
paginationInfo: PaginationInfoType;
16+
sorting?: { id: string; desc: boolean }[];
1617
onExportData: () => void;
1718
isLoading?: boolean;
1819
onPageChange?: (pageIndex: number, pageSize: number) => void;
20+
onSortingChange?: (sorting: { id: string; desc: boolean }[]) => void;
1921
};
2022

2123
const TestListTable = ({
2224
data,
2325
isLoading = false,
2426
paginationInfo,
2527
onPageChange,
26-
onExportData
28+
onExportData,
29+
onSortingChange,
30+
sorting
2731
}: TestListTableProp) => {
2832
const tableBodyRef = useRef<HTMLTableSectionElement>(null);
2933

@@ -83,6 +87,10 @@ const TestListTable = ({
8387
onPaginationChange={onPageChange}
8488
enableRowSelection
8589
enableColumnPinning
90+
enableSorting
91+
manualSorting
92+
sorting={sorting}
93+
onSortingChange={onSortingChange}
8694
isLoading={isLoading}
8795
tableBodyRef={tableBodyRef}
8896
hideToolbar={false}

frontend/src/views/CONSEP/TestingActivities/TestSearch/constants.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -168,25 +168,25 @@ export const getTestingActivityListColumns = (): MRT_ColumnDef<TestingSearchResp
168168
accessorKey: 'species',
169169
header: 'Sp',
170170
enableEditing: false,
171-
...tableCellProps(50, 'left')
171+
...tableCellProps(80, 'left')
172172
},
173173
{
174174
accessorKey: 'testRank',
175175
header: 'Rank',
176176
enableEditing: false,
177-
...tableCellProps(60, 'left')
177+
...tableCellProps(90, 'left')
178178
},
179179
{
180180
accessorKey: 'testCategoryCd',
181181
header: 'Category',
182182
enableEditing: false,
183-
...tableCellProps(90, 'left')
183+
...tableCellProps(120, 'left')
184184
},
185185
{
186186
accessorKey: 'activityId',
187187
header: 'Activity',
188188
enableEditing: false,
189-
...tableCellProps(80, 'left')
189+
...tableCellProps(110, 'left')
190190
},
191191
{
192192
header: 'Result',
@@ -205,7 +205,7 @@ export const getTestingActivityListColumns = (): MRT_ColumnDef<TestingSearchResp
205205
}
206206
},
207207
enableEditing: false,
208-
...tableCellProps(70),
208+
...tableCellProps(100),
209209
Cell: ({ cell }) => {
210210
const value = cell.getValue<number | null>();
211211
return value != null ? `${value}%` : '';
@@ -215,13 +215,13 @@ export const getTestingActivityListColumns = (): MRT_ColumnDef<TestingSearchResp
215215
accessorKey: 'pv',
216216
header: 'PV',
217217
enableEditing: false,
218-
...tableCellProps(90)
218+
...tableCellProps(120)
219219
},
220220
{
221221
accessorKey: 'currentTestInd',
222222
header: 'Curr',
223223
enableEditing: false,
224-
...tableCellProps(80, 'left'),
224+
...tableCellProps(110, 'left'),
225225
Cell: ({ cell }: { cell: MRT_Cell<TestingSearchResponseType> }) => {
226226
const value = cell.getValue();
227227
return value === -1 ? (
@@ -235,7 +235,7 @@ export const getTestingActivityListColumns = (): MRT_ColumnDef<TestingSearchResp
235235
accessorKey: 'testCompleteInd',
236236
header: 'Com',
237237
enableEditing: false,
238-
...tableCellProps(80, 'left'),
238+
...tableCellProps(110, 'left'),
239239
Cell: ({ cell }: { cell: MRT_Cell<TestingSearchResponseType> }) => {
240240
const value = cell.getValue();
241241
return value === -1 ? (
@@ -249,7 +249,7 @@ export const getTestingActivityListColumns = (): MRT_ColumnDef<TestingSearchResp
249249
accessorKey: 'acceptResultInd',
250250
header: 'Act',
251251
enableEditing: false,
252-
...tableCellProps(80, 'left'),
252+
...tableCellProps(110, 'left'),
253253
Cell: ({ cell }: { cell: MRT_Cell<TestingSearchResponseType> }) => {
254254
const value = cell.getValue();
255255
return value === -1 ? (
@@ -263,7 +263,7 @@ export const getTestingActivityListColumns = (): MRT_ColumnDef<TestingSearchResp
263263
accessorKey: 'significntStsInd',
264264
header: 'Sig',
265265
enableEditing: false,
266-
...tableCellProps(80, 'left'),
266+
...tableCellProps(110, 'left'),
267267
Cell: ({ cell }: { cell: MRT_Cell<TestingSearchResponseType> }) => {
268268
const value = cell.getValue();
269269
return value === -1 ? (
@@ -277,28 +277,28 @@ export const getTestingActivityListColumns = (): MRT_ColumnDef<TestingSearchResp
277277
accessorKey: 'seedWithdrawalDate',
278278
header: 'Wdrwl Date',
279279
enableEditing: false,
280-
...tableCellProps(120, 'left'),
280+
...tableCellProps(150, 'left'),
281281
Cell: ({ cell }) => formatDateCell(cell.getValue<string | null>())
282282
},
283283
{
284284
accessorKey: 'revisedEndDt',
285285
header: 'Sch End Date',
286286
enableEditing: false,
287-
...tableCellProps(120, 'left'),
287+
...tableCellProps(150, 'left'),
288288
Cell: ({ cell }) => formatDateCell(cell.getValue<string | null>())
289289
},
290290
{
291291
accessorKey: 'actualBeginDtTm',
292292
header: 'Start Date',
293293
enableEditing: false,
294-
...tableCellProps(120, 'left'),
294+
...tableCellProps(150, 'left'),
295295
Cell: ({ cell }) => formatDateCell(cell.getValue<string | null>())
296296
},
297297
{
298298
accessorKey: 'actualEndDtTm',
299299
header: 'End Date',
300300
enableEditing: false,
301-
...tableCellProps(120, 'left'),
301+
...tableCellProps(150, 'left'),
302302
Cell: ({ cell }) => formatDateCell(cell.getValue<string | null>())
303303
},
304304

frontend/src/views/CONSEP/TestingActivities/TestSearch/definitions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ export type ActivitySearchValidation = {
4040
requestYear: ValidationErrorType;
4141
orchardId: ValidationErrorType;
4242
};
43+
44+
export type Sorting = {
45+
id: string;
46+
desc: boolean;
47+
};

frontend/src/views/CONSEP/TestingActivities/TestSearch/index.tsx

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import {
4040
formatExportData, columnVisibilityLocalStorageKey
4141
} from './constants';
4242
import { THREE_HALF_HOURS, THREE_HOURS } from '../../../../config/TimeUnits';
43-
import { ActivitySearchRequest, ActivitySearchValidation } from './definitions';
43+
import { ActivitySearchRequest, ActivitySearchValidation, Sorting } from './definitions';
4444
import './styles.scss';
4545

4646
const csvConfig = mkConfig({
@@ -55,6 +55,7 @@ const TestSearch = () => {
5555
const [searchParams, setSearchParams] = useState<ActivitySearchRequest>(
5656
{}
5757
);
58+
const [sorting, setSorting] = useState<Sorting[]>([]);
5859
const [rawLotInput, setRawLotInput] = useState('');
5960
const [validateSearch, setValidateSearch] = useState<ActivitySearchValidation>(
6061
iniActSearchValidation
@@ -89,12 +90,16 @@ const TestSearch = () => {
8990
mutationFn: ({
9091
filter,
9192
page = 0,
92-
size = 100
93+
size = 100,
94+
sortBy,
95+
sortDirection
9396
}: {
9497
filter: ActivitySearchRequest;
9598
page?: number;
9699
size?: number;
97-
}) => searchTestingActivities(filter, page, size),
100+
sortBy?: string;
101+
sortDirection?: 'asc' | 'desc';
102+
}) => searchTestingActivities(filter, sortBy, sortDirection, false, size, page),
98103
onMutate: () => {
99104
resetAlert();
100105
setHasSearched(true);
@@ -117,7 +122,8 @@ const TestSearch = () => {
117122
});
118123

119124
const exportMutation = useMutation({
120-
mutationFn: (filter: ActivitySearchRequest) => searchTestingActivities(filter, 0, 0, true),
125+
mutationFn: (filter:
126+
ActivitySearchRequest) => searchTestingActivities(filter, undefined, undefined, true, 0, 0),
121127

122128
onSuccess: (data) => {
123129
const visibilityConfig = JSON.parse(
@@ -169,6 +175,19 @@ const TestSearch = () => {
169175
}
170176
});
171177

178+
const handleSortingChange = (newSorting: Sorting[]) => {
179+
setSorting(newSorting);
180+
181+
const sort = newSorting[0];
182+
searchMutation.mutate({
183+
filter: searchParams,
184+
page: paginationInfo.pageNumber,
185+
size: paginationInfo.pageSize,
186+
sortBy: sort?.id,
187+
sortDirection: sort?.desc ? 'desc' : 'asc'
188+
});
189+
};
190+
172191
const handleExportData = () => {
173192
exportMutation.mutate(searchParams);
174193
};
@@ -191,8 +210,15 @@ const TestSearch = () => {
191210
}, [testTypeQuery.error]);
192211

193212
const handlePageChange = (pageIndex: number, pageSize: number) => {
213+
const sort = sorting[0];
194214
searchMutation.mutate(
195-
{ filter: searchParams, page: pageIndex, size: pageSize },
215+
{
216+
filter: searchParams,
217+
page: pageIndex,
218+
size: pageSize,
219+
sortBy: sort?.id,
220+
sortDirection: sort?.desc ? 'desc' : 'asc'
221+
},
196222
{
197223
onSuccess: (data) => {
198224
setSearchResults(data.content);
@@ -543,8 +569,10 @@ const TestSearch = () => {
543569
data={searchResults}
544570
isLoading={searchMutation.isPending}
545571
paginationInfo={paginationInfo}
572+
sorting={sorting}
546573
onPageChange={handlePageChange}
547574
onExportData={handleExportData}
575+
onSortingChange={handleSortingChange}
548576
/>
549577
) : (
550578
<TablePlaceholder />

oracle-api/src/main/java/ca/bc/gov/oracleapi/ConsepOracleQueryConstants.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package ca.bc.gov.oracleapi;
22

3+
import java.util.Set;
4+
35
import lombok.AccessLevel;
46
import lombok.NoArgsConstructor;
57

@@ -45,8 +47,14 @@ public class ConsepOracleQueryConstants {
4547
AND (:completeStatus IS NULL OR a.testCompleteInd = :completeStatus)
4648
AND (:acceptanceStatus IS NULL OR a.acceptResultInd = :acceptanceStatus)
4749
AND (:geneticClassCode IS NULL OR a.geneticClassCode = :geneticClassCode)
48-
ORDER BY a.seedlotSample, a.actualBeginDtTm
4950
""";
50-
}
51-
5251

52+
public static final Set<String> ALLOWED_SORT_FIELDS = Set.of(
53+
"activityTypeCd", "activityId", "seedlotDisplay", "germinatorTrayId",
54+
"seedWithdrawalDate", "requestId", "testCategoryCd", "testRank", "species",
55+
"actualBeginDtTm", "actualEndDtTm", "revisedStartDt", "revisedEndDt",
56+
"germTrayAssignment", "testCompleteInd", "acceptResultInd", "geneticClassCode",
57+
"requestItem", "pv", "currentTestInd", "significntStsInd", "riaComment",
58+
"seedlotSample"
59+
);
60+
}

oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/consep/ActivitySearchEndpoint.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@
1717
import org.springdoc.core.annotations.ParameterObject;
1818
import org.springframework.data.domain.Pageable;
1919
import org.springframework.data.web.PageableDefault;
20+
import org.springframework.http.HttpStatus;
2021
import org.springframework.validation.annotation.Validated;
2122
import org.springframework.web.bind.annotation.GetMapping;
23+
import jakarta.validation.constraints.Pattern;
2224
import org.springframework.web.bind.annotation.PostMapping;
2325
import org.springframework.web.bind.annotation.RequestBody;
2426
import org.springframework.web.bind.annotation.RequestMapping;
2527
import org.springframework.web.bind.annotation.RequestParam;
2628
import org.springframework.web.bind.annotation.RestController;
29+
import org.springframework.web.server.ResponseStatusException;
30+
31+
import static ca.bc.gov.oracleapi.ConsepOracleQueryConstants.ALLOWED_SORT_FIELDS;
2732

2833
/**
2934
* This class exposes testing search resources API.
@@ -60,12 +65,27 @@ public class ActivitySearchEndpoint {
6065
public ActivitySearchPageResponseDto searchTestingActivities(
6166
@Valid @RequestBody ActivitySearchRequestDto filter,
6267
@RequestParam(defaultValue = "false") boolean unpaged,
68+
@RequestParam(required = false) String sortBy,
69+
@RequestParam(defaultValue = "asc")
70+
@Pattern(regexp = "^(asc|desc)$", flags = Pattern.Flag.CASE_INSENSITIVE,
71+
message = "sortDirection must be either 'asc' or 'desc'")
72+
String sortDirection,
6373
@ParameterObject @PageableDefault(size = 20) Pageable paginationParameters
6474
) {
75+
if (sortBy != null && !sortBy.isBlank() && !ALLOWED_SORT_FIELDS.contains(sortBy)) {
76+
throw new ResponseStatusException(
77+
HttpStatus.BAD_REQUEST,
78+
"Invalid sort field: " + sortBy);
79+
}
6580
if (unpaged) {
6681
paginationParameters = Pageable.unpaged();
6782
}
68-
return activitySearchService.searchTestingActivities(filter, paginationParameters);
83+
return activitySearchService.searchTestingActivities(
84+
filter,
85+
paginationParameters,
86+
sortBy,
87+
sortDirection
88+
);
6989
}
7090

7191
@GetMapping("/type-codes")

0 commit comments

Comments
 (0)