feat(service-map): dotted canvas, metric color toggle, and popover polish#2601
feat(service-map): dotted canvas, metric color toggle, and popover polish#2601elizabetdev wants to merge 3 commits into
Conversation
…lish Add a dotted background to the service map canvas and a Latency / Error rate / Throughput segmented control that recolors nodes by the selected metric, with a legend (sequential light-to-dark ramp, shared by node fills and the legend swatch) that also documents node size = throughput. The canvas and its controls now follow the app's light/dark color scheme instead of being hardcoded to dark. Polish the node popover into a raised surface with a service-name header, grouped sections, and severity-aware error coloring. Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 8ab8a38 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR polishes the Service Map page with a dotted canvas background, a metric color toggle (Latency / Error rate / Throughput) backed by a React context so metric switching recolors nodes instantly without re-running the dagre layout, a legend that shares ramp endpoints with node fills, light/dark color-scheme support, and an overhauled node popover with a service-name header and severity-aware error coloring.
Confidence Score: 5/5Safe to merge — purely additive UX changes to the service map with no data-layer or API impact. All changed files are scoped to the ServiceMap component subtree. The new metric context, color ramp, and legend are well-tested (54 unit tests with property-based ramp assertions). The 'Clear focus' toggle is correctly wired: the parent's onFocusService in DBServiceMapPage already implements the toggle-to-clear pattern. No data mutations, auth logic, or shared infrastructure is touched. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[ServiceMap outer component] -->|derives focusedService\nfrom serviceNames| B[ServiceMapPresentation]
B -->|useState: metric| C[SegmentedControl]
B -->|useMemo: metricMax| D[ServiceMapMetricContext.Provider]
D --> E[ReactFlow]
E --> F[ServiceMapNode]
F -->|useContext| D
F -->|getNodeColors\ngetServiceMetricValue| G[utils.ts\nrampFill / getNodeColors]
F --> H[ServiceMapTooltip\nisFocused / onFocus]
B --> I[ServiceMapLegend\ngetMetricGradientCss]
I -->|same ramp endpoints| G
C -->|setMetric| B
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[ServiceMap outer component] -->|derives focusedService\nfrom serviceNames| B[ServiceMapPresentation]
B -->|useState: metric| C[SegmentedControl]
B -->|useMemo: metricMax| D[ServiceMapMetricContext.Provider]
D --> E[ReactFlow]
E --> F[ServiceMapNode]
F -->|useContext| D
F -->|getNodeColors\ngetServiceMetricValue| G[utils.ts\nrampFill / getNodeColors]
F --> H[ServiceMapTooltip\nisFocused / onFocus]
B --> I[ServiceMapLegend\ngetMetricGradientCss]
I -->|same ramp endpoints| G
C -->|setMetric| B
Reviews (3): Last reviewed commit: "feat(service-map): state-aware focus CTA..." | Re-trigger Greptile |
| // Latency coloring is only meaningful when the source exposes duration data; | ||
| // otherwise every node reports 0 and the option is disabled. | ||
| const hasLatencyData = metricMax.latency > 0; |
There was a problem hiding this comment.
When
hasLatencyData flips to false (e.g. the data window is changed and the refreshed dataset has no duration data), the metric state stays 'latency'. The toggle correctly disables the option, but all nodes remain colored on the latency ramp with metricMax.latency === 0, giving every node the same minimum-intensity fill until the user manually switches. A short effect that resets metric whenever the active metric loses its data prevents the stuck-state.
| // Latency coloring is only meaningful when the source exposes duration data; | |
| // otherwise every node reports 0 and the option is disabled. | |
| const hasLatencyData = metricMax.latency > 0; | |
| // Latency coloring is only meaningful when the source exposes duration data; | |
| // otherwise every node reports 0 and the option is disabled. | |
| const hasLatencyData = metricMax.latency > 0; | |
| // If the active metric loses its data (e.g. time window changes and the new | |
| // dataset has no latency), fall back to errorRate so nodes don't all render | |
| // at the same zero-intensity color. | |
| useEffect(() => { | |
| if (metric === 'latency' && !hasLatencyData) { | |
| setMetric('errorRate'); | |
| } | |
| }, [metric, hasLatencyData]); |
| case 'throughput': | ||
| return isSingleTrace | ||
| ? `${formatApproximateNumber(max)} reqs` | ||
| : formatRate(getRequestsPerSecond(max, dateRange)); |
There was a problem hiding this comment.
formatApproximateNumber always prepends ~ (e.g. ~5 reqs), but for single-trace maps the tooltip shows the raw integer without any ~ prefix (e.g. 5 incoming requests). Using the raw number directly here would be consistent with the tooltip.
| case 'throughput': | |
| return isSingleTrace | |
| ? `${formatApproximateNumber(max)} reqs` | |
| : formatRate(getRequestsPerSecond(max, dateRange)); | |
| case 'throughput': | |
| return isSingleTrace | |
| ? `${max} reqs` | |
| : formatRate(getRequestsPerSecond(max, dateRange)); |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
E2E Test Results❌ 1 test failed • 222 passed • 3 skipped • 1482s
Tests ran across 4 shards in parallel. |
…over Focus used space-between with a short label, so the target icon floated to the far edge and read as broken. Switch to a centered light button with the icon as a leftSection so it looks like a distinct action, separate from the drill-in stat rows above it. Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the vague "Focus" button with a clearer, state-aware call to action: "Focus on this service" when unfocused and "Clear focus" when the map is already scoped to that service, with matching target / target-off icons. Threads the currently focused service down to each node so the tooltip reflects the toggle state. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Polishes the Service Map page (
@xyflow/reactgraph) with a set of related UX improvements:<Background variant={Dots} />, so the dots pan/zoom with the graph (the standard infinite-canvas feel) rather than a static CSS backdrop.Latency / Error rate / Throughputsegmented control that recolors nodes by the selected dimension. The active metric + graph-wide maxes flow through a small React context (ServiceMapMetricContext) so switching metrics recolors instantly without rebuilding nodes or re-running the dagre layout (preserving zoom/positions).getMetricGradientCss), so they can never drift.colorModeand zoom controls now follow the app's resolved color scheme instead of being hardcoded todark.Why
The service map lacked the spatial affordances and single-metric focus common to node-graph tools (Splunk APM, Datadog, ARMS), the node coloring only conveyed error rate and read as washed-out (grey→color), and the popover blended into the canvas with no title or hierarchy.
Test plan
yarn ci:unit—utils.test.tscovers the sequential ramp (light→dark, monotonic, capping, per-metric hue),getServiceMetricValue, andgetMetricGradientCss(54 tests, 100% coverage ofutils.ts)./service-mapin light and dark mode:Notes
@hyperdx/apppatch).Made with Cursor