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

carbon-design-system / carbon-addons-iot-react / 6485381999

11 Oct 2023 04:29PM UTC coverage: 97.443% (-0.06%) from 97.502%
6485381999

push

github

carbon-bot
v2.153.0

7777 of 8122 branches covered (0.0%)

Branch coverage included in aggregate %.

9375 of 9480 relevant lines covered (98.89%)

2510.49 hits per line

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

95.45
/packages/react/src/components/Table/tableReducer.js
1
import update from 'immutability-helper';
2
import { isNil, isEmpty, get, find } from 'lodash-es';
3
import { firstBy } from 'thenby';
4

5
import {
6
  getSortedData,
7
  caseInsensitiveSearch,
8
  sortTableData,
9
  isEmptyString,
10
} from '../../utils/componentUtilityFunctions';
11
import { fillArrWithRowIds } from '../../utils/tableReducer';
12
import { FILTER_EMPTY_STRING } from '../../constants/Filters';
13

14
import {
15
  TABLE_PAGE_CHANGE,
16
  TABLE_FILTER_APPLY,
17
  TABLE_TOOLBAR_TOGGLE,
18
  TABLE_FILTER_CLEAR,
19
  TABLE_ACTION_CANCEL,
20
  TABLE_ACTION_APPLY,
21
  TABLE_COLUMN_SORT,
22
  TABLE_ROW_SELECT,
23
  TABLE_ROW_SELECT_ALL,
24
  TABLE_ROW_EXPAND,
25
  TABLE_ROW_ACTION_START,
26
  TABLE_ROW_ACTION_EDIT,
27
  TABLE_ROW_ACTION_COMPLETE,
28
  TABLE_ROW_ACTION_ERROR,
29
  TABLE_COLUMN_ORDER,
30
  TABLE_COLUMN_RESIZE,
31
  TABLE_REGISTER,
32
  TABLE_SEARCH_APPLY,
33
  TABLE_ADVANCED_FILTER_REMOVE,
34
  TABLE_ADVANCED_FILTER_CHANGE,
35
  TABLE_ADVANCED_FILTER_CREATE,
36
  TABLE_ADVANCED_FILTER_TOGGLE,
37
  TABLE_ADVANCED_FILTER_CANCEL,
38
  TABLE_ADVANCED_FILTER_APPLY,
39
  TABLE_TOGGLE_AGGREGATIONS,
40
  TABLE_MULTI_SORT_TOGGLE_MODAL,
41
  TABLE_MULTI_SORT_SAVE,
42
  TABLE_MULTI_SORT_CANCEL,
43
  TABLE_MULTI_SORT_ADD_COLUMN,
44
  TABLE_MULTI_SORT_REMOVE_COLUMN,
45
  TABLE_MULTI_SORT_CLEAR,
46
  TABLE_ROW_LOAD_MORE,
47
} from './tableActionCreators';
48
import { baseTableReducer } from './baseTableReducer';
49
import { findRow } from './tableUtilities';
50

51
const searchWithEmptyString = (keys, searchTerm, baseSearch) => {
69✔
52
  if (searchTerm === FILTER_EMPTY_STRING) {
315,209✔
53
    return keys.some((key) => isEmptyString(key));
300✔
54
  }
55
  return baseSearch(keys, searchTerm);
314,909✔
56
};
57

58
const arraySearchWithEmptyString = (arr, item) => {
69✔
59
  if (arr.includes(FILTER_EMPTY_STRING) && isEmptyString(item)) {
703✔
60
    return true;
132✔
61
  }
62
  return arr.includes(item);
571✔
63
};
64

65
/**
66
 * Default function to compare value 1 and 2
67
 * @param {*} value1, filter value
68
 * @param {*} value2, actual
69
 * returns true if value1 contains value2 for strings, and true if value1 === value2 for numbers
70
 */
71
export const defaultComparison = (value1, value2) =>
69✔
72
  !isNil(value1) && typeof value1 === 'number' // only if the column value filter is not null/undefined
9,659✔
73
    ? value1 === Number(value2) // for a number type, attempt to convert filter to number and direct compare
74
    : !isNil(value1) && // type string do a lowercase includes comparison
19,316✔
75
      searchWithEmptyString([value1?.toString()], value2?.toString(), caseInsensitiveSearch);
76

77
export const runSimpleFilters = (data, filters, columns) => {
69✔
78
  return data.filter(({ values }) =>
116✔
79
    // return false if a value doesn't match a valid filter
80
    filters.reduce((acc, { columnId, value }) => {
8,041✔
81
      if (
13,815!
82
        typeof value === 'number' ||
29,637✔
83
        typeof value === 'string' ||
84
        typeof value === 'boolean' ||
85
        Array.isArray(value) ||
86
        value instanceof Date
87
      ) {
88
        if (!isNil(columns)) {
13,815✔
89
          const { filter } = find(columns, { id: columnId }) || {};
13,806!
90
          const filterFunction = filter?.filterFunction;
13,806✔
91
          if (Array.isArray(value) && !isEmpty(value)) {
13,806✔
92
            return (
700✔
93
              acc &&
1,400✔
94
              (filterFunction
700!
95
                ? filterFunction(values[columnId], value)
96
                : arraySearchWithEmptyString(value, values[columnId]))
97
            );
98
          }
99
          return (
13,106✔
100
            acc &&
22,763✔
101
            (filterFunction
9,657✔
102
              ? filterFunction(values[columnId], value)
103
              : defaultComparison(values[columnId], value))
104
          );
105
        }
106
        if (Array.isArray(value) && !isEmpty(value)) {
9✔
107
          return acc && arraySearchWithEmptyString(value, values[columnId]);
3✔
108
        }
109
        return acc && defaultComparison(values[columnId], value);
6✔
110
      }
111
      return false;
×
112
    }, true)
113
  );
114
};
115

116
const operands = {
69✔
117
  NEQ: (a, b) => a !== b,
36✔
118
  LT: (a, b) => a < b,
100✔
119
  LTOET: (a, b) => a <= b,
100✔
120
  EQ: (a, b) => a === b,
3,716✔
121
  GTOET: (a, b) => a >= b,
100✔
122
  GT: (a, b) => a > b,
117✔
123
  CONTAINS: (a, b) => a.includes(b),
683✔
124
};
125

126
/**
127
 * Recursively check each rule and determine if it's a normal rule or a ruleGroup.
128
 * If it's a group dig deeper into the tree passing the values and logic down as we go.
129
 * If it's a normal rule just run `every` on all the rules for 'ALL' logic and `some`
130
 * for 'ANY' logic.
131
 *
132
 * @param {string} logic 'ALL' or 'ANY'
133
 * @param {Array<Object>} rules Array of all the rules in this group
134
 * @param {object} values The values for each column in this row of the data
135
 *
136
 * @returns boolean
137
 */
138
const reduceRuleGroup = (logic, rules, values) => {
69✔
139
  const processRules = ({
3,822✔
140
    columnId,
141
    operand,
142
    groupLogic: childLogic,
143
    rules: childRules,
144
    value: filterValue,
145
  }) => {
146
    if (childLogic && Array.isArray(childRules)) {
4,955✔
147
      return reduceRuleGroup(childLogic, childRules, values);
103✔
148
    }
149

150
    const columnValue = values[columnId]?.toString();
4,852✔
151
    const comparitor = operands[operand];
4,852✔
152

153
    return comparitor(columnValue, filterValue);
4,852✔
154
  };
155

156
  if (logic === 'ALL') {
3,822✔
157
    return rules.every(processRules);
3,600✔
158
  }
159

160
  if (logic === 'ANY') {
222!
161
    return rules.some(processRules);
222✔
162
  }
163

164
  return false;
×
165
};
166

167
/**
168
 * Loop through all the currently active advanced filters TREATING THEM AS 'AND' CONDITIONS
169
 * to determine which rows should be shown.
170
 *
171
 * @param {Array<Object>} data tableData
172
 * @param {Array<{filterId: string; filterTitleText: string; filterRules: Object}>} advancedFilters All the currently active filters
173
 * @returns boolean
174
 */
175
export const runAdvancedFilters = (data, advancedFilters) => {
69✔
176
  return data.filter(({ values }) => {
37✔
177
    return advancedFilters.every(({ filterRules: { groupLogic, rules } }) => {
3,700✔
178
      return reduceRuleGroup(groupLogic, rules, values);
3,719✔
179
    });
180
  });
181
};
182

183
/**
184
 * Little utility to filter data
185
 * @param {Array<Object>} data data to filter
186
 * @param {Array<{columnId: string, value: any}>} filters
187
 * @param {Array<Object>} columns AKA headers
188
 */
189
export const filterData = (data, filters, columns, advancedFilters) => {
69✔
190
  const hasSimpleFilters = Array.isArray(filters) && filters.length;
1,662✔
191
  const hasAdvancedFilters = Array.isArray(advancedFilters) && advancedFilters.length;
1,662✔
192

193
  if (!hasSimpleFilters && !hasAdvancedFilters) {
1,662✔
194
    return data;
1,513✔
195
  }
196

197
  if (!hasAdvancedFilters) {
149✔
198
    return runSimpleFilters(data, filters, columns);
112✔
199
  }
200

201
  if (!hasSimpleFilters) {
37✔
202
    return runAdvancedFilters(data, advancedFilters);
33✔
203
  }
204

205
  return runSimpleFilters(runAdvancedFilters(data, advancedFilters), filters, columns);
4✔
206
};
207

208
// Little utility to search
209
export const searchData = (data, searchString) =>
69✔
210
  searchString && searchString !== ''
1,009✔
211
    ? data.filter(
212
        (
213
          { values } // globally check row values for a match
214
        ) => {
215
          const foundIndex = Object.values(values).findIndex((value) => {
50,281✔
216
            if (
408,293✔
217
              typeof value === 'number' ||
934,536✔
218
              typeof value === 'string' ||
219
              typeof value === 'boolean'
220
            ) {
221
              if (!isNil(value)) {
305,551!
222
                return searchWithEmptyString(
305,551✔
223
                  [value.toString()],
224
                  searchString.toString(),
225
                  caseInsensitiveSearch
226
                );
227
              }
228
            }
229

230
            return false;
102,742✔
231
          });
232

233
          return foundIndex !== -1;
50,281✔
234
        }
235
      )
236
    : data;
237

238
export const getCustomColumnSort = (columns, columnId) => {
69✔
239
  const currentlySortedColumn =
240
    columnId && columns && columns.find((column) => column.id === columnId);
311✔
241
  return currentlySortedColumn && currentlySortedColumn.sortFunction; // see if there's a custom sort function passed
239✔
242
};
243

244
/**
245
 * multi-sort helper for more readable code.
246
 *
247
 * @param {array} sort An array of sort objects [{columnId: string, direction: 'ASC' | 'DESC'}]
248
 * @param {array} columns An array of table columns matching the table column prop
249
 * @param {array} data An array of row data for the Table
250
 *
251
 * @returns the table data sorted by multiple dimensions
252
 */
253
const handleMultiSort = (sort, columns, data) => {
69✔
254
  // setup the stack with a inert firstBy, so that we can jump straight into the
255
  // thenBys below from the sort array
256
  let sortStack = firstBy(() => 0);
1,424✔
257
  sort.forEach(({ columnId, direction }) => {
31✔
258
    const customSortFn = getCustomColumnSort(columns, columnId);
62✔
259
    if (customSortFn) {
62✔
260
      const sortedValues = customSortFn({ data, columnId, direction }).map(({ values }) => values);
6✔
261
      sortStack = sortStack.thenBy((row) => row.values[columnId], {
16✔
262
        cmp: (a, b) => {
263
          return (
8✔
264
            sortedValues.findIndex((row) => row[columnId] === a) -
16✔
265
            sortedValues.findIndex((row) => row[columnId] === b)
16✔
266
          );
267
        },
268
      });
269
    } else {
270
      sortStack = sortStack.thenBy((row) => row.values[columnId], {
4,608✔
271
        cmp: sortTableData(columnId),
272
        direction: direction === 'ASC' ? 'asc' : 'desc',
60✔
273
      });
274
    }
275
  });
276

277
  return data.sort(sortStack);
31✔
278
};
279

280
// little utility to both sort and filter
281
export const filterSearchAndSort = (
69✔
282
  data,
283
  sort = {},
33✔
284
  search = {},
41✔
285
  filters = [],
3✔
286
  columns,
287
  advancedFilters = []
1,165✔
288
) => {
289
  const { value, defaultValue } = search;
1,652✔
290
  const searchValue = value ?? defaultValue;
1,652✔
291
  const filteredData = filterData(data, filters, columns, advancedFilters);
1,652✔
292
  const searchedData =
293
    searchValue && searchValue !== '' ? searchData(filteredData, searchValue) : filteredData;
1,652✔
294

295
  if (isEmpty(sort)) {
1,652✔
296
    return searchedData;
1,482✔
297
  }
298

299
  if (Array.isArray(sort)) {
170✔
300
    return handleMultiSort(sort, columns, searchedData);
17✔
301
  }
302

303
  const { columnId, direction } = sort;
153✔
304
  return getCustomColumnSort(columns, columnId)
153✔
305
    ? getCustomColumnSort(columns, columnId)({ data: searchedData, columnId, direction })
306
    : getSortedData(searchedData, columnId, direction);
307
};
308

309
/** This reducer handles sort, filter and search that needs data otherwise it proxies for the baseTableReducer */
310
export const tableReducer = (state = {}, action) => {
69✔
311
  switch (action.type) {
1,895!
312
    // Filter Actions
313
    case TABLE_FILTER_APPLY: {
314
      const newFilters = Object.entries(action.payload)
53✔
315
        .map(([key, value]) =>
316
          value !== ''
414✔
317
            ? {
318
                columnId: key,
319
                value,
320
              }
321
            : null
322
        )
323
        .filter((i) => i);
414✔
324

325
      return baseTableReducer(
53✔
326
        update(state, {
327
          view: {
328
            table: {
329
              filteredData: {
330
                $set: filterSearchAndSort(
331
                  state.data,
332
                  get(state, 'view.table.sort'),
333
                  get(state, 'view.toolbar.search'),
334
                  newFilters,
335
                  get(state, 'columns')
336
                ),
337
              },
338
            },
339
          },
340
        }),
341
        action
342
      );
343
    }
344
    case TABLE_FILTER_CLEAR:
345
      return baseTableReducer(
6✔
346
        update(state, {
347
          view: {
348
            selectedAdvancedFilterIds: {
349
              $set: [],
350
            },
351
            table: {
352
              filteredData: {
353
                $set: filterSearchAndSort(
354
                  state.data,
355
                  get(state, 'view.table.sort'),
356
                  get(state, 'view.toolbar.search'),
357
                  [],
358
                  get(state, 'columns'),
359
                  []
360
                ),
361
              },
362
            },
363
          },
364
        }),
365
        action
366
      );
367
    case TABLE_SEARCH_APPLY: {
368
      // console.log(state.data.length)
369
      // Quick search should search within the filtered and sorted data
370
      const data = filterSearchAndSort(
1,101✔
371
        state.data,
372
        get(state, 'view.table.sort'),
373
        { value: action.payload },
374
        get(state, 'view.filters'),
375
        get(state, 'columns')
376
      );
377
      return baseTableReducer(
1,101✔
378
        update(state, {
379
          view: {
380
            table: {
381
              filteredData: {
382
                $set: data,
383
              },
384
            },
385
          },
386
        }),
387
        action
388
      );
389
    }
390

391
    // Batch Actions
392
    case TABLE_ACTION_CANCEL:
393
      return baseTableReducer(state, action);
1✔
394
    case TABLE_ACTION_APPLY: {
395
      const { filteredData } = state.view.table;
2✔
396
      const data = filteredData || state.data;
2✔
397
      // only update the data and filtered data if deleted
398
      if (action.payload === 'delete') {
2✔
399
        const { selectedIds } = state.view.table;
1✔
400
        const { pagination } = state.view;
1✔
401
        const totalItems = pagination.totalItems - selectedIds.length;
1✔
402
        const numberOfPages = Math.ceil(totalItems / pagination.pageSize);
1✔
403
        const page = pagination.page > numberOfPages ? numberOfPages : pagination.page;
1!
404
        return baseTableReducer(
1✔
405
          update(state, {
406
            data: {
407
              $set: state.data.filter((i) => !selectedIds.includes(i.id)),
100✔
408
            },
409
            view: {
410
              table: {
411
                filteredData: {
412
                  $set: data.filter((i) => !selectedIds.includes(i.id)),
100✔
413
                },
414
              },
415
              pagination: {
416
                $set: {
417
                  ...pagination,
418
                  totalItems,
419
                  page,
420
                },
421
              },
422
            },
423
          }),
424
          action
425
        );
426
      }
427
      return baseTableReducer(state, action);
1✔
428
    }
429
    // Column operations
430
    case TABLE_COLUMN_SORT: {
431
      // TODO should check that columnId actually is valid
432
      const columnId = action.payload;
23✔
433
      const sorts = ['NONE', 'ASC', 'DESC'];
23✔
434
      const currentSort = get(state, 'view.table.sort');
23✔
435
      const isInMultiSort =
436
        Array.isArray(currentSort) && currentSort.some((column) => column.columnId === columnId);
27✔
437
      const currentSortDir = isInMultiSort
23✔
438
        ? currentSort.find((sort) => sort.columnId === columnId).direction
27✔
439
        : currentSort && currentSort.columnId === columnId
24✔
440
        ? currentSort.direction
441
        : 'NONE';
442

443
      const nextSortDir = isInMultiSort
23✔
444
        ? currentSortDir === 'ASC'
14!
445
          ? 'DESC'
446
          : 'ASC'
447
        : sorts[(sorts.findIndex((i) => i === currentSortDir) + 1) % sorts.length];
17✔
448

449
      // validate if there is any column of timestamp type
450
      const isTimestampColumn =
451
        action.columns &&
23✔
452
        action.columns.filter((column) => column.id === columnId && column.type === 'TIMESTAMP')
130✔
453
          .length > 0;
454

455
      const customColumnSort = getCustomColumnSort(get(state, 'columns'), columnId);
23✔
456

457
      let filteredData;
458
      let nextSort;
459
      if (isInMultiSort) {
23✔
460
        nextSort = currentSort.reduce((carry, column) => {
14✔
461
          if (column.columnId === columnId) {
28✔
462
            return [...carry, { ...column, direction: nextSortDir }];
14✔
463
          }
464

465
          return [...carry, column];
14✔
466
        }, []);
467
        filteredData = handleMultiSort(
14✔
468
          nextSort,
469
          state.columns,
470
          state.view.table.filteredData || state.data
15✔
471
        );
472
      } else {
473
        filteredData =
9✔
474
          nextSortDir !== 'NONE'
9✔
475
            ? customColumnSort // if there's a custom column sort apply it
6✔
476
              ? customColumnSort({
477
                  data: state.view.table.filteredData || state.data,
2✔
478
                  columnId,
479
                  direction: nextSortDir,
480
                })
481
              : getSortedData(
482
                  state.view.table.filteredData || state.data,
6✔
483
                  columnId,
484
                  nextSortDir,
485
                  isTimestampColumn
486
                )
487
            : filterSearchAndSort(
488
                state.data,
489
                null,
490
                { value: state.view.toolbar.search?.defaultValue },
491
                get(state, 'view.filters'),
492
                get(state, 'columns')
493
              );
494
      }
495
      return baseTableReducer(
23✔
496
        update(state, {
497
          view: {
498
            table: {
499
              filteredData: {
500
                $set: filteredData,
501
              },
502
            },
503
          },
504
        }),
505
        action
506
      );
507
    }
508

509
    case TABLE_ROW_SELECT: {
510
      return baseTableReducer(state, action);
11✔
511
    }
512
    case TABLE_ROW_SELECT_ALL: {
513
      return baseTableReducer(state, action);
11✔
514
    }
515
    // By default we need to setup our sorted and filteredData and turn off the loading state
516
    case TABLE_REGISTER: {
517
      const updatedData = action.payload.data || state.data;
445✔
518

519
      // The only thing that changes after additional child rows have been loaded is the
520
      // actual data, so we use that diff to find out which ids in loadingMoreIds that
521
      // we should keep.
522
      const loadingMoreIds =
523
        state.view?.table?.loadingMoreIds?.filter((loadMoreRowId) => {
445!
524
          const oldChildCount = findRow(loadMoreRowId, state.data)?.children?.length;
6✔
525
          const newChildCount = findRow(loadMoreRowId, action.payload.data)?.children?.length;
6✔
526
          return oldChildCount === newChildCount;
6✔
527
        }) ?? [];
528

529
      const { view, totalItems, hasUserViewManagement, hasRowSelection } = action.payload;
445✔
530
      const { pageSize, pageSizes, page } = get(view, 'pagination') || {};
445✔
531
      const paginationFromState = get(state, 'view.pagination');
445✔
532

533
      const activeBar = view?.toolbar?.activeBar;
445✔
534
      const activeBarFromState = state.view?.toolbar?.activeBar;
445✔
535
      const rowEditMode = activeBarFromState === 'rowEdit';
445✔
536
      const isMultiSelect = hasRowSelection === 'multi';
445✔
537

538
      const initialDefaultSearch =
539
        get(view, 'toolbar.search.defaultValue') || get(view, 'toolbar.search.value');
445✔
540
      // update the column ordering if I'm passed new columns
541
      // but only if hasUserViewManagement is not active.
542
      const ordering = hasUserViewManagement
445✔
543
        ? get(state, 'view.table.ordering')
544
        : get(view, 'table.ordering') || get(state, 'view.table.ordering');
461✔
545

546
      // update the search if a new one is passed
547
      const searchFromState = get(view, 'toolbar.search');
445✔
548

549
      // if hasUserViewManagement is active we rely on defaultValue
550
      const searchTermFromState = hasUserViewManagement
445✔
551
        ? searchFromState?.defaultValue
552
        : searchFromState?.value;
553

554
      const nextPageSize = paginationFromState.pageSize || pageSize;
445!
555
      const nextTotalItems = totalItems || updatedData.length;
445✔
556
      const nextPage = rowEditMode ? paginationFromState.page : page || 1;
445✔
557
      const pagination = get(state, 'view.pagination')
445!
558
        ? {
559
            totalItems: { $set: nextTotalItems },
560
            pageSize: { $set: nextPageSize },
561
            pageSizes: { $set: pageSizes },
562
            page: { $set: nextPage },
563
          }
564
        : {};
565

566
      const advancedFilters = get(view, 'advancedFilters', []);
445✔
567
      const selectedAdvancedFilterIds =
568
        get(view, 'selectedAdvancedFilterIds') || get(state, 'view.selectedAdvancedFilterIds', []);
445✔
569
      const selectedAdvancedFilters = advancedFilters.filter((advFilter) =>
445✔
570
        selectedAdvancedFilterIds.includes(advFilter.filterId)
30✔
571
      );
572

573
      const rowActionsFromState = get(state, 'view.table.rowActions', []);
445✔
574
      const rowActionsFromProps = view?.table?.rowActions ?? [];
445✔
575
      const rowActions = rowActionsFromState
445✔
576
        // filter actions from state that have been removed from props
577
        .filter(({ rowId }) => rowActionsFromProps.some((row) => row.rowId === rowId))
8✔
578
        .concat(
579
          // add actions from props that aren't in state
580
          rowActionsFromProps.filter(({ rowId }) =>
581
            rowActionsFromState.every((row) => row.rowId !== rowId)
9✔
582
          )
583
        );
584

585
      const columns = get(state, 'columns');
445✔
586
      const filtersInit = get(state, 'view.filters');
445✔
587

588
      // allow filtering by empty string during the table initialization
589
      const filters = filtersInit.map((filter) => {
445✔
590
        if (isEmptyString(filter.value)) {
107✔
591
          const emptyFilterId = filter.columnId;
1✔
592
          const columnWithEmptyFilter = columns.find((el) => el.id === emptyFilterId);
3✔
593
          if (!isEmpty(columnWithEmptyFilter.filter.options)) {
1!
594
            return {
1✔
595
              ...filter,
596
              value: FILTER_EMPTY_STRING,
597
            };
598
          }
599
        }
600
        return filter;
106✔
601
      });
602

603
      const allRowsId = [];
445✔
604
      updatedData.forEach((row) => fillArrWithRowIds(row, allRowsId));
19,040✔
605

606
      const selectedIds = view ? view.table.selectedIds : [];
445✔
607
      const isSelectingAll = isMultiSelect && selectedIds?.length === allRowsId.length;
445✔
608
      const isSelectAllSelected = isSelectingAll
445✔
609
        ? true
610
        : view
444✔
611
        ? view.table.isSelectAllSelected
612
        : false;
613

614
      return update(state, {
445✔
615
        data: {
616
          $set: updatedData,
617
        },
618
        view: {
619
          pagination,
620
          advancedFilters: {
621
            $set: advancedFilters,
622
          },
623
          toolbar: {
624
            initialDefaultSearch: { $set: initialDefaultSearch },
625
            search: { $set: searchFromState },
626
            activeBar: {
627
              $set: activeBar,
628
            },
629
            rowEditBarButtons: { $set: get(view, 'toolbar.rowEditBarButtons') },
630
            hideClearAllFiltersButton: { $set: get(view, 'toolbar.hideClearAllFiltersButton') },
631
          },
632
          table: {
633
            ordering: { $set: ordering },
634
            filteredData: {
635
              $set: filterSearchAndSort(
636
                updatedData,
637
                get(state, 'view.table.sort'),
638
                { value: searchTermFromState },
639
                filters,
640
                columns,
641
                selectedAdvancedFilters
642
              ),
643
            },
644
            loadingState: {
645
              $set: {
646
                isLoading: action.payload.isLoading,
647
                rowCount: get(state, 'view.table.loadingState.rowCount') || 0,
463✔
648
                columnCount: get(state, 'view.table.loadingState.columnCount') || 0,
463✔
649
              },
650
            },
651
            rowActions: {
652
              $set: rowActions,
653
            },
654
            // Reset the selection to the previous values
655
            selectedIds: {
656
              $set: selectedIds,
657
            },
658
            isSelectAllIndeterminate: {
659
              $set: view ? view.table.isSelectAllIndeterminate : false,
445✔
660
            },
661
            isSelectAllSelected: {
662
              $set: isSelectAllSelected,
663
            },
664
            loadingMoreIds: {
665
              $set: loadingMoreIds,
666
            },
667
            singleRowEditButtons: { $set: get(view, 'table.singleRowEditButtons') },
668
          },
669
        },
670
      });
671
    }
672

673
    case TABLE_ADVANCED_FILTER_TOGGLE: {
674
      const isOpen = state.view.toolbar.advancedFilterFlyoutOpen === true;
15✔
675
      return update(state, {
15✔
676
        view: {
677
          toolbar: {
678
            $set: {
679
              advancedFilterFlyoutOpen: !isOpen,
680
            },
681
          },
682
        },
683
      });
684
    }
685
    case TABLE_ADVANCED_FILTER_REMOVE: {
686
      const { filterId } = action.payload;
1✔
687
      const newSelectedFilters = state.view.selectedAdvancedFilterIds.filter(
1✔
688
        (id) => id !== filterId
2✔
689
      );
690
      return update(state, {
1✔
691
        view: {
692
          selectedAdvancedFilterIds: {
693
            $set: newSelectedFilters,
694
          },
695
        },
696
      });
697
    }
698

699
    case TABLE_ADVANCED_FILTER_CHANGE: {
700
      return state;
1✔
701
    }
702

703
    case TABLE_ADVANCED_FILTER_CREATE: {
704
      return state;
1✔
705
    }
706

707
    case TABLE_ADVANCED_FILTER_CANCEL: {
708
      return update(state, {
2✔
709
        view: {
710
          toolbar: {
711
            $set: {
712
              advancedFilterFlyoutOpen: false,
713
            },
714
          },
715
        },
716
      });
717
    }
718

719
    case TABLE_ADVANCED_FILTER_APPLY: {
720
      const newSimpleFilters = Object.entries(action.payload?.simple ?? {})
21✔
721
        .map(([key, value]) =>
722
          value !== ''
2!
723
            ? {
724
                columnId: key,
725
                value,
726
              }
727
            : null
728
        )
729
        .filter((i) => i);
2✔
730

731
      const newAdvancedFilters = state.view.advancedFilters.filter((advFilter) =>
21✔
732
        action?.payload?.advanced?.filterIds?.includes(advFilter.filterId)
34✔
733
      );
734

735
      return baseTableReducer(
21✔
736
        update(state, {
737
          view: {
738
            selectedAdvancedFilterIds: {
739
              $set: newAdvancedFilters.map((advFilter) => advFilter.filterId),
21✔
740
            },
741
            table: {
742
              filteredData: {
743
                $set: filterSearchAndSort(
744
                  state.data,
745
                  get(state, 'view.table.sort'),
746
                  get(state, 'view.toolbar.search'),
747
                  newSimpleFilters,
748
                  get(state, 'columns'),
749
                  newAdvancedFilters
750
                ),
751
              },
752
            },
753
            toolbar: {
754
              advancedFilterFlyoutOpen: {
755
                $set: false,
756
              },
757
            },
758
          },
759
        }),
760
        action
761
      );
762
    }
763

764
    case TABLE_TOGGLE_AGGREGATIONS: {
765
      return update(state, {
2✔
766
        view: {
767
          aggregations: {
768
            isHidden: {
769
              $set: !state.view.aggregations.isHidden,
770
            },
771
          },
772
        },
773
      });
774
    }
775

776
    case TABLE_MULTI_SORT_TOGGLE_MODAL: {
777
      const { columnId } = action.payload;
14✔
778
      const { sort: currentSort } = state.view.table;
14✔
779

780
      const arrayifiedSort = Array.isArray(currentSort)
14✔
781
        ? currentSort
782
        : currentSort !== undefined
1!
783
        ? [currentSort]
784
        : [];
785

786
      const alreadySortedBy = arrayifiedSort.some((by) => by.columnId === columnId);
14✔
787

788
      return update(state, {
14✔
789
        view: {
790
          table: {
791
            showMultiSortModal: {
792
              $set: !state.view.table.showMultiSortModal,
793
            },
794
            multiSortModal: {
795
              $set: {
796
                anticipatedColumn: !alreadySortedBy ? { columnId, direction: 'ASC' } : undefined,
14!
797
              },
798
            },
799
          },
800
        },
801
      });
802
    }
803

804
    case TABLE_MULTI_SORT_SAVE: {
805
      const selectedAdvancedFilterIds = get(state, 'view.selectedAdvancedFilterIds', []);
15✔
806
      const advancedFilters = get(state, 'view.advancedFilters', []);
15✔
807

808
      const selectedAdvancedFilters = advancedFilters.filter((advFilter) =>
15✔
809
        selectedAdvancedFilterIds.includes(advFilter.filterId)
26✔
810
      );
811

812
      return update(state, {
15✔
813
        view: {
814
          table: {
815
            sort: {
816
              $set: action.payload,
817
            },
818
            filteredData: {
819
              $set: filterSearchAndSort(
820
                state.data,
821
                action.payload,
822
                get(state, 'view.toolbar.search'),
823
                get(state, 'view.filters'),
824
                get(state, 'columns'),
825
                selectedAdvancedFilters
826
              ),
827
            },
828
            showMultiSortModal: {
829
              $set: false,
830
            },
831
          },
832
        },
833
      });
834
    }
835

836
    case TABLE_MULTI_SORT_CANCEL: {
837
      return update(state, {
2✔
838
        view: {
839
          table: {
840
            showMultiSortModal: {
841
              $set: false,
842
            },
843
          },
844
        },
845
      });
846
    }
847

848
    case TABLE_MULTI_SORT_CLEAR: {
849
      return update(state, {
1✔
850
        view: {
851
          table: {
852
            showMultiSortModal: {
853
              $set: false,
854
            },
855
            sort: {
856
              $set: undefined,
857
            },
858
            filteredData: {
859
              $set: filterData(state.data, state.view.filters, state.columns),
860
            },
861
          },
862
        },
863
      });
864
    }
865

866
    case TABLE_MULTI_SORT_ADD_COLUMN: {
867
      return state;
13✔
868
    }
869

870
    case TABLE_MULTI_SORT_REMOVE_COLUMN: {
871
      return state;
×
872
    }
873

874
    case TABLE_ROW_LOAD_MORE: {
875
      return update(state, {
×
876
        view: {
877
          table: {
878
            loadingMoreIds: {
879
              $set: [...state.view.table.loadingMoreIds, action.payload],
880
            },
881
          },
882
        },
883
      });
884
    }
885

886
    // Actions that are handled by the base reducer
887
    case TABLE_PAGE_CHANGE:
888
    case TABLE_ROW_ACTION_START:
889
    case TABLE_ROW_ACTION_EDIT:
890
    case TABLE_ROW_ACTION_COMPLETE:
891
    case TABLE_ROW_ACTION_ERROR:
892
    case TABLE_TOOLBAR_TOGGLE:
893
    case TABLE_COLUMN_ORDER:
894
    case TABLE_ROW_EXPAND:
895
    case TABLE_COLUMN_RESIZE:
896
      return baseTableReducer(state, action);
153✔
897
    default:
898
      return state;
1✔
899
  }
900
};
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