This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git
The following commit(s) were added to refs/heads/main by this push:
new f9866cf ui dashboards: selection changes re-trigger cascade, widgets
show loading state
f9866cf is described below
commit f9866cf0f53ced0fe98261adde2f6db6a4e113c6
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 17 17:35:36 2026 +0800
ui dashboards: selection changes re-trigger cascade, widgets show loading
state
Two complementary changes so the page stays interactive during a
service / instance / endpoint pick instead of feeling "frozen":
1. Orchestrator re-arms at the level the selection touches:
- layerKey / scope change → reset all stamps (true new page)
- service change → reset stamps 3-6 (config + services
list stay valid)
- instance change → reset stamps 5 + dashboard
- endpoint change → reset stamps 5 + dashboard
First fire of each watch is the initial mount and is skipped
(the per-step watches already handle that via immediate: true).
2. Widget bodies distinguish "loading" from "no data". On a
selection change, vue-query clears the cached result for the
new key while the new fetch is in flight; widgets used to
render "no data" which is misleading. Now: when isFetching
AND the widget id isn't in `resultsById`, show "loading…"
(or "…" for card widgets). Once the result lands, the normal
render takes over — async, one widget at a time as the
response arrives.
---
.../render/layer-dashboard/LayerDashboardsView.vue | 10 +++--
.../layer-dashboard/useLayerPageOrchestrator.ts | 43 ++++++++++++++++++++--
2 files changed, 45 insertions(+), 8 deletions(-)
diff --git a/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
b/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
index ecfa7ed..53a5be0 100644
--- a/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
+++ b/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
@@ -544,7 +544,9 @@ function isVisible(
<template v-else-if="w.type === 'card'">
<div class="card-value">
<span class="num" :class="{ muted: resultsById.get(w.id)?.value
== null }">
- {{ fmtMetricAs(resultsById.get(w.id)?.value ?? null, w.format)
}}
+ {{ resultsById.has(w.id)
+ ? fmtMetricAs(resultsById.get(w.id)?.value ?? null, w.format)
+ : (isFetching ? '…' : fmtMetricAs(null, w.format)) }}
</span>
<span v-if="w.unit" class="unit">{{ w.unit }}</span>
</div>
@@ -558,7 +560,7 @@ function isVisible(
:accent="widgetColor(w)"
:format="w.format"
/>
- <span v-else class="muted">no data</span>
+ <span v-else class="muted">{{ isFetching && !resultsById.has(w.id)
? 'loading…' : 'no data' }}</span>
</template>
<template v-else-if="w.type === 'top'">
<TopList
@@ -573,7 +575,7 @@ function isVisible(
:unit="w.unit"
:color="widgetColor(w)"
/>
- <span v-else class="muted">no data</span>
+ <span v-else class="muted">{{ isFetching && !resultsById.has(w.id)
? 'loading…' : 'no data' }}</span>
</template>
<template v-else-if="w.type === 'record'">
<!-- Slow-statement / record table — reuses the TopList
@@ -585,7 +587,7 @@ function isVisible(
:unit="w.unit"
:color="widgetColor(w)"
/>
- <span v-else class="muted">no data</span>
+ <span v-else class="muted">{{ isFetching && !resultsById.has(w.id)
? 'loading…' : 'no data' }}</span>
</template>
</div>
</div>
diff --git a/apps/ui/src/render/layer-dashboard/useLayerPageOrchestrator.ts
b/apps/ui/src/render/layer-dashboard/useLayerPageOrchestrator.ts
index 703fe18..89a00f4 100644
--- a/apps/ui/src/render/layer-dashboard/useLayerPageOrchestrator.ts
+++ b/apps/ui/src/render/layer-dashboard/useLayerPageOrchestrator.ts
@@ -290,16 +290,51 @@ export function useLayerPageOrchestrator(refs:
OrchestratorRefs): {
{ immediate: true },
);
- // Re-arm on route / scope change. Both the stamps and `done` reset
- // so the next page produces a fresh top-to-bottom sequence; the
- // event log itself is reset by the router.afterEach hook.
+ // Re-arm scoped to what actually changed. Layer/scope = whole
+ // chain. Service change = step 3 onwards (config + services list
+ // stay valid). Instance/endpoint pick = step 5 + dashboard. This
+ // keeps the EventTicker showing the cascade for each new
+ // selection without spamming a fresh "config / services" pair
+ // for every picker click.
watch(
[() => refs.layerKey.value, () => refs.scope.value],
- () => {
+ (_now, before) => {
+ if (before === undefined) return;
Object.assign(stamps, freshStamps());
done.value = false;
},
);
+ watch(
+ () => refs.effectiveService.value,
+ (_now, before) => {
+ if (before === undefined) return;
+ stamps.service = false;
+ stamps.instances = false;
+ stamps.instance = false;
+ stamps.endpoints = false;
+ stamps.endpoint = false;
+ stamps.dashboard = false;
+ done.value = false;
+ },
+ );
+ watch(
+ () => refs.effectiveInstance.value,
+ (_now, before) => {
+ if (before === undefined) return;
+ stamps.instance = false;
+ stamps.dashboard = false;
+ done.value = false;
+ },
+ );
+ watch(
+ () => refs.effectiveEndpoint.value,
+ (_now, before) => {
+ if (before === undefined) return;
+ stamps.endpoint = false;
+ stamps.dashboard = false;
+ done.value = false;
+ },
+ );
return { done };
}