--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { default as unionize, ofType, UnionOf } from "unionize";
+import { SortDirection, DataColumn } from "../../components/data-table/data-column";
+import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
+
+type WithId<T> = T & { id: string };
+
+const actions = unionize({
+ SET_COLUMNS: ofType<WithId<{ columns: Array<DataColumn<any>> }>>(),
+ SET_FILTERS: ofType<WithId<{columnName: string, filters: DataTableFilterItem[]}>>(),
+ SET_ITEMS: ofType<WithId<{items: any[]}>>(),
+ SET_PAGE: ofType<WithId<{page: number}>>(),
+ SET_ROWS_PER_PAGE: ofType<WithId<{rowsPerPage: number}>>(),
+ TOGGLE_COLUMN: ofType<WithId<{ columnName: string }>>(),
+ TOGGLE_SORT: ofType<WithId<{ columnName: string }>>(),
+ SET_SEARCH_VALUE: ofType<WithId<{searchValue: string}>>()
+}, { tag: "type", value: "payload" });
+
+export type DataExplorerAction = UnionOf<typeof actions>;
+
+export default actions;
+
+
+
+
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import dataExplorerReducer, { initialDataExplorer } from "./data-explorer-reducer";
+import actions from "./data-explorer-action";
+import { DataColumn } from "../../components/data-table/data-column";
+import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
+
+describe('data-explorer-reducer', () => {
+ it('should set columns', () => {
+ const columns: Array<DataColumn<any>> = [{
+ name: "Column 1",
+ render: jest.fn(),
+ selected: true
+ }];
+ const state = dataExplorerReducer(undefined,
+ actions.SET_COLUMNS({ id: "Data explorer", columns }));
+ expect(state["Data explorer"].columns).toEqual(columns);
+ });
+
+ it('should toggle sorting', () => {
+ const columns: Array<DataColumn<any>> = [{
+ name: "Column 1",
+ render: jest.fn(),
+ selected: true,
+ sortDirection: "asc"
+ }, {
+ name: "Column 2",
+ render: jest.fn(),
+ selected: true,
+ sortDirection: "none",
+ }];
+ const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
+ actions.TOGGLE_SORT({ id: "Data explorer", columnName: "Column 2" }));
+ expect(state["Data explorer"].columns[0].sortDirection).toEqual("none");
+ expect(state["Data explorer"].columns[1].sortDirection).toEqual("asc");
+ });
+
+ it('should set filters', () => {
+ const columns: Array<DataColumn<any>> = [{
+ name: "Column 1",
+ render: jest.fn(),
+ selected: true,
+ }];
+
+ const filters: DataTableFilterItem[] = [{
+ name: "Filter 1",
+ selected: true
+ }];
+ const state = dataExplorerReducer({ "Data explorer": { ...initialDataExplorer, columns } },
+ actions.SET_FILTERS({ id: "Data explorer", columnName: "Column 1", filters }));
+ expect(state["Data explorer"].columns[0].filters).toEqual(filters);
+ });
+
+ it('should set items', () => {
+ const state = dataExplorerReducer({ "Data explorer": undefined },
+ actions.SET_ITEMS({ id: "Data explorer", items: ["Item 1", "Item 2"] }));
+ expect(state["Data explorer"].items).toEqual(["Item 1", "Item 2"]);
+ });
+
+ it('should set page', () => {
+ const state = dataExplorerReducer({ "Data explorer": undefined },
+ actions.SET_PAGE({ id: "Data explorer", page: 2 }));
+ expect(state["Data explorer"].page).toEqual(2);
+ });
+
+ it('should set rows per page', () => {
+ const state = dataExplorerReducer({ "Data explorer": undefined },
+ actions.SET_ROWS_PER_PAGE({ id: "Data explorer", rowsPerPage: 5 }));
+ expect(state["Data explorer"].rowsPerPage).toEqual(5);
+ });
+});
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { DataColumn, toggleSortDirection, resetSortDirection } from "../../components/data-table/data-column";
+import actions, { DataExplorerAction } from "./data-explorer-action";
+import { DataTableFilterItem } from "../../components/data-table-filters/data-table-filters";
+
+interface DataExplorer {
+ columns: Array<DataColumn<any>>;
+ items: any[];
+ page: number;
+ rowsPerPage: number;
+}
+
+export const initialDataExplorer: DataExplorer = {
+ columns: [],
+ items: [],
+ page: 0,
+ rowsPerPage: 0
+};
+
+export type DataExplorerState = Record<string, DataExplorer | undefined>;
+
+const dataExplorerReducer = (state: DataExplorerState = {}, action: DataExplorerAction) =>
+ actions.match(action, {
+ SET_COLUMNS: ({ id, columns }) => update(state, id, setColumns(columns)),
+ SET_FILTERS: ({ id, columnName, filters }) => update(state, id, mapColumns(setFilters(columnName, filters))),
+ SET_ITEMS: ({ id, items }) => update(state, id, explorer => ({ ...explorer, items })),
+ SET_PAGE: ({ id, page }) => update(state, id, explorer => ({ ...explorer, page })),
+ SET_ROWS_PER_PAGE: ({ id, rowsPerPage }) => update(state, id, explorer => ({ ...explorer, rowsPerPage })),
+ TOGGLE_SORT: ({ id, columnName }) => update(state, id, mapColumns(toggleSort(columnName))),
+ TOGGLE_COLUMN: ({ id, columnName }) => update(state, id, mapColumns(toggleColumn(columnName))),
+ default: () => state
+ });
+
+export default dataExplorerReducer;
+
+const get = (state: DataExplorerState, id: string) => state[id] || initialDataExplorer;
+
+const update = (state: DataExplorerState, id: string, updateFn: (dataExplorer: DataExplorer) => DataExplorer) =>
+ ({ ...state, [id]: updateFn(get(state, id)) });
+
+const setColumns = (columns: Array<DataColumn<any>>) =>
+ (dataExplorer: DataExplorer) =>
+ ({ ...dataExplorer, columns });
+
+const mapColumns = (mapFn: (column: DataColumn<any>) => DataColumn<any>) =>
+ (dataExplorer: DataExplorer) =>
+ ({ ...dataExplorer, columns: dataExplorer.columns.map(mapFn) });
+
+const toggleSort = (columnName: string) =>
+ (column: DataColumn<any>) => column.name === columnName
+ ? toggleSortDirection(column)
+ : resetSortDirection(column);
+
+const toggleColumn = (columnName: string) =>
+ (column: DataColumn<any>) => column.name === columnName
+ ? { ...column, selected: !column.selected }
+ : column;
+
+const setFilters = (columnName: string, filters: DataTableFilterItem[]) =>
+ (column: DataColumn<any>) => column.name === columnName
+ ? { ...column, filters }
+ : column;