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 d0a8b15  ui dashboards: gate widget batch on instance/endpoint 
resolution (strict downstream)
d0a8b15 is described below

commit d0a8b155d6442ac6529a5862135439e5b59fa560
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 17 17:36:41 2026 +0800

    ui dashboards: gate widget batch on instance/endpoint resolution (strict 
downstream)
    
    Principle the user spelled out: 后置的控件,必须要监听前置控件的变化,之后再触发,
    不应该自己初始化. The trailing control (the widget batch) must wait
    for every upstream control (layer → service → instance/endpoint)
    to be resolved before it fires, never self-init.
    
    Before, the dashboard query was enabled the moment `service` was
    truthy, even on the instance/endpoint scopes. The BFF then auto-
    picked a default entity, returned data for it, and the widgets
    flashed that data — then the UI's picker auto-resolved a different
    ?instance= / ?endpoint=, the queryKey changed, and the widgets
    re-rendered with the "real" data. Double fire, brief flash of
    wrong entity's metrics, exactly the "instance selected but no
    widget updated" symptom (the UI was actually showing the BFF
    default for a beat before snapping to the pick).
    
    Now `enabled` requires the full chain:
      - service  scope → service
      - instance scope → service + instance
      - endpoint scope → service + endpoint
      - layer-wide scopes (topology / dependency / logs / trace /
        *-profiling) → just layerKey
    
    Widgets stay in their "loading…" state (from f9866cf) until the
    upstream pick lands, then the batch fires once with the right
    entity. The BFF's auto-pick is still there as a safety net for
    direct API consumers but the SPA no longer triggers it.
---
 .../render/layer-dashboard/useLayerDashboard.ts    | 33 ++++++++++++----------
 1 file changed, 18 insertions(+), 15 deletions(-)

diff --git a/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts 
b/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
index 8b54611..0115d71 100644
--- a/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
+++ b/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
@@ -120,25 +120,28 @@ export function useLayerDashboard(
         },
         mockTop?.value ? { mockTop: mockTop.value } : {},
       ),
-    // Gate the metric query on the entity actually being resolved.
-    // Otherwise the dashboard fires twice on landing: once with
-    // `service: null` (BFF then auto-picks the first service by
-    // orderBy, which often differs from the URL-selected one), then
-    // again once the landing rows arrive and `serviceName` resolves.
-    // The first fire returns rows for the wrong service and they
-    // briefly flash on screen — for instance/endpoint scopes the
-    // first fire returns mostly empty widgets because the BFF has no
-    // entity to scope to. Wait until both layer + service are known.
+    // Trailing-control principle: the widget batch is the deepest
+    // control in the chain and must wait for everything upstream
+    // (layer → service → instance/endpoint) to be resolved by the
+    // UI before firing. The BFF can auto-pick a default instance
+    // when the SPA omits one, but doing so silently means the
+    // dashboard fires TWICE on landing (BFF default → then again
+    // when the UI's picker auto-resolves the URL ?instance= it
+    // wants), which manifested as widgets snapping to "BFF default"
+    // data before re-rendering with the operator's actual pick.
     //
-    // Layer-wide scopes (`topology`, `dependency`, `logs`,
-    // `trace*Profiling`, `trace`) don't need a service — keep them
-    // enabled the moment `layerKey` is known.
+    // Gating rules:
+    //   - layer-wide scopes (topology / dependency / logs /
+    //     trace / *-profiling) only need `layerKey`.
+    //   - service scope                          needs service.
+    //   - instance scope needs service + instance.
+    //   - endpoint scope needs service + endpoint.
     enabled: computed(() => {
       if (layerKey.value.length === 0) return false;
       const s = scope?.value ?? 'service';
-      if (s === 'service' || s === 'instance' || s === 'endpoint') {
-        return Boolean(service.value);
-      }
+      if (s === 'service') return Boolean(service.value);
+      if (s === 'instance') return Boolean(service.value && 
entityRefs.instance?.value);
+      if (s === 'endpoint') return Boolean(service.value && 
entityRefs.endpoint?.value);
       return true;
     }),
     staleTime: 25_000,

Reply via email to