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 } : {},
),