Skip to content

Commit

Permalink
Add cell filtering to tables. Closes #662.
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelBurgess committed Jan 9, 2025
1 parent 0c31073 commit 3985e20
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 29 deletions.
39 changes: 26 additions & 13 deletions ui/dashboard/src/components/dashboards/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,11 @@ const getColumns = (
const columnVisibility: {
[key: string]: boolean;
} = {};
const filtersByColumn: KeyValuePairs<Filter[]> = {};
if (!isDetectionTable) {
for (const filter of filters) {
if (filter.key && !(filter.key in filtersByColumn)) {
filtersByColumn[filter.key] = [];
}
filtersByColumn[filter.key].push(filter);
}
}

const columnLookup: KeyValuePairs<LeafNodeDataColumn> = {};

const columns: TableColumnInfo[] = cols.map((col) => {
columnLookup[col.name] = col;
let colHref: string | null = null;
let colWrap: TableColumnWrap = "none";
if (properties?.columns?.[col.original_name || col.name]) {
Expand All @@ -118,6 +112,16 @@ const getColumns = (
}
}

const filtersByColumn: KeyValuePairs<Filter[]> = {};
if (!isDetectionTable) {
for (const filter of filters) {
if (filter.key && !(filter.key in filtersByColumn)) {
filtersByColumn[filter.key] = [];
}
filtersByColumn[filter.key].push(filter);
}
}

// If we've got display columns set up and this column hasn't already had its default visibility set,
// and it's not listed as a column to show, hide it by default
if (
Expand All @@ -136,13 +140,22 @@ const getColumns = (
data_type: col.data_type,
wrap: colWrap,
sortType: col.data_type === "BOOL" ? "basic" : "alphanumeric",
// Generic filter function that will apply all filters for a column
filterFn: (row: Row<any>, columnId: string) => {
const filtersForColumn = filtersByColumn[columnId];
if (!filtersForColumn.length) {
return true;
}
const columnInfo = columnLookup[columnId];
for (const filter of filtersForColumn) {
const match = applyFilter(filter, row.original[filter.key]);
const value = row.original[filter.key];
const match = applyFilter(
filter,
value,
columnInfo.data_type === "jsonb" ||
columnInfo.data_type === "varchar[]" ||
isObject(value),
);
if (!match) {
return false;
}
Expand Down Expand Up @@ -868,7 +881,7 @@ const TableViewVirtualizedRows = (props: TableProps) => {
icon: "add_column_right",
action: async () => {
selectSidePanel({
props,
panel: props,
context: {
mode: "settings",
leafColumns: table.getAllLeafColumns(),
Expand Down Expand Up @@ -935,7 +948,7 @@ const TableViewVirtualizedRows = (props: TableProps) => {
: "filter_alt_off"
}
/>
<span>{`${filter.key}: ${filter.value}`}</span>
<span>{`${filter.key}: ${isObject(filter.value) ? JSON.stringify(filter.value) : filter.value}`}</span>
<span
onClick={() => removeFilter(filter.key, filter.value)}
className="cursor-pointer text-black-scale-6 hover:text-black-scale-8 focus:outline-none"
Expand Down Expand Up @@ -1184,4 +1197,4 @@ registerComponent("table", Table);

export default Table;

export { TableViewWrapper };
export { TableViewVirtualizedRows as TableViewWrapper };
32 changes: 19 additions & 13 deletions ui/dashboard/src/components/dashboards/grouping/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,25 +346,31 @@ export const summaryCardFilterPath = ({
}
};

export const applyFilter = (filter: Filter, value: string) => {
const isEqual = (a: any, b: any, isJson: boolean) => {
return isJson ? JSON.stringify(a) === JSON.stringify(b) : a === b;
};

export const applyFilter = (
filter: Filter,
value: any,
isJson: boolean = false,
) => {
// Perform operation based on the filter operator
switch (filter.operator) {
case "equal":
// Ensure filter value is a string and compare directly
return value === filter.value;

return isEqual(value, filter.value, isJson);
case "not_equal":
// Ensure filter value is a string and compare directly
return value !== filter.value;

return !isEqual(value, filter.value, isJson);
case "in":
// Ensure filter value is an array and check if value exists in the array
return Array.isArray(filter.value) && filter.value.includes(value);

return (
Array.isArray(filter.value) &&
filter.value.some((v) => isEqual(value, filter.value, isJson))
);
case "not_in":
// Ensure filter value is an array and check if value does NOT exist in the array
return Array.isArray(filter.value) && !filter.value.includes(value);

return (
Array.isArray(filter.value) &&
!filter.value.some((v) => isEqual(value, filter.value, isJson))
);
default:
// If an unknown operator is provided, return false
return false;
Expand Down
2 changes: 1 addition & 1 deletion ui/dashboard/src/hooks/useDashboardPanelDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface IDashboardPanelDetailContext {
closePanel: () => void;
panelOverrideData: LeafNodeData | null;
selectedSidePanel: SidePanelInfo | null;
selectSidePanel: (SidePanelInfo: SidePanelInfo | null) => void;
selectSidePanel: (sidePanelInfo: SidePanelInfo | null) => void;
closeSidePanel: () => void;
}

Expand Down
29 changes: 27 additions & 2 deletions ui/dashboard/src/hooks/useDetectionGrouping.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import DetectionErrorNode from "@powerpipe/components/dashboards/grouping/common
import DetectionKeyValuePairNode from "@powerpipe/components/dashboards/grouping/common/node/DetectionKeyValuePairNode";
import DetectionRootNode from "@powerpipe/components/dashboards/grouping/common/node/DetectionRootNode";
import DetectionRunningNode from "@powerpipe/components/dashboards/grouping/common/node/DetectionRunningNode";
import isObject from "lodash/isObject";
import useFilterConfig from "./useFilterConfig";
import useGroupingConfig from "@powerpipe/hooks/useGroupingConfig";
import usePrevious from "./usePrevious";
Expand Down Expand Up @@ -36,7 +37,10 @@ import {
PanelsMap,
} from "@powerpipe/types";
import { KeyValuePairs } from "@powerpipe/components/dashboards/common/types";
import { LeafNodeDataRow } from "@powerpipe/components/dashboards/common";
import {
LeafNodeDataColumn,
LeafNodeDataRow,
} from "@powerpipe/components/dashboards/common";
import { useDashboardState } from "./useDashboardState";
import { useDashboardControls } from "@powerpipe/components/dashboards/layout/Dashboard/DashboardControlsProvider";

Expand Down Expand Up @@ -590,6 +594,8 @@ const includeResult = (
return true;
}

const columnLookup: KeyValuePairs<LeafNodeDataColumn> = {};

let matches: boolean[] = [];
for (const filter of [filterForRootPanel, filterForDetection].filter(
(f) => !!f && !!f.expressions?.length,
Expand Down Expand Up @@ -619,11 +625,30 @@ const includeResult = (
case "dimension": {
let newRows: LeafNodeDataRow[] = [];
let includeRow = false;
let column: LeafNodeDataColumn | undefined;
if (!(expression.key in columnLookup)) {
column = result?.columns?.find((c) => c.name === expression.key);
if (!column) {
matches.push(false);
break;
}
columnLookup[expression.key] = column;
} else {
column = columnLookup[expression.key];
}

for (const row of result.rows || []) {
const rowValue = row[expression.key];
includeRow =
!!expression.key &&
expression.key in row &&
applyFilter(expression, row[expression.key]);
applyFilter(
expression,
rowValue,
column.data_type === "jsonb" ||
column.data_type === "varchar[]" ||
isObject(rowValue),
);
if (includeRow) {
newRows.push(row);
}
Expand Down

0 comments on commit 3985e20

Please sign in to comment.