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 };
 }

Reply via email to