@kopai/ui 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +23 -0
  2. package/dist/index.cjs +1591 -231
  3. package/dist/index.d.cts +559 -2
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +559 -2
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +1590 -207
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +12 -11
  10. package/src/components/observability/DynamicDashboard/DynamicDashboard.test.tsx +239 -0
  11. package/src/components/observability/DynamicDashboard/index.tsx +64 -0
  12. package/src/components/observability/MetricHistogram/MetricHistogram.stories.tsx +10 -1
  13. package/src/components/observability/MetricHistogram/index.tsx +85 -19
  14. package/src/components/observability/MetricStat/MetricStat.stories.tsx +2 -1
  15. package/src/components/observability/MetricTimeSeries/MetricTimeSeries.stories.tsx +23 -1
  16. package/src/components/observability/MetricTimeSeries/index.tsx +70 -27
  17. package/src/components/observability/__fixtures__/metrics.ts +97 -0
  18. package/src/components/observability/index.ts +3 -0
  19. package/src/components/observability/renderers/OtelLogTimeline.tsx +28 -0
  20. package/src/components/observability/renderers/OtelMetricHistogram.tsx +2 -0
  21. package/src/components/observability/renderers/OtelMetricStat.tsx +1 -13
  22. package/src/components/observability/renderers/OtelMetricTimeSeries.tsx +2 -0
  23. package/src/components/observability/renderers/OtelTraceDetail.tsx +35 -0
  24. package/src/components/observability/renderers/index.ts +2 -0
  25. package/src/components/observability/utils/attributes.ts +7 -0
  26. package/src/components/observability/utils/units.test.ts +116 -0
  27. package/src/components/observability/utils/units.ts +132 -0
  28. package/src/index.ts +1 -0
  29. package/src/lib/__snapshots__/generate-prompt-instructions.test.ts.snap +7 -1
  30. package/src/lib/generate-prompt-instructions.test.ts +1 -1
  31. package/src/lib/generate-prompt-instructions.ts +18 -6
  32. package/src/lib/observability-catalog.ts +7 -1
  33. package/src/lib/renderer.tsx +1 -1
  34. package/src/pages/observability.test.tsx +129 -0
  35. package/src/pages/observability.tsx +60 -34
  36. package/src/lib/dashboard-datasource.ts +0 -76
@@ -0,0 +1,132 @@
1
+ export interface ResolvedScale {
2
+ divisor: number;
3
+ suffix: string;
4
+ label: string;
5
+ isPercent: boolean;
6
+ }
7
+
8
+ interface ScaleEntry {
9
+ threshold: number;
10
+ divisor: number;
11
+ suffix: string;
12
+ }
13
+
14
+ const BYTE_SCALES: ScaleEntry[] = [
15
+ { threshold: 1e12, divisor: 1e12, suffix: "TB" },
16
+ { threshold: 1e9, divisor: 1e9, suffix: "GB" },
17
+ { threshold: 1e6, divisor: 1e6, suffix: "MB" },
18
+ { threshold: 1e3, divisor: 1e3, suffix: "KB" },
19
+ { threshold: 0, divisor: 1, suffix: "B" },
20
+ ];
21
+
22
+ const SECOND_SCALES: ScaleEntry[] = [
23
+ { threshold: 3600, divisor: 3600, suffix: "h" },
24
+ { threshold: 60, divisor: 60, suffix: "min" },
25
+ { threshold: 1, divisor: 1, suffix: "s" },
26
+ { threshold: 0, divisor: 0.001, suffix: "ms" },
27
+ ];
28
+
29
+ const MS_SCALES: ScaleEntry[] = [
30
+ { threshold: 1000, divisor: 1000, suffix: "s" },
31
+ { threshold: 0, divisor: 1, suffix: "ms" },
32
+ ];
33
+
34
+ const US_SCALES: ScaleEntry[] = [
35
+ { threshold: 1e6, divisor: 1e6, suffix: "s" },
36
+ { threshold: 1000, divisor: 1000, suffix: "ms" },
37
+ { threshold: 0, divisor: 1, suffix: "\u03BCs" },
38
+ ];
39
+
40
+ const GENERIC_SCALES: ScaleEntry[] = [
41
+ { threshold: 1e9, divisor: 1e9, suffix: "B" },
42
+ { threshold: 1e6, divisor: 1e6, suffix: "M" },
43
+ { threshold: 1e3, divisor: 1e3, suffix: "K" },
44
+ { threshold: 0, divisor: 1, suffix: "" },
45
+ ];
46
+
47
+ const BRACE_UNIT_PATTERN = /^\{(.+)\}$/;
48
+
49
+ const UNIT_SCALE_MAP: Record<string, ScaleEntry[]> = {
50
+ By: BYTE_SCALES,
51
+ s: SECOND_SCALES,
52
+ ms: MS_SCALES,
53
+ us: US_SCALES,
54
+ };
55
+
56
+ function pickScale(scales: ScaleEntry[], maxValue: number): ScaleEntry {
57
+ const abs = Math.abs(maxValue);
58
+ for (const s of scales) {
59
+ if (abs >= s.threshold && s.threshold > 0) return s;
60
+ }
61
+ return scales[scales.length - 1]!;
62
+ }
63
+
64
+ export function resolveUnitScale(
65
+ unit: string | null | undefined,
66
+ maxValue: number
67
+ ): ResolvedScale {
68
+ if (!unit) {
69
+ const s = pickScale(GENERIC_SCALES, maxValue);
70
+ return {
71
+ divisor: s.divisor,
72
+ suffix: s.suffix,
73
+ label: "",
74
+ isPercent: false,
75
+ };
76
+ }
77
+
78
+ // Dimensionless ratio → percent
79
+ if (unit === "1") {
80
+ return { divisor: 0.01, suffix: "%", label: "Percent", isPercent: true };
81
+ }
82
+
83
+ // Known unit families
84
+ const scales = UNIT_SCALE_MAP[unit];
85
+ if (scales) {
86
+ const s = pickScale(scales, maxValue);
87
+ return {
88
+ divisor: s.divisor,
89
+ suffix: s.suffix,
90
+ label: s.suffix,
91
+ isPercent: false,
92
+ };
93
+ }
94
+
95
+ // Curly-brace units like {requests}
96
+ const braceMatch = BRACE_UNIT_PATTERN.exec(unit);
97
+ if (braceMatch) {
98
+ const cleaned = braceMatch[1]!;
99
+ const s = pickScale(GENERIC_SCALES, maxValue);
100
+ const suffix = s.suffix ? `${s.suffix} ${cleaned}` : cleaned;
101
+ return { divisor: s.divisor, suffix, label: cleaned, isPercent: false };
102
+ }
103
+
104
+ // Unknown unit — generic scaling + append unit
105
+ const s = pickScale(GENERIC_SCALES, maxValue);
106
+ const suffix = s.suffix ? `${s.suffix} ${unit}` : unit;
107
+ return { divisor: s.divisor, suffix, label: unit, isPercent: false };
108
+ }
109
+
110
+ export function formatTickValue(value: number, scale: ResolvedScale): string {
111
+ const scaled = value / scale.divisor;
112
+ if (scale.isPercent) return `${scaled.toFixed(1)}`;
113
+ if (Number.isInteger(scaled) && Math.abs(scaled) < 1e4)
114
+ return scaled.toString();
115
+ return scaled.toFixed(1);
116
+ }
117
+
118
+ export function formatDisplayValue(
119
+ value: number,
120
+ scale: ResolvedScale
121
+ ): string {
122
+ const tick = formatTickValue(value, scale);
123
+ if (!scale.suffix) return tick;
124
+ if (scale.isPercent) return `${tick}${scale.suffix}`;
125
+ return `${tick} ${scale.suffix}`;
126
+ }
127
+
128
+ /** Convenience: resolve + format in one call (for MetricStat) */
129
+ export function formatOtelValue(value: number, unit: string): string {
130
+ const scale = resolveUnitScale(unit, Math.abs(value));
131
+ return formatDisplayValue(value, scale);
132
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as ObservabilityPage } from "./pages/observability.js";
2
2
  export { createCatalog } from "./lib/component-catalog.js";
3
+ export { observabilityCatalog } from "./lib/observability-catalog.js";
3
4
  export { generatePromptInstructions } from "./lib/generate-prompt-instructions.js";
4
5
  export {
5
6
  Renderer,
@@ -1,7 +1,13 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
3
  exports[`generatePromptInstructions > generates full prompt instructions 1`] = `
4
- "## Available Components
4
+ "## UI Tree Version
5
+
6
+ Use uiTreeVersion: "0.5.0" when creating dashboards.
7
+
8
+ ---
9
+
10
+ ## Available Components
5
11
 
6
12
  ### Card
7
13
  A card container
@@ -21,7 +21,7 @@ describe("generatePromptInstructions", () => {
21
21
  },
22
22
  });
23
23
 
24
- const prompt = generatePromptInstructions(catalog);
24
+ const prompt = generatePromptInstructions(catalog, "0.5.0");
25
25
  expect(prompt).toMatchSnapshot();
26
26
  });
27
27
  });
@@ -74,16 +74,19 @@ function buildExampleElements(
74
74
  for (const name of otherNames) {
75
75
  const key = `${String(name).toLowerCase()}-1`;
76
76
  childKeys.push(key);
77
- elements[key] = {
77
+ const element: Record<string, unknown> = {
78
78
  key,
79
79
  type: String(name),
80
80
  props: {},
81
81
  parentKey: containerKey,
82
- dataSource: {
82
+ };
83
+ if (!components[name]?.hasChildren) {
84
+ element.dataSource = {
83
85
  method: "searchTracesPage",
84
86
  params: { limit: 10 },
85
- },
86
- };
87
+ };
88
+ }
89
+ elements[key] = element;
87
90
  }
88
91
 
89
92
  return { root: containerKey, elements };
@@ -140,7 +143,10 @@ function buildUnifiedSchema(treeSchema: z.ZodTypeAny): object {
140
143
  * const prompt = `Build a dashboard UI.\n\n${instructions}`;
141
144
  * ```
142
145
  */
143
- export function generatePromptInstructions(catalog: Catalog): string {
146
+ export function generatePromptInstructions(
147
+ catalog: Catalog,
148
+ uiTreeVersion: string
149
+ ): string {
144
150
  const componentNames = Object.keys(catalog.components);
145
151
 
146
152
  const componentSections = componentNames
@@ -167,7 +173,13 @@ ${roleLine}`;
167
173
  catalog.components
168
174
  );
169
175
 
170
- return `## Available Components
176
+ return `## UI Tree Version
177
+
178
+ Use uiTreeVersion: "${uiTreeVersion}" when creating dashboards.
179
+
180
+ ---
181
+
182
+ ## Available Components
171
183
 
172
184
  ${componentSections}
173
185
 
@@ -106,13 +106,19 @@ export const observabilityCatalog = createCatalog({
106
106
  props: z.object({
107
107
  height: z.number().nullable(),
108
108
  showBrush: z.boolean().nullable(),
109
+ yAxisLabel: z.string().nullable(),
110
+ unit: z.string().nullable(),
109
111
  }),
110
112
  hasChildren: false,
111
113
  description: "Time series line chart for Gauge/Sum metrics",
112
114
  },
113
115
 
114
116
  MetricHistogram: {
115
- props: z.object({ height: z.number().nullable() }),
117
+ props: z.object({
118
+ height: z.number().nullable(),
119
+ yAxisLabel: z.string().nullable(),
120
+ unit: z.string().nullable(),
121
+ }),
116
122
  hasChildren: false,
117
123
  description: "Histogram bar chart for distribution metrics",
118
124
  },
@@ -24,7 +24,7 @@ type RegistryFromCatalog<
24
24
 
25
25
  type Catalog = ReturnType<typeof createCatalog>;
26
26
 
27
- type UITree = z.infer<Catalog["uiTreeSchema"]>;
27
+ export type UITree = z.infer<Catalog["uiTreeSchema"]>;
28
28
 
29
29
  type UIElement = UITree["elements"][string];
30
30
 
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
5
+ import { createElement } from "react";
6
+ import { render, screen, waitFor } from "@testing-library/react";
7
+ import ObservabilityPage from "./observability.js";
8
+ import { queryClient } from "../providers/kopai-provider.js";
9
+ import type { KopaiClient } from "@kopai/sdk";
10
+
11
+ type MockClient = {
12
+ [K in keyof KopaiClient]: ReturnType<typeof vi.fn>;
13
+ };
14
+
15
+ function createMockClient(): MockClient {
16
+ return {
17
+ searchTracesPage: vi.fn().mockResolvedValue({ data: [] }),
18
+ searchLogsPage: vi.fn().mockResolvedValue({ data: [] }),
19
+ searchMetricsPage: vi.fn().mockResolvedValue({ data: [] }),
20
+ getTrace: vi.fn().mockResolvedValue({ data: [] }),
21
+ discoverMetrics: vi.fn().mockResolvedValue({ data: [] }),
22
+ searchTraces: vi.fn().mockResolvedValue({ data: [] }),
23
+ searchLogs: vi.fn().mockResolvedValue({ data: [] }),
24
+ searchMetrics: vi.fn().mockResolvedValue({ data: [] }),
25
+ createDashboard: vi.fn().mockResolvedValue({}),
26
+ getDashboard: vi.fn().mockResolvedValue({}),
27
+ searchDashboardsPage: vi
28
+ .fn()
29
+ .mockResolvedValue({ data: [], nextCursor: null }),
30
+ searchDashboards: vi.fn().mockReturnValue((async function* () {})()),
31
+ };
32
+ }
33
+
34
+ const VALID_TREE = {
35
+ root: "root",
36
+ elements: {
37
+ root: {
38
+ key: "root",
39
+ type: "Stack",
40
+ children: ["heading"],
41
+ parentKey: "",
42
+ props: { direction: "vertical", gap: "md", align: null },
43
+ },
44
+ heading: {
45
+ key: "heading",
46
+ type: "Heading",
47
+ children: [],
48
+ parentKey: "root",
49
+ props: { text: "Test Dashboard", level: "h2" },
50
+ },
51
+ },
52
+ };
53
+
54
+ describe("useDashboardTree validation", () => {
55
+ let mockClient: MockClient;
56
+ let originalLocation: string;
57
+
58
+ beforeEach(() => {
59
+ mockClient = createMockClient();
60
+ queryClient.clear();
61
+ vi.clearAllMocks();
62
+ originalLocation = window.location.search;
63
+ });
64
+
65
+ afterEach(() => {
66
+ vi.restoreAllMocks();
67
+ window.history.replaceState(
68
+ null,
69
+ "",
70
+ window.location.pathname + originalLocation
71
+ );
72
+ });
73
+
74
+ function setURL(params: string) {
75
+ window.history.replaceState(null, "", window.location.pathname + params);
76
+ window.dispatchEvent(new PopStateEvent("popstate"));
77
+ }
78
+
79
+ it("renders DynamicDashboard when API returns a valid uiTree", async () => {
80
+ vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
81
+ new Response(JSON.stringify({ uiTree: VALID_TREE }), {
82
+ status: 200,
83
+ headers: { "Content-Type": "application/json" },
84
+ })
85
+ );
86
+
87
+ setURL("?tab=metrics&dashboardId=abc");
88
+
89
+ render(
90
+ createElement(ObservabilityPage, {
91
+ client: mockClient as unknown as KopaiClient,
92
+ })
93
+ );
94
+
95
+ await waitFor(() => {
96
+ expect(screen.getByText("Test Dashboard")).toBeTruthy();
97
+ });
98
+
99
+ expect(screen.queryByText(/invalid layout/i)).toBeNull();
100
+ });
101
+
102
+ it("shows error when API returns an invalid uiTree", async () => {
103
+ const invalidTree = {
104
+ root: "x",
105
+ elements: {
106
+ x: { type: "Bogus", key: "x", children: [], parentKey: "" },
107
+ },
108
+ };
109
+
110
+ vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
111
+ new Response(JSON.stringify({ uiTree: invalidTree }), {
112
+ status: 200,
113
+ headers: { "Content-Type": "application/json" },
114
+ })
115
+ );
116
+
117
+ setURL("?tab=metrics&dashboardId=def");
118
+
119
+ render(
120
+ createElement(ObservabilityPage, {
121
+ client: mockClient as unknown as KopaiClient,
122
+ })
123
+ );
124
+
125
+ await waitFor(() => {
126
+ expect(screen.getByText(/invalid layout/i)).toBeTruthy();
127
+ });
128
+ });
129
+ });
@@ -7,14 +7,13 @@ import {
7
7
  useRef,
8
8
  } from "react";
9
9
  import { KopaiSDKProvider, useKopaiSDK } from "../providers/kopai-provider.js";
10
+ import { useQuery } from "@tanstack/react-query";
10
11
  import { KopaiClient } from "@kopai/sdk";
11
12
  import { useKopaiData } from "../hooks/use-kopai-data.js";
12
13
  import { useLiveLogs } from "../hooks/use-live-logs.js";
13
14
  import type { denormalizedSignals, dataFilterSchemas } from "@kopai/core";
14
15
  import type { DataSource } from "../lib/component-catalog.js";
15
16
  import { observabilityCatalog } from "../lib/observability-catalog.js";
16
- import { createRendererFromCatalog } from "../lib/renderer.js";
17
-
18
17
  // Observability components
19
18
  import {
20
19
  LogTimeline,
@@ -25,24 +24,15 @@ import {
25
24
  TraceDetail,
26
25
  KeyboardShortcutsProvider,
27
26
  useRegisterShortcuts,
27
+ DynamicDashboard,
28
28
  } from "../components/observability/index.js";
29
+ import type { UITree } from "../components/observability/DynamicDashboard/index.js";
29
30
  import type {
30
31
  TraceSummary,
31
32
  TraceSearchFilters,
32
33
  } from "../components/observability/index.js";
33
34
 
34
35
  import { SERVICES_SHORTCUTS } from "../components/observability/ServiceList/shortcuts.js";
35
- import { OtelMetricDiscovery } from "../components/observability/renderers/index.js";
36
- import {
37
- Heading,
38
- Text,
39
- Card,
40
- Stack,
41
- Grid,
42
- Badge,
43
- Divider,
44
- Empty,
45
- } from "../components/dashboard/index.js";
46
36
 
47
37
  type OtelTracesRow = denormalizedSignals.OtelTracesRow;
48
38
 
@@ -67,6 +57,7 @@ interface URLState {
67
57
  service: string | null;
68
58
  trace: string | null;
69
59
  span: string | null;
60
+ dashboardId: string | null;
70
61
  }
71
62
 
72
63
  function readURLState(): URLState {
@@ -74,13 +65,14 @@ function readURLState(): URLState {
74
65
  const service = params.get("service");
75
66
  const trace = params.get("trace");
76
67
  const span = params.get("span");
68
+ const dashboardId = params.get("dashboardId");
77
69
  const rawTab = params.get("tab");
78
70
  const tab = service
79
71
  ? "services"
80
72
  : rawTab === "logs" || rawTab === "metrics"
81
73
  ? rawTab
82
74
  : "services";
83
- return { tab, service, trace, span };
75
+ return { tab, service, trace, span, dashboardId };
84
76
  }
85
77
 
86
78
  function pushURLState(
@@ -89,6 +81,7 @@ function pushURLState(
89
81
  service?: string | null;
90
82
  trace?: string | null;
91
83
  span?: string | null;
84
+ dashboardId?: string | null;
92
85
  },
93
86
  { replace = false }: { replace?: boolean } = {}
94
87
  ) {
@@ -97,6 +90,12 @@ function pushURLState(
97
90
  if (state.service) params.set("service", state.service);
98
91
  if (state.trace) params.set("trace", state.trace);
99
92
  if (state.span) params.set("span", state.span);
93
+ // Preserve dashboardId from current URL if not explicitly provided
94
+ const dashboardId =
95
+ state.dashboardId !== undefined
96
+ ? state.dashboardId
97
+ : new URLSearchParams(window.location.search).get("dashboardId");
98
+ if (dashboardId) params.set("dashboardId", dashboardId);
100
99
  const qs = params.toString();
101
100
  const url = `${window.location.pathname}${qs ? `?${qs}` : ""}`;
102
101
  if (replace) {
@@ -118,6 +117,7 @@ let _cachedState: URLState = {
118
117
  service: null,
119
118
  trace: null,
120
119
  span: null,
120
+ dashboardId: null,
121
121
  };
122
122
 
123
123
  function getURLSnapshot(): URLState {
@@ -660,26 +660,10 @@ function ServicesTab({
660
660
  }
661
661
 
662
662
  // ---------------------------------------------------------------------------
663
- // Metrics tab — renderer + catalog
663
+ // Metrics tab — DynamicDashboard
664
664
  // ---------------------------------------------------------------------------
665
665
 
666
- const MetricsRenderer = createRendererFromCatalog(observabilityCatalog, {
667
- Card,
668
- Grid,
669
- Stack,
670
- Heading,
671
- Text,
672
- Badge,
673
- Divider,
674
- Empty,
675
- LogTimeline: () => null,
676
- TraceDetail: () => null,
677
- MetricTimeSeries: () => null,
678
- MetricHistogram: () => null,
679
- MetricStat: () => null,
680
- MetricTable: () => null,
681
- MetricDiscovery: OtelMetricDiscovery,
682
- });
666
+ const DASHBOARDS_API_BASE = "/dashboards";
683
667
 
684
668
  const METRICS_TREE = {
685
669
  root: "root",
@@ -731,8 +715,50 @@ const METRICS_TREE = {
731
715
  },
732
716
  };
733
717
 
718
+ function useDashboardTree(dashboardId: string | null) {
719
+ const { data, isFetching, error } = useQuery<UITree, Error>({
720
+ queryKey: ["dashboard-tree", dashboardId],
721
+ queryFn: async ({ signal }) => {
722
+ const res = await fetch(`${DASHBOARDS_API_BASE}/${dashboardId}`, {
723
+ signal,
724
+ });
725
+ if (!res.ok) throw new Error(`Failed to load dashboard: ${res.status}`);
726
+ const json = await res.json();
727
+ const parsed = observabilityCatalog.uiTreeSchema.safeParse(json.uiTree);
728
+ if (!parsed.success) {
729
+ const issue = parsed.error.issues[0];
730
+ const path = issue?.path.length ? issue.path.join(".") + ": " : "";
731
+ throw new Error(
732
+ `Dashboard has an invalid layout: ${path}${issue?.message}`
733
+ );
734
+ }
735
+ return parsed.data;
736
+ },
737
+ enabled: !!dashboardId,
738
+ });
739
+
740
+ return {
741
+ loading: isFetching,
742
+ error: error?.message ?? null,
743
+ tree: data ?? null,
744
+ };
745
+ }
746
+
734
747
  function MetricsTab() {
735
- return <MetricsRenderer tree={METRICS_TREE} />;
748
+ const kopaiClient = useKopaiSDK();
749
+ const { dashboardId } = useURLState();
750
+ const { loading, error, tree } = useDashboardTree(dashboardId);
751
+
752
+ if (loading)
753
+ return (
754
+ <p className="text-muted-foreground text-sm">Loading dashboard...</p>
755
+ );
756
+ if (error)
757
+ return <p className="text-muted-foreground text-sm">Error: {error}</p>;
758
+
759
+ return (
760
+ <DynamicDashboard kopaiClient={kopaiClient} uiTree={tree ?? METRICS_TREE} />
761
+ );
736
762
  }
737
763
 
738
764
  // ---------------------------------------------------------------------------
@@ -741,7 +767,7 @@ function MetricsTab() {
741
767
 
742
768
  let _defaultClient: KopaiClient | undefined;
743
769
  function getDefaultClient() {
744
- _defaultClient ??= new KopaiClient({ baseUrl: "/signals" });
770
+ _defaultClient ??= new KopaiClient({ baseUrl: "" });
745
771
  return _defaultClient;
746
772
  }
747
773
 
@@ -1,76 +0,0 @@
1
- import { observabilityCatalog } from "./observability-catalog.js";
2
- import { z } from "zod";
3
-
4
- type UiTree = z.infer<typeof observabilityCatalog.uiTreeSchema>;
5
-
6
- export type DashboardId = string;
7
- export type UiTreeId = string;
8
- export type OwnerId = string;
9
-
10
- export interface DashboardMeta {
11
- dashboardId: DashboardId;
12
- uiTreeId: UiTreeId;
13
- name: string;
14
- ownerId: OwnerId;
15
- pinned: boolean;
16
- createdAt: string;
17
- updatedAt: string;
18
- }
19
-
20
- export interface VersionMeta {
21
- uiTreeId: UiTreeId;
22
- createdAt: string;
23
- }
24
-
25
- export interface DashboardWriteDatasource {
26
- createDashboard(
27
- uiTree: UiTree,
28
- name: string,
29
- ownerId: OwnerId
30
- ): Promise<DashboardMeta>;
31
-
32
- // uiTrees are immutable, creates new one with ref to previous
33
- updateDashboard(
34
- dashboardId: DashboardId,
35
- uiTree: UiTree,
36
- pinned?: boolean
37
- ): Promise<DashboardMeta>;
38
-
39
- deleteDashboard(dashboardId: DashboardId): Promise<void>;
40
-
41
- // Creates new uiTree copying content from specified version
42
- restoreVersion(
43
- dashboardId: DashboardId,
44
- uiTreeId: UiTreeId
45
- ): Promise<DashboardMeta>;
46
- }
47
-
48
- export interface DashboardReadDatasource {
49
- searchDashboards(params: {
50
- limit: number;
51
- cursor?: string;
52
- pinned?: boolean;
53
- ownerId?: OwnerId;
54
- }): Promise<{
55
- dashboards: (DashboardMeta & { uiTree: UiTree })[];
56
- nextCursor?: string;
57
- }>;
58
-
59
- getDashboard(
60
- dashboardId: DashboardId
61
- ): Promise<DashboardMeta & { uiTree: UiTree }>;
62
-
63
- listVersions(params: {
64
- dashboardId: DashboardId;
65
- limit: number;
66
- cursor?: string;
67
- }): Promise<{
68
- versions: VersionMeta[];
69
- nextCursor?: string;
70
- }>;
71
-
72
- getDashboardAtVersion(uiTreeId: UiTreeId): Promise<{
73
- uiTree: UiTree;
74
- createdAt: string;
75
- }>;
76
- }