• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CBIIT / crdc-datahub-ui / 18789341118

24 Oct 2025 06:57PM UTC coverage: 78.178% (+15.5%) from 62.703%
18789341118

push

github

web-flow
Merge pull request #888 from CBIIT/3.4.0

3.4.0 Release

4977 of 5488 branches covered (90.69%)

Branch coverage included in aggregate %.

8210 of 9264 new or added lines in 257 files covered. (88.62%)

6307 existing lines in 120 files now uncovered.

30203 of 39512 relevant lines covered (76.44%)

213.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.0
/src/utils/tableUtils.ts
1
import { isEqual, isString } from "lodash";
1✔
2

3
import { SORT, DIRECTION } from "../config/TableConfig";
1✔
4

5
import { compareStrings } from "./stringUtils";
1✔
6

7
/**
8
 * Converts a string sorting order to its corresponding numeric value
9
 *
10
 * @param {Order} sortDirection - The sorting direction as a string ("asc" or "desc")
11
 * @returns {number} The numeric representation of the sorting direction: 1 for ascending, -1 for descending
12
 */
13
export const getSortDirection = (sortDirection: Order) =>
1✔
14
  sortDirection?.toLowerCase() === SORT.ASC ? DIRECTION.ASC : DIRECTION.DESC;
9!
15

16
/**
17
 * Sorts and paginates a dataset
18
 *
19
 * @param {T[]} data - The array of data to be sorted and paginated
20
 * @param {FetchListing<T>} fetchListing - Object containing sorting and pagination parameters
21
 * @returns {T[]} The sorted and paginated subset of the original data
22
 *
23
 * @template T - Type of the elements in the data array
24
 */
25
export const paginateAndSort = <T>(data: T[], fetchListing: FetchListing<T>): T[] => {
1✔
26
  if (!data) {
30!
27
    return [];
×
UNCOV
28
  }
×
29
  // Sorting logic
30
  const sortedData = [...data].sort((a, b) => {
30✔
31
    const { orderBy, sortDirection } = fetchListing;
9✔
32
    const sort = getSortDirection(sortDirection);
9✔
33
    const propA = a[orderBy];
9✔
34
    const propB = b[orderBy];
9✔
35

36
    if (!propA) return sort;
9✔
37
    if (!propB) return -sort;
9!
38
    if (propA > propB) return sort;
4!
39
    if (propA < propB) return -sort;
×
40

41
    return 0;
×
42
  });
30✔
43

44
  // Pagination logic
45
  const { first, offset } = fetchListing;
30✔
46
  return sortedData.slice(offset, offset + first);
30✔
47
};
30✔
48

49
/**
50
 * Validates if the total number of items is a valid number and non-negative.
51
 * @param {number} total - The total number of items to validate.
52
 * @returns {boolean} - Returns `true` if the total is a valid, non-negative number; otherwise, returns `false`.
53
 */
54
export const validateTotal = (total: number) => {
1✔
55
  if (total == null || isNaN(total)) {
262✔
56
    return false;
2✔
57
  }
2✔
58
  if (total < 0) {
262✔
59
    return false;
5✔
60
  }
5✔
61

62
  return true;
255✔
63
};
255✔
64

65
/**
66
 * Validates if the provided page number is a valid number and not less than the minimum page number (0).
67
 * @param {number} page - The page number to validate.
68
 * @returns {boolean} - Returns `true` if the page number is valid and not less than 0; otherwise, returns `false`.
69
 */
70
export const validatePage = (page: number) => {
1✔
71
  if (page == null || isNaN(page)) {
1,071✔
72
    return false;
2✔
73
  }
2✔
74
  const minPage = 0;
1,069✔
75
  if (page < minPage) {
1,071✔
76
    return false;
4✔
77
  }
4✔
78

79
  return true;
1,065✔
80
};
1,065✔
81

82
/**
83
 * Validates if the given rows per page number is included in the provided options.
84
 * @param {number} perPage - The number of rows per page to validate.
85
 * @param {number[]} perPageOptions - An array of valid options for rows per page.
86
 * @returns {boolean} - Returns `true` if perPage is a number and exists in perPageOptions; otherwise, returns `false`.
87
 */
88
export const validateRowsPerPage = (perPage: number, perPageOptions: number[]) => {
1✔
89
  if (isNaN(perPage)) {
187✔
90
    return false;
1✔
91
  }
1✔
92
  if (perPageOptions?.length && !perPageOptions?.includes(perPage)) {
187✔
93
    return false;
3✔
94
  }
3✔
95

96
  return true;
183✔
97
};
183✔
98

99
/**
100
 * Validates that the provided array contains only numeric values.
101
 * @param {number[]} perPageOptions - An array of numbers representing the valid options for rows per page.
102
 * @returns {boolean} - Returns `true` if the array is valid and all elements are numbers; otherwise, returns `false`.
103
 */
104
export const validatePerPageOptions = (perPageOptions: number[]) => {
1✔
105
  if (!Array.isArray(perPageOptions)) {
6✔
106
    return false;
2✔
107
  }
2✔
108
  if (perPageOptions.some((opt) => isNaN(opt))) {
6✔
109
    return false;
2✔
110
  }
2✔
111

112
  return true;
2✔
113
};
2✔
114

115
/**
116
 * Validates that the sort direction is either 'asc' or 'desc'.
117
 * @param {string} sortDirection - The sort direction to validate.
118
 * @returns {boolean} - Returns `true` if the sort direction is either 'asc' or 'desc'; otherwise, returns `false`.
119
 */
120
export const validateSortDirection = (sortDirection: string): sortDirection is Order =>
1✔
121
  sortDirection === "asc" || sortDirection === "desc";
204✔
122

123
/**
124
 * Validates if the provided value is either null or a string.
125
 * This function is used to ensure that order by parameters are either non-existent (null)
126
 * or correctly represented as strings.
127
 *
128
 * @param value {string | null} - The value to be validated.
129
 * @returns {boolean} - Returns `true` if the value is either null or a string, otherwise returns `false`.
130
 */
131
export const validateOrderBy = (value: string | null): boolean =>
1✔
132
  value === null || typeof value === "string";
121✔
133

134
/**
135
 * Conditionally updates a property within a state object if the new value passes an optional validation function
136
 * and is different from the current value. This function helps ensure state immutability and controlled updates.
137
 *
138
 * @template T - The type of the state object.
139
 * @template K - The key of the property within the state object to update.
140
 * @param {T} state - The current state object.
141
 * @param {K} key - The key of the property in the state object to update.
142
 * @param {T[K]} payload - The new value for the property specified by key.
143
 * @param {(value: T[K]) => boolean} [validatorFn] - An optional function to validate the new value before updating.
144
 * If the validation function is provided and returns false, the state is not updated.
145
 *
146
 * @returns {T} - Returns the updated state if the new value is valid and different from the current value; otherwise,
147
 * returns the original state unchanged.
148
 */
149
export const validateAndSetIfChanged = <T, K extends keyof T>(
1✔
150
  state: T,
1,519✔
151
  key: K,
1,519✔
152
  payload: T[K],
1,519✔
153
  validatorFn?: (value: T[K]) => boolean
1,519✔
154
): T => {
1,519✔
155
  if (validatorFn && !validatorFn(payload)) {
1,519✔
156
    return state; // return unchanged state if fails validation
18✔
157
  }
18✔
158

159
  // if attempting to set state to same value
160
  if (isEqual(payload, state[key])) {
1,519✔
161
    return state;
1,267✔
162
  }
1,267✔
163

164
  return { ...state, [key]: payload };
234✔
165
};
234✔
166

167
/**
168
 * Dynamically retrieves a validation function based on a specified key of a TableState object.
169
 * This utility function is useful for abstracting the validation logic according to the field
170
 * specified in the state, simplifying the process of validating various fields within a table's state.
171
 *
172
 * @param {TableState<T>} state - The current state of the table, used to access any necessary additional
173
 *                                parameters for specific validation functions that require them.
174
 * @param {K} key - The key from the TableState object which denotes the field to be validated.
175
 * @returns {(value: any) => boolean} | never - Returns a validation function appropriate for the specified
176
 *                                              field. Throws an error if the key is not recognized.
177
 * @template T - The type of elements contained in the table state.
178
 * @template K - The type representing the keys of the table state.
179
 */
180
export const getValidationFn = <K extends keyof TableState<T>, T>(state: TableState<T>, key: K) => {
1✔
181
  switch (key) {
377✔
182
    case "data":
377✔
183
      return Array.isArray;
1✔
184
    case "total":
377✔
185
      return validateTotal;
5✔
186
    case "page":
377✔
187
      return validatePage;
92✔
188
    case "perPage":
377✔
189
      return (val) => validateRowsPerPage(val, state.perPageOptions);
92✔
190
    case "perPageOptions":
377✔
191
      return validatePerPageOptions;
1✔
192
    case "sortDirection":
377✔
193
      return validateSortDirection;
92✔
194
    case "orderBy":
377✔
195
      return validateOrderBy;
93✔
196
    default:
377✔
197
      throw new Error(`Unexpected table key.`);
1✔
198
  }
377✔
199
};
377✔
200

201
/**
202
 * Sorts an array of data based on a specified field and direction.
203
 * @param {T[]} data - Array of data to be sorted.
204
 * @param {string} orderBy - The key to order the data by.
205
 * @param {Order} sortDirection - The direction to sort the data.
206
 * @param {(a: T, b: T) => number} [comparator] - Optional comparator function to customize the sorting logic.
207
 * @returns {T[]} - The sorted array of data.
208
 */
209
export const sortData = <T>(
1✔
210
  data: T[],
342✔
211
  orderBy: string,
342✔
212
  sortDirection: Order,
342✔
213
  comparator?: (a: T, b: T) => number
342✔
214
): T[] => {
342✔
215
  if (!Array.isArray(data) || !data?.length) {
342✔
216
    return [];
100✔
217
  }
100✔
218

219
  const dataClone = [...data];
242✔
220
  const sortedData = dataClone.sort((a, b) => {
242✔
221
    if (comparator) {
27,644✔
222
      return comparator(a, b);
27,633✔
223
    }
27,633✔
224

225
    const valA = a[orderBy]?.toString();
27,644✔
226
    const valB = b[orderBy]?.toString();
27,644✔
227
    if (valA && valB && isString(valA) && isString(valB)) {
27,644✔
228
      return compareStrings(valA, valB);
7✔
229
    }
7✔
230

231
    return 0;
4✔
232
  });
242✔
233

234
  if (sortDirection === "desc") {
342✔
235
    return sortedData.reverse();
1✔
236
  }
1✔
237
  return sortedData;
241✔
238
};
241✔
239

240
/**
241
 * Filters an array of data based on multiple filter criteria.
242
 * @param {T[]} data - Array of data to be filtered.
243
 * @param {Array<(item: T) => boolean>} filters - An array of filter functions to apply to the data.
244
 * @returns {T[]} - The filtered array of data.
245
 */
246
export const filterData = <T>(data: T[], filters: Array<(item: T) => boolean>): T[] =>
1✔
247
  data.filter((item) => filters.every((filter) => filter(item)));
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc