From c115462b02808795d560649e8f5925c0b8771326 Mon Sep 17 00:00:00 2001 From: jmeridth Date: Thu, 25 May 2023 10:29:03 -0500 Subject: [PATCH] feat: Add Select All checkbox to workflow dag filter options Fixes #11129 - [x] also fixed bug when click some labels of checkboxes, they would change state of other same-named checkboxes. This was due to non-unique IDs on the checkboxes. Signed-off-by: jmeridth Co-Authored-by: Ben Compton <3343482+bencompton@users.noreply.github.com> Co-Authored-by: Steven Johnson --- .../shared/components/filter-drop-down.tsx | 4 +- .../shared/components/graph/graph-panel.tsx | 98 ++++++++++++++----- 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/ui/src/app/shared/components/filter-drop-down.tsx b/ui/src/app/shared/components/filter-drop-down.tsx index cebbcf2c28e8d..fe30983032abd 100644 --- a/ui/src/app/shared/components/filter-drop-down.tsx +++ b/ui/src/app/shared/components/filter-drop-down.tsx @@ -36,8 +36,8 @@ export const FilterDropDown = (props: FilterDropDownProps) => { .map(([label, checked]) => (
  • - item.onChange(label, v)} /> - + item.onChange(label, v)} /> +
  • ))} diff --git a/ui/src/app/shared/components/graph/graph-panel.tsx b/ui/src/app/shared/components/graph/graph-panel.tsx index 6a2237695f7fc..32f0e45dd45e6 100644 --- a/ui/src/app/shared/components/graph/graph-panel.tsx +++ b/ui/src/app/shared/components/graph/graph-panel.tsx @@ -13,29 +13,21 @@ require('./graph-panel.scss'); type IconShape = 'rect' | 'circle'; -interface NodeGenres { +interface INodeSelectionMap { [type: string]: boolean; } -interface NodeClassNames { - [type: string]: boolean; -} - -interface NodeTags { - [key: string]: boolean; -} - interface Props { graph: Graph; storageScope: string; // the scope of storage, similar graphs should use the same vaulue options?: React.ReactNode; // add to the option panel classNames?: string; nodeGenresTitle: string; - nodeGenres: NodeGenres; + nodeGenres: INodeSelectionMap; nodeClassNamesTitle?: string; - nodeClassNames?: NodeClassNames; + nodeClassNames?: INodeSelectionMap; nodeTagsTitle?: string; - nodeTags?: NodeTags; + nodeTags?: INodeSelectionMap; nodeSize?: number; // default "64" horizontal?: boolean; // default "false" hideNodeTypes?: boolean; // default "false" @@ -53,9 +45,10 @@ export const GraphPanel = (props: Props) => { const [nodeSize, setNodeSize] = React.useState(storage.getItem('nodeSize', props.nodeSize)); const [horizontal, setHorizontal] = React.useState(storage.getItem('horizontal', !!props.horizontal)); const [fast, setFast] = React.useState(storage.getItem('fast', false)); - const [nodeGenres, setNodeGenres] = React.useState(storage.getItem('nodeGenres', props.nodeGenres)); - const [nodeClassNames, setNodeClassNames] = React.useState(storage.getItem('nodeClassNames', props.nodeClassNames)); - const [nodeTags, setNodeTags] = React.useState(props.nodeTags); + const [nodeGenres, setNodeGenres] = React.useState(storage.getItem('nodeGenres', props.nodeGenres)); + const [nodeClassNames, setNodeClassNames] = React.useState(storage.getItem('nodeClassNames', props.nodeClassNames)); + const [nodeTags, setNodeTags] = React.useState(props.nodeTags); + const [checkAll, setCheckAll] = React.useState(true); const [nodeSearchKeyword, setNodeSearchKeyword] = React.useState(''); useEffect(() => storage.setItem('nodeSize', nodeSize, props.nodeSize), [nodeSize]); @@ -70,6 +63,11 @@ export const GraphPanel = (props: Props) => { useEffect(() => setNodeClassNames(merge(nodeClassNames, props.nodeClassNames)), [props.nodeClassNames]); useEffect(() => setNodeTags(merge(nodeTags, props.nodeTags)), [props.nodeTags]); + useEffect(() => { + const allSelected = getIsAllSelected(nodeClassNames, nodeTags, nodeGenres); + setCheckAll(allSelected); + }, [nodeGenres, nodeClassNames, nodeTags]); + const visible = (id: Node) => { const label = props.graph.nodes.get(id); // If the node matches the search string, return without considering filters @@ -91,6 +89,54 @@ export const GraphPanel = (props: Props) => { return true; }; + const checkBoxHandler = (callback: React.Dispatch>, label: string, checked: boolean) => { + callback(v => { + return { + ...v, + [label]: checked + }; + }); + }; + + const getNodeMapWithAllNodesSetToValue = (nodeSelectionMap: INodeSelectionMap, value: boolean) => { + return Object.keys(nodeSelectionMap).reduce((accumulatedMap, nextKey) => { + return { + ...accumulatedMap, + [nextKey]: value + }; + }, {}); + }; + + const getIsAllSelected = (nodeClassNames: INodeSelectionMap, nodeTags: INodeSelectionMap, nodeGenres: INodeSelectionMap) => { + const nodeSelections: INodeSelectionMap = {...nodeClassNames, ...nodeTags, ...nodeGenres}; + const totalNodeCount = Object.keys(nodeSelections).length; + + const selectedNodeCount = Object.keys(nodeSelections).reduce((accumulatedTotal, key) => { + if (nodeSelections[key]) { + return accumulatedTotal + 1; + } else { + return accumulatedTotal; + } + }, 0); + + return selectedNodeCount === totalNodeCount; + }; + + const onSelectAll = ( + nodeClassNames: INodeSelectionMap, + nodeTags: INodeSelectionMap, + nodeGenres: INodeSelectionMap, + setNodeClassNames: React.Dispatch>, + setNodeTags: React.Dispatch>, + setNodeGenres: React.Dispatch> + ) => { + const isAllSelected = getIsAllSelected(nodeClassNames, nodeTags, nodeGenres); + + setNodeClassNames(getNodeMapWithAllNodesSetToValue(nodeClassNames, !isAllSelected)); + setNodeTags(getNodeMapWithAllNodesSetToValue(nodeTags, !isAllSelected)); + setNodeGenres(getNodeMapWithAllNodesSetToValue(nodeGenres, !isAllSelected)); + }; + layout(props.graph, nodeSize, horizontal, id => !visible(id), fast); const width = props.graph.width; const height = props.graph.height; @@ -101,34 +147,32 @@ export const GraphPanel = (props: Props) => {
    { + onSelectAll(nodeClassNames, nodeTags, nodeGenres, setNodeClassNames, setNodeTags, setNodeGenres); + } + }, { title: props.nodeGenresTitle, values: nodeGenres, onChange: (label, checked) => { - setNodeGenres(v => { - v[label] = checked; - return Object.assign({}, v); - }); + checkBoxHandler(setNodeGenres, label, checked); } }, { title: props.nodeClassNamesTitle, values: nodeClassNames, onChange: (label, checked) => { - setNodeClassNames(v => { - v[label] = checked; - return Object.assign({}, v); - }); + checkBoxHandler(setNodeClassNames, label, checked); } }, { title: props.nodeTagsTitle, values: nodeTags, onChange: (label, checked) => { - setNodeTags(v => { - v[label] = checked; - return Object.assign({}, v); - }); + checkBoxHandler(setNodeTags, label, checked); } } ]}