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 cff0f8b  ui dashboards: wire global time-picker into landing + widget 
queryKeys
cff0f8b is described below

commit cff0f8b638fcad8f74347fe24275d7acc61f03b0
Author: Wu Sheng <[email protected]>
AuthorDate: Sun May 17 17:39:02 2026 +0800

    ui dashboards: wire global time-picker into landing + widget queryKeys
    
    Closing the second half of the principle 各个控件,也会有自己的选择事件,
    触发进一步的下次更新: the time picker is itself an upstream control and
    must trigger the downstream landing rollup + widget batch when the
    operator changes it.
    
    - useLayerLanding gains an optional `range` ref. Threaded into a
      bucketed queryKey (per-minute granularity so rolling presets
      don't invalidate the cache on every paint) and into the BFF
      body via the existing `range` arg.
    - useLayerDashboard gains the same optional `range` ref +
      bucketed key.
    - LayerDashboardsView reads the global useTimeRangeStore and
      feeds the resulting ref to both. Other view callers
      (LayerShell / LayerTracesView / LayerLogsView / profiling)
      don't pass range — they manage their own time picker or use
      the BFF default, behaviour unchanged.
    
    With this, a time-picker change cascades exactly like a service
    or instance pick: queryKey changes → vue-query refires → widgets
    show "loading…" (per f9866cf) → results stream in async per
    widget.
---
 apps/ui/src/layer/useLayerLanding.ts               | 25 ++++++++++++++++--
 .../render/layer-dashboard/LayerDashboardsView.vue | 13 +++++++++-
 .../render/layer-dashboard/useLayerDashboard.ts    | 30 ++++++++++++++++++++++
 3 files changed, 65 insertions(+), 3 deletions(-)

diff --git a/apps/ui/src/layer/useLayerLanding.ts 
b/apps/ui/src/layer/useLayerLanding.ts
index b22b199..e0ae23f 100644
--- a/apps/ui/src/layer/useLayerLanding.ts
+++ b/apps/ui/src/layer/useLayerLanding.ts
@@ -30,9 +30,19 @@ import { bffClient } from '@/api/client';
  * The query key includes the resolved column set so changing a layer's
  * setup (in Stage 2.3+) re-fetches automatically.
  */
+export interface LandingRange {
+  step: 'MINUTE' | 'HOUR' | 'DAY';
+  startMs: number;
+  endMs: number;
+}
+
 export function useLayerLanding(
   layer: Ref<LayerDef>,
   cfg: Ref<LandingConfig>,
+  /** Optional global time-range ref. Threaded into the BFF body
+   *  + queryKey so a time-picker change refires the landing
+   *  rollup the same way a layer change does. */
+  range?: Ref<LandingRange | null>,
 ) {
   const layerKey = computed(() => layer.value.key);
   // Cache key reflects every field that changes the server response —
@@ -45,10 +55,21 @@ export function useLayerLanding(
     spark: cfg.value.spark,
     throughput: cfg.value.throughput,
   }));
+  const rangeRef = range ?? computed<LandingRange | null>(() => null);
+  const rangeKey = computed(() => {
+    const r = rangeRef.value;
+    if (!r) return null;
+    return `${r.step}:${Math.floor(r.startMs / 60_000)}:${Math.floor(r.endMs / 
60_000)}`;
+  });
 
   const q = useQuery({
-    queryKey: ['layer-landing', layerKey, cfgHash],
-    queryFn: () => bffClient.layer.landing(layerKey.value, cfg.value),
+    queryKey: ['layer-landing', layerKey, cfgHash, rangeKey],
+    queryFn: () =>
+      bffClient.layer.landing(
+        layerKey.value,
+        cfg.value,
+        rangeRef.value ?? undefined,
+      ),
     staleTime: 45_000,
     refetchInterval: 60_000,
     refetchOnWindowFocus: true,
diff --git a/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue 
b/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
index 53a5be0..eca4d87 100644
--- a/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
+++ b/apps/ui/src/render/layer-dashboard/LayerDashboardsView.vue
@@ -37,6 +37,7 @@ import { useLayerPageOrchestrator } from 
'@/render/layer-dashboard/useLayerPageO
 import { useLayerEndpoints } from '@/layer/useLayerEndpoints';
 import { useLayerInstances } from '@/layer/useLayerInstances';
 import { useLayerLanding } from '@/layer/useLayerLanding';
+import { useTimeRangeStore } from '@/controls/timeRange';
 import { useLayers } from '@/shell/useLayers';
 import { useSelectedEndpoint } from '@/layer/useSelectedEndpoint';
 import { useSelectedInstance } from '@/layer/useSelectedInstance';
@@ -82,7 +83,16 @@ const safeCfg = computed(() => {
   if (!layer.value) return { priority: 99, topN: 5, orderBy: 'cpm', columns: 
[], style: 'table' as const };
   return store.ensure(layer.value.key, { slots: layer.value.slots, caps: 
layer.value.caps, metrics: layer.value.metrics, overview: layer.value.overview 
}).landing;
 });
-const landing = useLayerLanding(safeLayer, safeCfg);
+// Global time-range — picker change refires the landing rollup
+// AND the widget batch via queryKey. Each downstream control
+// listens to its upstream picker the same way it listens to
+// service/instance/endpoint changes.
+const timeRange = useTimeRangeStore();
+const rangeRef = computed(() => {
+  const r = timeRange.range;
+  return { step: timeRange.step, startMs: r.startMs, endMs: r.endMs };
+});
+const landing = useLayerLanding(safeLayer, safeCfg, rangeRef);
 const serviceName = computed<string | null>(() => {
   const rows = landing.data.value?.sampledRows ?? landing.rows.value ?? [];
   const match = rows.find((r) => r.serviceId === selectedId.value);
@@ -235,6 +245,7 @@ const { data, isFetching, error } = useLayerDashboard(
   scope,
   mockTop,
   { instance: selectedInstance, endpoint: selectedEndpoint },
+  rangeRef,
 );
 
 // Sequential page-init events for the EventTicker — config →
diff --git a/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts 
b/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
index 0115d71..46620a4 100644
--- a/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
+++ b/apps/ui/src/render/layer-dashboard/useLayerDashboard.ts
@@ -76,6 +76,12 @@ export interface DashboardEntityRefs {
   endpoint?: Ref<string | null>;
 }
 
+export interface DashboardRange {
+  step: 'MINUTE' | 'HOUR' | 'DAY';
+  startMs: number;
+  endMs: number;
+}
+
 export function useLayerDashboard(
   layerKey: Ref<string>,
   service: Ref<string | null>,
@@ -88,6 +94,11 @@ export function useLayerDashboard(
    *  ref keeps the query key cache-aware so switching instances on
    *  the same service re-fetches. */
   entityRefs: DashboardEntityRefs = {},
+  /** Global time-range ref (start/end ms + step). When omitted the
+   *  BFF picks a default window. Threaded into the queryKey so a
+   *  time-picker change refires the widget batch the same way a
+   *  service/instance pick does. */
+  range?: Ref<DashboardRange | null>,
 ) {
   // Auto-refresh is metrics-only. Trace / log / profiling pages are
   // explore-style (operator-driven queries, log tails, etc.) and would
@@ -99,6 +110,17 @@ export function useLayerDashboard(
     const s = scope?.value ?? 'service';
     return METRIC_SCOPES.has(s) ? 30_000 : false;
   });
+  const rangeRef = range ?? computed<DashboardRange | null>(() => null);
+  // Bucket the range into a stable key fragment — millisecond
+  // precision in the key would invalidate the cache on every paint
+  // for rolling presets. Step + 60s buckets are precise enough for
+  // dashboard refreshes and keep the cache useful across closely-
+  // spaced operator interactions.
+  const rangeKey = computed(() => {
+    const r = rangeRef.value;
+    if (!r) return null;
+    return `${r.step}:${Math.floor(r.startMs / 60_000)}:${Math.floor(r.endMs / 
60_000)}`;
+  });
   const q = useQuery({
     queryKey: [
       'dashboard',
@@ -108,6 +130,7 @@ export function useLayerDashboard(
       mockTop ?? computed(() => 0),
       entityRefs.instance ?? computed(() => null),
       entityRefs.endpoint ?? computed(() => null),
+      rangeKey,
     ],
     queryFn: () =>
       bffClient.layer.dashboard(
@@ -117,6 +140,13 @@ export function useLayerDashboard(
           ...(scope?.value ? { scope: scope.value } : {}),
           ...(entityRefs.instance?.value ? { serviceInstance: 
entityRefs.instance.value } : {}),
           ...(entityRefs.endpoint?.value ? { endpoint: 
entityRefs.endpoint.value } : {}),
+          ...(rangeRef.value
+            ? {
+                step: rangeRef.value.step,
+                startMs: rangeRef.value.startMs,
+                endMs: rangeRef.value.endMs,
+              }
+            : {}),
         },
         mockTop?.value ? { mockTop: mockTop.value } : {},
       ),

Reply via email to