This is an automated email from the ASF dual-hosted git repository. wu-sheng pushed a commit to branch fix/3d-fps-and-layer-rehydrate in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
commit 3627d154651d30c2f4824037a6eafd28b0d21eb6 Author: Wu Sheng <[email protected]> AuthorDate: Sun May 31 09:00:00 2026 +0800 fix(layer): validate URL ?service= against the full roster The auto-pick watch validated selectedId only against the landing top-N sample, so a valid low-traffic ?service= outside the sample was treated as stale and overwritten by the first sampled row (clearing instance/endpoint too). Validate against useLayerServices (the layer's real catalog); only a genuinely-absent id auto-corrects, and only once the roster has loaded. --- apps/ui/src/layer/LayerShell.vue | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/ui/src/layer/LayerShell.vue b/apps/ui/src/layer/LayerShell.vue index 4247c59..e0b2438 100644 --- a/apps/ui/src/layer/LayerShell.vue +++ b/apps/ui/src/layer/LayerShell.vue @@ -42,6 +42,7 @@ import { useTimeRangeStore } from '@/controls/timeRange'; import { useLayers, firstLayerTab } from '@/shell/useLayers'; import { layerContentToDef, type LayerTemplateContent } from '@/shell/layerFromTemplate'; import { useSelectedService } from '@/layer/useSelectedService'; +import { useLayerServices } from '@/layer/useLayerServices'; import { useLayerSelectionStore } from '@/state/layerSelection'; import { useSetupStore } from '@/state/setup'; import { fmtMetric } from '@/utils/formatters'; @@ -303,19 +304,31 @@ const isZipkinTrace = computed<boolean>(() => { return scopeSegment.value === 'trace' && layer.value?.traces?.source === 'zipkin'; }); +// Full service roster (the layer's REAL catalog, independent of landing's +// top-N sample which misses low-traffic services). A URL `?service=` is +// validated against THIS, not the sample — otherwise a valid but +// low-traffic deep link is wrongly treated as stale. +const { services: fullRoster, isLoading: rosterLoading } = useLayerServices(layerKey); + // Keep the URL-backed service selection honest for every page that -// uses the shell picker. A stale `?service=` can survive navigation or -// manual URL entry; the switch label used to fall back visually to the -// first row while the metric query still waited for a valid service. +// uses the shell picker. A `?service=` outside the landing sample is +// trusted when it exists in the full roster; only a genuinely stale id +// (absent from the roster) auto-corrects to the first sampled row, and +// only once the roster has loaded so a valid pin isn't clobbered in flight. watch( - [sampledServices, selectedId, viewOwnsServiceSelector], - ([rows, id, ownsSelector]) => { + [sampledServices, selectedId, viewOwnsServiceSelector, fullRoster, rosterLoading], + ([rows, id, ownsSelector, roster, rosterIsLoading]) => { if (ownsSelector) return; const first = rows[0]; if (!first) return; - if (!id || !rows.some((s) => s.serviceId === id)) { + if (!id) { setSelected(first.serviceId); + return; } + if (rows.some((s) => s.serviceId === id)) return; // in the sample → keep + if (rosterIsLoading) return; // don't clobber a pin while the roster loads + if (roster.some((s) => s.id === id)) return; // valid in the full roster → keep + setSelected(first.serviceId); // genuinely stale → fall back }, { immediate: true }, );
