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

CBIIT / crdc-datahub-ui / 9669215793

25 Jun 2024 08:34PM UTC coverage: 34.109% (+2.5%) from 31.605%
9669215793

push

github

GitHub
Merge pull request #407 from CBIIT/table-update

1140 of 3959 branches covered (28.8%)

Branch coverage included in aggregate %.

1808 of 4684 relevant lines covered (38.6%)

93.46 hits per line

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

80.0
/src/utils/tableUtils.ts
1
import { isEqual, isString } from "lodash";
2
import { SORT, DIRECTION } from "../config/TableConfig";
3
import { compareStrings } from "./stringUtils";
4

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

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

34
    if (!propA) return sort;
×
35
    if (!propB) return -sort;
×
36
    if (propA > propB) return sort;
×
37
    if (propA < propB) return -sort;
×
38

39
    return 0;
×
40
  });
41

42
  // Pagination logic
43
  const { first, offset } = fetchListing;
×
44
  return sortedData.slice(offset, offset + first);
×
45
};
46

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

60
  return true;
68✔
61
};
62

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

77
  return true;
110✔
78
};
79

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

94
  return true;
16✔
95
};
96

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

110
  return true;
4✔
111
};
112

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

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

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

157
  // if attempting to set state to same value
158
  if (isEqual(payload, state[key])) {
196✔
159
    return state;
138✔
160
  }
161

162
  return { ...state, [key]: payload };
58✔
163
};
164

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

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

217
  const dataClone = [...data];
12✔
218
  const sortedData = dataClone.sort((a, b) => {
12✔
219
    if (comparator) {
26✔
220
      return comparator(a, b);
4✔
221
    }
222

223
    const valA = a[orderBy]?.toString();
22✔
224
    const valB = b[orderBy]?.toString();
22✔
225
    if (valA && valB && isString(valA) && isString(valB)) {
22✔
226
      return compareStrings(valA, valB);
14✔
227
    }
228

229
    return 0;
8✔
230
  });
231

232
  if (sortDirection === "desc") {
12✔
233
    return sortedData.reverse();
2✔
234
  }
235
  return sortedData;
10✔
236
};
237

238
/**
239
 * Filters an array of data based on multiple filter criteria.
240
 * @param {T[]} data - Array of data to be filtered.
241
 * @param {Array<(item: T) => boolean>} filters - An array of filter functions to apply to the data.
242
 * @returns {T[]} - The filtered array of data.
243
 */
244
export const filterData = <T>(data: T[], filters: Array<(item: T) => boolean>): T[] =>
34✔
245
  data.filter((item) => filters.every((filter) => filter(item)));
36✔
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