Skip to content

Commit

Permalink
Merge pull request #1003 from akto-api-security/feature/persist_filters
Browse files Browse the repository at this point in the history
Feature/persist filters
  • Loading branch information
Ark2307 authored Apr 13, 2024
2 parents 8ee51f9 + 24e70a9 commit 4199041
Show file tree
Hide file tree
Showing 19 changed files with 456 additions and 289 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,58 @@ import {
ChoiceList,
Tabs} from '@shopify/polaris';
import {GithubRow} from './rows/GithubRow';
import { useState, useCallback, useEffect, useReducer } from 'react';
import DateRangePicker from '../layouts/DateRangePicker';
import { useState, useCallback, useEffect } from 'react';
import "./style.css"
import func from '@/util/func';
import { produce } from "immer"
import values from "@/util/values"
import transform from '../../pages/observe/transform';
import DropdownSearch from '../shared/DropdownSearch';
import PersistStore from '../../../main/PersistStore';
import tableFunc from './transform';
import useTable from './TableContext';

function GithubServerTable(props) {

const filtersMap = PersistStore(state => state.filtersMap)
const setFiltersMap = PersistStore(state => state.setFiltersMap)
const tableInitialState = PersistStore(state => state.tableInitialState)
const setTableInitialState = PersistStore(state => state.setTableInitialState)

const currentPageKey = window.location.href

const pageFiltersMap = filtersMap[currentPageKey]
const initialStateFilters = tableFunc.mergeFilters(props.appliedFilters || [], (pageFiltersMap?.filters || []),props.disambiguateLabel)
const { mode, setMode } = useSetIndexFiltersMode(props?.mode ? props.mode : IndexFiltersMode.Filtering);
const [sortSelected, setSortSelected] = useState(props?.sortOptions?.length > 0 ? [props.sortOptions[0].value] : []);
const [sortSelected, setSortSelected] = useState(tableFunc.getInitialSortSelected(props.sortOptions, pageFiltersMap))
const [data, setData] = useState([]);
const [total, setTotal] = useState([]);
const [page, setPage] = useState(0);
const pageLimit = props?.pageLimit || 20;
const [appliedFilters, setAppliedFilters] = useState(props.appliedFilters || []);
const [appliedFilters, setAppliedFilters] = useState(initialStateFilters);
const [queryValue, setQueryValue] = useState('');

const { applyFilter } = useTable()

const [sortableColumns, setSortableColumns] = useState([])
const [activeColumnSort, setActiveColumnSort] = useState({columnIndex: -1, sortDirection: 'descending'})

let filterOperators = props.headers.reduce((map, e) => { map[e.sortKey || e.value] = 'OR'; return map }, {})

const [currDateRange, dispatchCurrDateRange] = useReducer(produce((draft, action) => func.dateRangeReducer(draft, action)), values.ranges[3]);
const handleSelectedTab = (x) => {
const tableTabs = props.tableTabs ? props.tableTabs : props.tabs
if(tableTabs){
const primitivePath = window.location.origin + window.location.pathname
const newUrl = primitivePath + "#" + tableTabs[x].id
window.history.replaceState(null, null, newUrl)
}
}

useEffect(()=> {
setAppliedFilters(initialStateFilters)
setSortSelected(tableFunc.getInitialSortSelected(props.sortOptions, pageFiltersMap))
},[currentPageKey])

useEffect(() => {
let [sortKey, sortOrder] = sortSelected.length == 0 ? ["", ""] : sortSelected[0].split(" ");
setActiveColumnSort(tableFunc.getColumnSort(sortSelected, props?.sortOptions))
let filters = props.headers.reduce((map, e) => { map[e.filterKey || e.value] = []; return map }, {})
appliedFilters.forEach((filter) => {
filters[filter.key] = filter.value
Expand All @@ -44,31 +71,61 @@ function GithubServerTable(props) {
let tempData = await props.fetchData(sortKey, sortOrder == 'asc' ? -1 : 1, page * pageLimit, pageLimit, filters, filterOperators, queryValue);
tempData ? setData([...tempData.value]) : setData([])
tempData ? setTotal(tempData.total) : setTotal(0)
applyFilter(tempData.total)

setTableInitialState({
...tableInitialState,
[currentPageKey]: tempData.total
})
}
handleSelectedTab(props?.selected)
fetchData();
}, [sortSelected, appliedFilters, queryValue, page])
}, [sortSelected, appliedFilters, queryValue, page, pageFiltersMap])

useEffect(()=> {
setSortableColumns(tableFunc.getSortableChoices(props?.headers))
},[props?.headers])

const handleSort = (col, dir) => {
let tempSortSelected = props?.sortOptions.filter(x => x.columnIndex === (col + 1))
let sortVal = [tempSortSelected[0].value]
if(dir.includes("desc")){
setSortSelected([tempSortSelected[1].value])
sortVal = [tempSortSelected[1].value]
}else{
setSortSelected([tempSortSelected[0].value])
}
let copyFilters = filtersMap
copyFilters[currentPageKey] = {
'filters': pageFiltersMap?.filters || [],
'sort': sortVal
}
setFiltersMap(copyFilters)
}

const handleRemoveAppliedFilter = (key) => {
let temp = appliedFilters
temp = temp.filter((filter) => {
return filter.key != key
})
props?.appliedFilters?.forEach((defaultAppliedFilter) => {
if (key == defaultAppliedFilter.key) {
if (key === defaultAppliedFilter.key) {
temp.push(defaultAppliedFilter)
}
})
if (key == "dateRange") {
getDate({ type: "update", period: values.ranges[3] });
} else {
setAppliedFilters(temp);
setAppliedFilters(temp);
let tempFilters = filtersMap
tempFilters[currentPageKey] = {
'filters': temp,
'sort': pageFiltersMap?.sort || []
}
setFiltersMap(tempFilters)
}

const changeAppliedFilters = (key, value) => {
let temp = appliedFilters
temp = temp.filter((filter) => {
return filter.key != key
return filter.key !== key
})
if (value.length > 0 || Object.keys(value).length > 0) {
temp.push({
Expand All @@ -79,6 +136,12 @@ function GithubServerTable(props) {
})
}
setPage(0);
let tempFilters = filtersMap
tempFilters[currentPageKey] = {
'filters': temp,
'sort': pageFiltersMap?.sort || []
}
setFiltersMap(tempFilters)
setAppliedFilters(temp);
};

Expand All @@ -95,12 +158,6 @@ function GithubServerTable(props) {
changeAppliedFilters(key, value);
}

const getDate = (dateObj) => {
dispatchCurrDateRange({type: "update", period: dateObj.period, title: dateObj.title, alias: dateObj.alias})
let obj = dateObj.period.period;
handleFilterStatusChange("dateRange",obj)
}

const getSortedChoices = (choices) => {
return choices.sort((a, b) => (a?.label || a) - (b?.label || b));
}
Expand Down Expand Up @@ -148,24 +205,15 @@ function GithubServerTable(props) {
}
})
}
if (props.calenderFilter) {
filters.push({
key: "dateRange",
label: props.calenderLabel || "Discovered",
filter:
(
<DateRangePicker ranges={values.ranges}
initialDispatch = {currDateRange}
dispatch={(dateObj) => getDate(dateObj)}
setPopoverState={() => {}}
/>),
pinned: true
})
}

const handleFiltersClearAll = useCallback(() => {
setAppliedFilters(props.appliedFilters || [])
getDate({ type: "update", period: values.ranges[3] });
setFiltersMap({
...filtersMap,
[currentPageKey]:{
sort: filtersMap[currentPageKey]?.sort || []
}
})
setAppliedFilters([])
}, []);

const resourceIDResolver = (data) => {
Expand Down Expand Up @@ -222,11 +270,10 @@ function GithubServerTable(props) {

let tableHeightClass = props.increasedHeight ? "control-row" : (props.condensedHeight ? "condensed-row" : '')
let tableClass = props.useNewRow ? "new-table" : (props.selectable ? "removeHeaderColor" : "hideTableHead")

return (
<div className={tableClass}>
<LegacyCard>
{props.tabs && <Tabs tabs={props.tabs} selected={props.selected} onSelect={props.onSelect}></Tabs>}
{props.tabs && <Tabs tabs={props.tabs} selected={props.selected} onSelect={(x) => {props?.onSelect(x); handleSelectedTab(x)}}></Tabs>}
{props.tabs && props.tabs[props.selected].component ? props.tabs[props.selected].component :
<div>
<LegacyCard.Section flush>
Expand All @@ -252,7 +299,7 @@ function GithubServerTable(props) {
setMode={setMode}
loading={props.loading || false}
selected={props?.selected}
onSelect={props?.onSelect}
onSelect={(x) => {props?.onSelect(x); handleSelectedTab(x)}}
/>
<div className={tableHeightClass}>
<IndexTable
Expand All @@ -274,6 +321,10 @@ function GithubServerTable(props) {
bulkActions={props.selectable ? props.bulkActions && props.bulkActions(selectedResources) : []}
promotedBulkActions={props.selectable ? props.promotedBulkActions && props.promotedBulkActions(selectedResources) : []}
hasZebraStriping={props.hasZebraStriping || false}
sortable={sortableColumns}
sortColumnIndex={activeColumnSort.columnIndex}
sortDirection={activeColumnSort.sortDirection}
onSort={handleSort}
>
{rowMarkup}
</IndexTable>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createContext, useReducer, useContext } from "react";
import tableReducer, {initialState} from "./tableReducer";

const TableContext = createContext(initialState);

export const TableContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(tableReducer, initialState);

const applyFilter = (filter) => {
dispatch({
type: "APPLY_FILTER",
payload: {
tabsInfo: filter
}
});
};

const value = {
tabsInfo: state.tabsInfo,
applyFilter,
};
return <TableContext.Provider value={value}>{children}</TableContext.Provider>;
};

const useTable = () => {
const context = useContext(TableContext);

if (context === undefined) {
throw new Error("useTable must be used within TableContext");
}

return context;
};

export default useTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import PersistStore from "../../../main/PersistStore";
const tableInitialState = PersistStore.getState().tableInitialState[window.location.href] || {}

export const initialState = {
tabsInfo : tableInitialState
}

const tableReducer = (state, action) =>{
const { type, payload } = action;
switch (type) {
case "APPLY_FILTER":
return {
...state,
tabsInfo: payload.tabsInfo,
};
default:
return{...state}
}
}

export default tableReducer
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const tableFunc = {
let key = header.filterKey || header.value
let label = header.filterLabel || header.text
let allItemValues = []
props.data.forEach(i => {
props.data && props.data.forEach(i => {
let value = i[key]
if (value instanceof Set) {
allItemValues = allItemValues.concat(...value)
Expand Down Expand Up @@ -61,7 +61,79 @@ const tableFunc = {
let finalData = props.useModifiedData ? props.modifyData(tempData,filters) : tempData

return {value:finalData,total:tempData.length}
},
mergeFilters(filterArray1, filterArray2, labelFunc){
const combined = [...filterArray1, ...filterArray2];
const mergedByKey = combined.reduce((acc, {key, value}) => {
if (acc[key]) {
if(key === 'dateRange'){
acc[key].value = this.mergeTimeRanges(acc[key].value, value)
}else{
acc[key].value = [...new Set([...acc[key].value, ...value])];
}
} else {
if(key === 'dateRange'){
acc[key] = {key, value}
}else{
acc[key] = { key, value: [...value] };
}
}
return acc;
}, {});

return Object.keys(mergedByKey).map((key) => {
const obj = mergedByKey[key]
return {
...obj,
label: labelFunc(obj.key, obj.value)
}
})
},
mergeTimeRanges(obj1, obj2) {
const sinceEpoch1 = Date.parse(obj1.since);
const untilEpoch1 = Date.parse(obj1.until);
const sinceEpoch2 = Date.parse(obj2.since);
const untilEpoch2 = Date.parse(obj2.until);
const minSinceEpoch = Math.min(sinceEpoch1, sinceEpoch2);
const maxUntilEpoch = Math.max(untilEpoch1, untilEpoch2);
const since = new Date(minSinceEpoch).toISOString();
const until = new Date(maxUntilEpoch).toISOString();

return { since, until };
},
getSortableChoices(sortOptions){
if(!sortOptions || sortOptions === undefined || sortOptions.length === 0){
return []
}

let sortableColumns = []
sortOptions.forEach((opt) => {
sortableColumns.push(opt.sortActive || false)
})
return sortableColumns
},
getColumnSort(sortSelected, sortOptions){
if(!sortSelected || sortSelected.length === 0 || !sortOptions || sortOptions === undefined || sortOptions.length === 0){
return {columnIndex: -1, sortDirection: 'descending'}
}

const sortColumn = sortOptions.filter((x) => x.value === sortSelected[0])[0]
const sortDirection = sortSelected[0].split(" ")[1] === "asc" ? "ascending" : "descending"

return {
columnIndex: sortColumn.columnIndex - 1,
sortDirection: sortDirection
}
},
getInitialSortSelected(sortOptions, filtersMap){
if(!sortOptions || sortOptions === undefined || sortOptions.length === 0){
return ['']
}
if(!filtersMap || filtersMap?.sort === undefined || filtersMap.sort.length === 0){
return [sortOptions[0].value]
}
return filtersMap.sort
}
}

export default tableFunc;
Loading

0 comments on commit 4199041

Please sign in to comment.