Mirror of https://github.com/roostorg/osprey github.com/roostorg/osprey
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 186 lines 6.0 kB view raw
1import { useState } from 'react'; 2import { Alert, Button, Card, Spin, Switch } from 'antd'; 3import { AimOutlined } from '@ant-design/icons'; 4import shallow from 'zustand/shallow'; 5import { Css, Core } from 'cytoscape'; 6 7import useRulesVisualizerStore from '../../stores/RulesVisualizerStore'; 8import HierarchicalGraph from './HierarchicalGraph'; 9import RulesVisualizerHeader from './RulesVisualizerHeader'; 10import { Node, NodeType, LabelType } from '../../types/RulesVisualizerTypes'; 11 12import styles from './RulesVisualizer.module.css'; 13import { getGraphJson } from '../../actions/RulesVisualizerActions'; 14 15export const DEFAULT_ANIMATE_DURATION = 1000; 16 17const typeToShape: Record<string, Css.NodeShape> = { 18 [NodeType.Label]: 'ellipse', 19 [NodeType.Rule]: 'round-rectangle', 20}; 21 22const nodeStyle: Css.Node = { 23 content: 'data(label)', 24 'background-color': (node) => getNodeColor(node.data('type'), node.data('label_type')), 25 shape: (node) => typeToShape[node.data('type')], 26}; 27 28const ToolTip = ({ node }: { node: any }) => { 29 return <div>{node.data('file_path')}</div>; 30}; 31 32const RulesVisualizerView = () => { 33 const [updateRuleVizGraph, nodes, edges, selectedFeature, selectedFeatureType, errorMessage] = 34 useRulesVisualizerStore( 35 (state) => [ 36 state.updateRuleVizGraph, 37 state.nodes, 38 state.edges, 39 state.selectedFeature, 40 state.selectedFeatureType, 41 state.errorMessage, 42 ], 43 shallow 44 ); 45 const [isLoading, setIsLoading] = useState(false); 46 const [showLabelUpstream, setShowLabelUpstream] = useState(false); 47 const [showLabelDownstream, setShowLabelDownstream] = useState(true); 48 const [cyto, setCyto] = useState<Core | null>(null); 49 50 const elements = { 51 nodes: (nodes || []).map((node) => ({ 52 data: { 53 id: `${node.id}`, 54 label: getLabel(node), 55 type: node.type, 56 label_type: node.label_type, 57 label_name: node.label_name, 58 entity_name: node.entity_name, 59 file_path: node.file_path, 60 }, 61 })), 62 edges: (edges || []).map((edge, idx) => ({ 63 data: { 64 id: `edge-${idx}`, 65 source: `${edge.source}`, 66 target: `${edge.target}`, 67 }, 68 })), 69 }; 70 71 let alert; 72 if (nodes && !nodes.length) { 73 alert = <Alert className={styles.centered} type="warning" message="No associated nodes were found." />; 74 } else if (errorMessage) { 75 alert = <Alert className={styles.centered} type="error" message={`Error: ${errorMessage}. Please try again.`} />; 76 } 77 78 const onGraphLoad = (cy: Core) => { 79 // View defaults to fitting whole graph within viewport. Disable zooming out past that. 80 cy.minZoom(cy.zoom()); 81 setCyto(cy); 82 }; 83 84 const recenterOnClick = () => { 85 if (cyto) { 86 cyto.animate({ 87 easing: 'ease-in-out', 88 duration: DEFAULT_ANIMATE_DURATION, 89 fit: { eles: cyto.elements(), padding: 0 }, 90 }); 91 } 92 }; 93 94 const onShowLabelUpstreamToggle = async (checked: boolean) => { 95 setShowLabelUpstream(checked); 96 await rerenderLabelViewGraph(checked, showLabelDownstream); 97 }; 98 99 const onShowLabelDownstreamToggle = async (checked: boolean) => { 100 setShowLabelDownstream(checked); 101 await rerenderLabelViewGraph(showLabelUpstream, checked); 102 }; 103 104 const rerenderLabelViewGraph = async (show_upstream: boolean, show_downstream: boolean) => { 105 if (!selectedFeature || !selectedFeatureType) { 106 return; 107 } 108 setIsLoading(true); 109 updateRuleVizGraph({ nodes: null, edges: null }); 110 const graphJson = await getGraphJson( 111 selectedFeatureType.toLowerCase(), 112 [`${selectedFeature}`], 113 show_upstream, 114 show_downstream 115 ); 116 updateRuleVizGraph(graphJson); 117 setIsLoading(false); 118 }; 119 120 return ( 121 <div className={styles.viewContainer}> 122 <RulesVisualizerHeader 123 cy={cyto} 124 setIsLoading={setIsLoading} 125 showLabelUpstream={showLabelUpstream} 126 showLabelDownstream={showLabelDownstream} 127 /> 128 <Spin className={styles.centered} size="large" spinning={isLoading} /> 129 {alert} 130 <div className={styles.graphContainer}> 131 {selectedFeatureType === 'Label' && ( 132 <Card size="small" title="Label View Filters" className={styles.recenterCard}> 133 <Switch onChange={onShowLabelUpstreamToggle} disabled={isLoading} defaultChecked={showLabelUpstream} /> Show 134 Upstream Nodes 135 <br></br> 136 <Switch 137 onChange={onShowLabelDownstreamToggle} 138 disabled={isLoading} 139 defaultChecked={showLabelDownstream} 140 />{' '} 141 Show Downstream Nodes 142 </Card> 143 )} 144 <HierarchicalGraph elements={elements} nodeStyle={nodeStyle} onLoad={onGraphLoad} ToolTip={ToolTip} /> 145 {nodes && !!nodes.length && ( 146 <Button 147 className={styles.recenterButton} 148 shape="circle" 149 icon={<AimOutlined />} 150 size="large" 151 onClick={recenterOnClick} 152 /> 153 )} 154 </div> 155 </div> 156 ); 157}; 158 159function getLabel(node: Node) { 160 if (node.type === NodeType.Label) { 161 return `${node.type} [${getLabelTypeName(node.label_type)}]\n${node.label_name}\non ${ 162 node.entity_name ? node.entity_name : 'Unassigned' 163 }`; 164 } 165 return `${node.type}\n${node.value}`; 166} 167 168function getLabelTypeName(label_type?: string) { 169 const labelTypeName = Object.entries(LabelType).find(([key, value]) => value === label_type); 170 return labelTypeName?.[0] || ''; 171} 172 173function getNodeColor(type: string, label_type?: string) { 174 if (type === NodeType.Label && label_type == LabelType.Check) { 175 return '#DEA39E'; 176 } else if (type === NodeType.Label && label_type == LabelType.Add) { 177 return '#D4DE9E'; 178 } else if (type === NodeType.Label && label_type == LabelType.Remove) { 179 return '#BC916E'; 180 } else if (type === NodeType.Rule) { 181 return '#CAE0F9'; 182 } 183 return '#8F8F8F'; 184} 185 186export default RulesVisualizerView;