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 acf34a9  ui dashboards: drop SPA-side widget chunking — BFF already 
chunks faster
acf34a9 is described below

commit acf34a9753a4da17858f5917efd2afaa64f210a0
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 17 20:09:06 2026 +0800

    ui dashboards: drop SPA-side widget chunking — BFF already chunks faster
    
    Reverted the per-chunk Promise.all I added in cc631e1. It made
    instance/service clicks visibly slower because each chunk's BFF
    call ran its own \`listServices\` round-trip to resolve the entity
    (the BFF doesn't cache between calls). For a 12-widget dashboard
    that's 2 redundant ~100-300ms OAP hits per click — purely waste,
    since the BFF already chunks the MQE GraphQL trips internally via
    Promise.all (see http/query/dashboard.ts step 2, MAX_WIDGETS_PER_BATCH=6).
    
    Single BFF call now:
      - 1 listServices (entity resolution + normal flag)
      - N internal MQE chunks via Promise.all (already inside OAP's
        per-query budget)
      - merged response
    
    Loading indicator stays useful but drops the live tick — shows
    "loading 12 metrics" while the BFF batch is in flight, then the
    grid takes over when the merged response lands. Live tick can come
    back later via a streaming BFF endpoint (NDJSON / SSE) without the
    re-resolution overhead.
---
 .../render/layer-dashboard/LayerDashboardsView.vue |  7 +--
 .../render/layer-dashboard/useLayerDashboard.ts    | 69 +++++-----------------
 2 files changed, 17 insertions(+), 59 deletions(-)

diff --git a/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue 
b/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
index cccfd84..961ecd8 100644
--- a/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
+++ b/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
@@ -304,7 +304,7 @@ const effectiveEndpoint = computed<string | null>(() => {
   return endpointList.value.some((e) => e.name === v) ? v : null;
 });
 const widgetsForQuery = computed(() => config.value?.widgets ?? []);
-const { data, isFetching, error, progress } = useLayerDashboard(
+const { data, isFetching, error } = useLayerDashboard(
   layerKey,
   serviceName,
   scope,
@@ -313,7 +313,6 @@ const { data, isFetching, error, progress } = 
useLayerDashboard(
   rangeRef,
   widgetsForQuery,
 );
-const widgetsArrived = computed(() => progress.value.arrived);
 
 // Sequential page-init events for the EventTicker — config →
 // services → service → instances/endpoints → instance/endpoint →
@@ -653,8 +652,8 @@ function isVisible(
             / <b>{{ selectedEndpoint }}</b>
           </template>
         </template>
-        <span class="reading-progress">
-          · {{ widgetsArrived }}/{{ widgets.length }} metric{{ widgets.length 
=== 1 ? '' : 's' }} {{ widgetsArrived === widgets.length ? 'ready' : 'firing' }}
+        <span v-if="widgets.length > 0" class="reading-progress">
+          · loading {{ widgets.length }} metric{{ widgets.length === 1 ? '' : 
's' }}
         </span>
       </span>
     </div>
diff --git a/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts 
b/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
index 7748236..e70fe72 100644
--- a/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
+++ b/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
@@ -38,9 +38,7 @@ import {
 } from '@/controls/configBundle';
 import type {
   DashboardConfig,
-  DashboardResponse,
   DashboardWidget,
-  DashboardWidgetResult,
 } from '@skywalking-horizon-ui/api-client';
 
 export function useLayerDashboardConfig(layerKey: Ref<string>, scope?: 
Ref<string>) {
@@ -134,12 +132,15 @@ export function useLayerDashboard(
     if (!r) return null;
     return `${r.step}:${Math.floor(r.startMs / 60_000)}:${Math.floor(r.endMs / 
60_000)}`;
   });
-  // Per-chunk progress for the loading indicator. Resets to
-  // {arrived: 0, total: N} at queryFn start, ticks up as each
-  // parallel BFF chunk resolves. The widget-list path uses this;
-  // the legacy single-call path leaves it at {0, 0}.
+  // Total widget count for the loading indicator — only used to
+  // surface "N metrics loading" while the BFF batch is in flight.
+  // We don't chunk on the SPA side (each BFF call would re-run
+  // `listServices` to resolve the entity, doubling the latency for
+  // no real win — the BFF already chunks the OAP GraphQL trips
+  // internally via Promise.all, see http/query/dashboard.ts step 2).
+  // Single BFF call → one entity resolution + chunked MQE batches
+  // server-side → one merged response.
   const progress = ref<{ arrived: number; total: number }>({ arrived: 0, 
total: 0 });
-  const CHUNK_SIZE = 6;
 
   const q = useQuery({
     queryKey: [
@@ -151,10 +152,11 @@ export function useLayerDashboard(
       entityRefs.instance ?? computed(() => null),
       entityRefs.endpoint ?? computed(() => null),
       rangeKey,
-      // Hash of widget ids so adding/removing a widget refires.
       computed(() => widgetsList?.value.map((w) => w.id).join('|') ?? null),
     ],
     queryFn: async () => {
+      const total = widgetsList?.value.length ?? 0;
+      progress.value = { arrived: 0, total };
       const baseBody = {
         ...(service.value ? { service: service.value } : {}),
         ...(scope?.value ? { scope: scope.value } : {}),
@@ -167,55 +169,12 @@ export function useLayerDashboard(
               endMs: rangeRef.value.endMs,
             }
           : {}),
+        ...(widgetsList?.value.length ? { widgets: widgetsList.value } : {}),
       };
       const opts = mockTop?.value ? { mockTop: mockTop.value } : {};
-      const all = widgetsList?.value ?? [];
-      // No widget list → fall back to the legacy single-call path:
-      // BFF resolves widgets from the layer template + auto-picks
-      // entities. No per-chunk progress (no list to chunk against).
-      if (all.length === 0) {
-        progress.value = { arrived: 0, total: 0 };
-        return bffClient.layer.dashboard(layerKey.value, baseBody, opts);
-      }
-      // Chunked path. Fire N parallel BFF calls (one per 6-widget
-      // group) so each chunk's completion ticks the progress ref.
-      // Wall-clock is roughly max(chunk_time) thanks to Promise.all;
-      // each chunk is small enough to stay inside OAP's per-query
-      // budget without relying on BFF-internal chunking.
-      progress.value = { arrived: 0, total: all.length };
-      const chunks: DashboardWidget[][] = [];
-      for (let i = 0; i < all.length; i += CHUNK_SIZE) {
-        chunks.push(all.slice(i, i + CHUNK_SIZE));
-      }
-      const merged: DashboardWidgetResult[] = [];
-      let baseResp: DashboardResponse | null = null;
-      await Promise.all(
-        chunks.map(async (chunk) => {
-          const resp = await bffClient.layer.dashboard(
-            layerKey.value,
-            { ...baseBody, widgets: chunk },
-            opts,
-          );
-          // First-arrived chunk donates the response envelope
-          // (`service`, `reachable`, `durationStart/End`, etc.) —
-          // all chunks share the same upstream state since they
-          // were resolved against the same entity.
-          if (!baseResp) baseResp = resp;
-          merged.push(...(resp.widgets ?? []));
-          progress.value = { arrived: merged.length, total: all.length };
-        }),
-      );
-      const finalEnvelope = baseResp ?? {
-        layer: layerKey.value,
-        service: service.value ?? null,
-        generatedAt: Date.now(),
-        step: 'MINUTE' as const,
-        durationStart: '',
-        durationEnd: '',
-        reachable: true,
-        widgets: [],
-      };
-      return { ...finalEnvelope, widgets: merged };
+      const resp = await bffClient.layer.dashboard(layerKey.value, baseBody, 
opts);
+      progress.value = { arrived: resp.widgets?.length ?? total, total };
+      return resp;
     },
     // Trailing-control principle: the widget batch is the deepest
     // control in the chain and must wait for everything upstream

Reply via email to