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