From cbd00af33a3a694152f00f8f484b09407ffc2b9d Mon Sep 17 00:00:00 2001 From: Adam Tackett Date: Wed, 6 Nov 2024 14:15:53 -0800 Subject: [PATCH] loading state, borders, no movement, focus Signed-off-by: Adam Tackett --- .../__snapshots__/create.test.tsx.snap | 64 +++++-- .../service_config.test.tsx.snap | 10 +- .../components/common/helper_functions.tsx | 19 ++- .../__snapshots__/service_map.test.tsx.snap | 24 ++- .../components/common/plots/service_map.tsx | 160 +++++++++++++----- .../__snapshots__/services.test.tsx.snap | 12 +- .../components/services/services_content.tsx | 1 + test/constants.ts | 57 +++++-- 8 files changed, 265 insertions(+), 82 deletions(-) diff --git a/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap b/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap index edd073981..c3b15e282 100644 --- a/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap +++ b/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap @@ -563,7 +563,9 @@ Object { > + > + Select metric for service map display +
@@ -1841,7 +1843,9 @@ Object { > + > + Select metric for service map display +
@@ -3058,7 +3062,9 @@ Object { > + > + Select metric for service map display +
@@ -4250,7 +4256,9 @@ Object { > + > + Select metric for service map display +
@@ -5535,7 +5543,9 @@ Object { > + > + Select metric for service map display +
@@ -6727,7 +6737,9 @@ Object { > + > + Select metric for service map display +
@@ -7939,7 +7951,9 @@ Object { > + > + Select metric for service map display +
@@ -9092,7 +9106,9 @@ Object { > + > + Select metric for service map display +
@@ -10307,7 +10323,9 @@ Object { > + > + Select metric for service map display +
@@ -11465,7 +11483,9 @@ Object { > + > + Select metric for service map display +
@@ -12714,7 +12734,9 @@ Object { > + > + Select metric for service map display +
@@ -13906,7 +13928,9 @@ Object { > + > + Select metric for service map display +
@@ -15123,7 +15147,9 @@ Object { > + > + Select metric for service map display +
@@ -16315,7 +16341,9 @@ Object { > + > + Select metric for service map display +
@@ -17570,7 +17598,9 @@ Object { > + > + Select metric for service map display +
@@ -18848,7 +18878,9 @@ Object { > + > + Select metric for service map display +
diff --git a/public/components/application_analytics/__tests__/__snapshots__/service_config.test.tsx.snap b/public/components/application_analytics/__tests__/__snapshots__/service_config.test.tsx.snap index c4cbeeec4..f8ad6ce5f 100644 --- a/public/components/application_analytics/__tests__/__snapshots__/service_config.test.tsx.snap +++ b/public/components/application_analytics/__tests__/__snapshots__/service_config.test.tsx.snap @@ -705,6 +705,7 @@ exports[`Service Config component renders empty service config 1`] = ` buttonSize="s" color="text" idSelected="latency" + legend="Select metric for service map display" onChange={[Function]} options={ Array [ @@ -730,7 +731,9 @@ exports[`Service Config component renders empty service config 1`] = ` + > + Select metric for service map display +
+ > + Select metric for service map display +
= 0 ? `rgba(${color}, 1)` : `rgba(${color}, 0.2)`, + borderWidth: 3, + color: { + border: '#4A4A4A', + background: + relatedServices!.indexOf(service) >= 0 ? `rgba(${color}, 1)` : `rgba(${color}, 0.2)`, + }, font: { color: relatedServices!.indexOf(service) >= 0 @@ -194,10 +198,10 @@ export function getServiceMapGraph( }; } else { styleOptions = { - borderWidth: 1.0, + borderWidth: 3, chosen: false, color: { - border: '#DADADC', + border: '#4A4A4A', background: '#FFFFFF', }, }; @@ -540,9 +544,14 @@ interface JsonMapping { } export const extractAttributes = ( - mapping: AttributeMapping['properties'], + mapping: AttributeMapping['properties'] | undefined, prefix: string ): string[] => { + if (!mapping) { + console.warn('Mapping is missing or undefined.'); + return []; + } + let attributes: string[] = []; for (const [key, value] of Object.entries(mapping)) { diff --git a/public/components/trace_analytics/components/common/plots/__tests__/__snapshots__/service_map.test.tsx.snap b/public/components/trace_analytics/components/common/plots/__tests__/__snapshots__/service_map.test.tsx.snap index f3cdbbf3b..390230fd8 100644 --- a/public/components/trace_analytics/components/common/plots/__tests__/__snapshots__/service_map.test.tsx.snap +++ b/public/components/trace_analytics/components/common/plots/__tests__/__snapshots__/service_map.test.tsx.snap @@ -13,6 +13,7 @@ exports[`Service map component renders service map 1`] = ` buttonSize="s" color="text" idSelected="latency" + legend="Select metric for service map display" onChange={[Function]} options={ Array [ @@ -112,7 +113,28 @@ exports[`Service map component renders service map 1`] = ` "position": "relative", } } - /> + > +
+ +
+
) => void; filterByCurrService?: boolean; includeMetricsCallback?: () => void; + mode?: string; }) { const [graphKey, setGraphKey] = useState(0); // adding key to allow for re-renders const [invalid, setInvalid] = useState(false); @@ -85,6 +88,8 @@ export function ServiceMap({ const [items, setItems] = useState({}); const [query, setQuery] = useState(''); const [selectableOptions, setSelectableOptions] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [filterChange, setIsFilterChange] = useState(false); const toggleButtons = [ { @@ -149,11 +154,26 @@ export function ServiceMap({ const options = { layout: { + randomSeed: 10, + improvedLayout: false, + clusterThreshold: 150, hierarchical: { + enabled: false, + }, + }, + physics: { + enabled: true, + stabilization: { enabled: true, - direction: 'UD', // UD, DU, LR, RL - sortMethod: 'directed', // hubsize, directed - shakeTowards: 'leaves', // roots, leaves + iterations: 1000, // Increase iterations for better layout stability + updateInterval: 25, + }, + solver: 'forceAtlas2Based', + forceAtlas2Based: { + gravitationalConstant: -100, // Adjust this for node repulsion + centralGravity: 0.005, + springLength: 200, // Increase to make nodes further apart + springConstant: 0.08, }, }, edges: { @@ -162,7 +182,7 @@ export function ServiceMap({ enabled: true, }, }, - physics: false, + physics: true, }, nodes: { shape: 'dot', @@ -194,8 +214,8 @@ export function ServiceMap({ networkInstance.on('zoom', (params) => { const zoomLevel = params.scale; - if (zoomLevel < 0.25 && zoomLevel < lastZoomLevel) { - networkInstance.moveTo({ scale: 0.25, position: initialPosition }); + if (zoomLevel < 0.1 && zoomLevel < lastZoomLevel) { + networkInstance.moveTo({ scale: 0.1, position: initialPosition }); } else if (zoomLevel > 1.75) { networkInstance.moveTo({ scale: 1.75 }); } @@ -206,6 +226,8 @@ export function ServiceMap({ const addServiceFilter = (selectedServiceName: string) => { if (!addFilter) return; + setGraphKey((prevKey) => prevKey + 1); + addFilter({ field: 'serviceName', operator: 'is', @@ -213,12 +235,24 @@ export function ServiceMap({ inverted: false, disabled: false, }); + setIsFilterChange(true); + if (!['appCreate', 'detailFlyout'].includes(page)) { window.scrollTo({ left: 0, top: 0, behavior: 'smooth' }); } }; const events = { + stabilizationProgress: () => { + setIsLoading(true); + }, + // Disable physics after rendering the tree + stabilizationIterationsDone: () => { + if (network) { + network.setOptions({ physics: { enabled: false } }); + } + setIsLoading(false); + }, select: (event) => { const { nodes } = event; if (!addFilter || !nodes) return; @@ -226,18 +260,17 @@ export function ServiceMap({ if (selectedNode) { const details = { label: selectedNode.label, - average_latency: selectedNode.average_latency, - error_rate: selectedNode.error_rate, - throughput: selectedNode.throughput, + average_latency: selectedNode.average_latency || '-', + error_rate: selectedNode.error_rate || '-', + throughput: selectedNode.throughput || '-', }; - // On traces page with custom sources - // When user clicks on empty graph, load metrics - if (selectableValue.length === 0) { - onChangeSelectable('latency'); + if (serviceMap[selectedNode.label]) { + setSelectedNodeDetails(details); + } else { + console.warn('Selected node details are missing in the new data source.'); + setSelectedNodeDetails(null); } - // Update the state to display node details - setSelectedNodeDetails(details); } }, hoverNode: (_event) => {}, @@ -245,28 +278,41 @@ export function ServiceMap({ const onFocus = (service: string, networkInstance?: any) => { if (service.length === 0) { - // Reset all nodes to the default size when no service is selected - const resetNodes = items.graph.nodes.map((node) => ({ ...node, size: 15 })); - setItems({ - ...items, - graph: { ...items.graph, nodes: resetNodes }, - }); - if (networkInstance) networkInstance.fit(); // Adjust the view if needed + // Reset all nodes to the default size and show the entire graph when no service is selected + const resetGraph = getServiceMapGraph( + serviceMap, + idSelected, + ticks, + undefined, + serviceMap[currService!]?.relatedServices, + false // Do not filter by the current service to show the entire graph + ); + setItems(resetGraph); + + if (networkInstance) networkInstance.fit(); setInvalid(false); } else if (serviceMap[service]) { if (!networkInstance) networkInstance = network; - // Enlarge the focused node and reset others - const updatedNodes = items.graph.nodes.map((node) => - node.label === service ? { ...node, size: 30 } : { ...node, size: 15 } + // Get a filtered graph showing only nodes connected to the focused service + const filteredGraph = getServiceMapGraph( + serviceMap, + idSelected, + ticks, + service, + serviceMap[service]?.relatedServices, + true // Enable filtering by the current service to show only connected nodes ); - setItems({ - ...items, - graph: { ...items.graph, nodes: updatedNodes }, - }); + setItems(filteredGraph); - networkInstance.focus(serviceMap[service].id, { animation: true }); + networkInstance.focus(serviceMap[service].id, { + scale: 0.75, // Higher scale for closer zoom + animation: { + duration: 1000, // Duration of the zoom-in animation in milliseconds + easingFunction: 'easeInOutQuad', + }, + }); setInvalid(false); } else { setInvalid(true); @@ -274,17 +320,30 @@ export function ServiceMap({ }; useEffect(() => { - if (selectedNodeDetails) { - const selectedNode = items?.graph.nodes.find( + setSelectedNodeDetails(null); + setQuery(''); + setItems({}); + + if (filterChange) { + setIsFilterChange(false); + } + }, [mode, filterChange]); + + useEffect(() => { + if (selectedNodeDetails && items?.graph?.nodes) { + const selectedNode = items.graph.nodes.find( (node) => node.label === selectedNodeDetails.label ); - const details = { - label: selectedNode.label, - average_latency: selectedNode.average_latency, - error_rate: selectedNode.error_rate, - throughput: selectedNode.throughput, - }; - setSelectedNodeDetails(details); + + if (selectedNode) { + const details = { + label: selectedNode.label, + average_latency: selectedNode.average_latency || '-', + error_rate: selectedNode.error_rate || '-', + throughput: selectedNode.throughput || '-', + }; + setSelectedNodeDetails(details); + } } }, [items]); @@ -329,6 +388,7 @@ export function ServiceMap({ onChange={(id) => setIdSelected(id as 'latency' | 'error_rate' | 'throughput')} buttonSize="s" color="text" + legend="Select metric for service map display" /> @@ -347,7 +407,9 @@ export function ServiceMap({ const newValue = e.target.value; setQuery(newValue); if (newValue === '') { - onFocus(''); // Clear node focus when input is cleared + setGraphKey((prevKey) => prevKey + 1); + setQuery(''); + onFocus(''); } }} isInvalid={query.length > 0 && invalid} @@ -436,6 +498,24 @@ export function ServiceMap({ }} /> )} + {isLoading && ( +
+ +
+ )} {selectedNodeDetails && (
+ > + Select metric for service map display +
+ > + Select metric for service map display +
) : (
diff --git a/test/constants.ts b/test/constants.ts index fed560878..fc436f41c 100644 --- a/test/constants.ts +++ b/test/constants.ts @@ -209,8 +209,11 @@ export const TEST_SERVICE_MAP_GRAPH = { label: 'order', size: 15, title: 'order\n\n Average duration: 90.1ms \n Error rate: 4.17% \n Request rate: 48', - borderWidth: 0, - color: 'rgba(158, 134, 192, 1)', + borderWidth: 3, + color: { + background: 'rgba(158, 134, 192, 1)', + border: '#4A4A4A', + }, font: { color: 'rgba(72, 122, 180, 1)', }, @@ -224,8 +227,11 @@ export const TEST_SERVICE_MAP_GRAPH = { size: 15, title: 'analytics-service\n\n Average duration: 12.99ms \n Error rate: 0% \n Request rate: 37', - borderWidth: 0, - color: 'rgba(210, 202, 224, 1)', + borderWidth: 3, + color: { + background: 'rgba(210, 202, 224, 1)', + border: '#4A4A4A', + }, font: { color: 'rgba(72, 122, 180, 1)', }, @@ -238,8 +244,11 @@ export const TEST_SERVICE_MAP_GRAPH = { label: 'database', size: 15, title: 'database\n\n Average duration: 49.54ms \n Error rate: 3.77% \n Request rate: 53', - borderWidth: 0, - color: 'rgba(187, 171, 212, 1)', + borderWidth: 3, + color: { + background: 'rgba(187, 171, 212, 1)', + border: '#4A4A4A', + }, font: { color: 'rgba(72, 122, 180, 1)', }, @@ -253,8 +262,11 @@ export const TEST_SERVICE_MAP_GRAPH = { size: 15, title: 'frontend-client\n\n Average duration: 207.71ms \n Error rate: 7.41% \n Request rate: 27', - borderWidth: 0, - color: 'rgba(78, 42, 122, 1)', + borderWidth: 3, + color: { + background: 'rgba(78, 42, 122, 1)', + border: '#4A4A4A', + }, font: { color: 'rgba(72, 122, 180, 1)', }, @@ -267,8 +279,11 @@ export const TEST_SERVICE_MAP_GRAPH = { label: 'inventory', size: 15, title: 'inventory\n\n Average duration: 183.52ms \n Error rate: 3.23% \n Request rate: 31', - borderWidth: 0, - color: 'rgba(95, 61, 138, 1)', + borderWidth: 3, + color: { + background: 'rgba(95, 61, 138, 1)', + border: '#4A4A4A', + }, font: { color: 'rgba(72, 122, 180, 1)', }, @@ -282,8 +297,11 @@ export const TEST_SERVICE_MAP_GRAPH = { size: 15, title: 'authentication\n\n Average duration: 139.09ms \n Error rate: 8.33% \n Request rate: 12', - borderWidth: 0, - color: 'rgba(125, 95, 166, 1)', + borderWidth: 3, + color: { + background: 'rgba(125, 95, 166, 1)', + border: '#4A4A4A', + }, font: { color: 'rgba(72, 122, 180, 1)', }, @@ -296,8 +314,11 @@ export const TEST_SERVICE_MAP_GRAPH = { label: 'payment', size: 15, title: 'payment\n\n Average duration: 134.36ms \n Error rate: 9.09% \n Request rate: 11', - borderWidth: 0, - color: 'rgba(129, 99, 169, 1)', + borderWidth: 3, + color: { + background: 'rgba(129, 99, 169, 1)', + border: '#4A4A4A', + }, font: { color: 'rgba(72, 122, 180, 1)', }, @@ -311,8 +332,11 @@ export const TEST_SERVICE_MAP_GRAPH = { size: 15, title: 'recommendation\n\n Average duration: 176.97ms \n Error rate: 6.25% \n Request rate: 16', - borderWidth: 0, - color: 'rgba(100, 66, 143, 1)', + borderWidth: 3, + color: { + background: 'rgba(100, 66, 143, 1)', + border: '#4A4A4A', + }, font: { color: 'rgba(72, 122, 180, 1)', }, @@ -390,6 +414,7 @@ export const TEST_SERVICE_MAP_GRAPH = { ], }, }; + export const TEST_SERVICE_MAP = { order: { serviceName: 'order',