@kopai/ui 0.6.0 → 0.8.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kopai/ui",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Vladimir Adamic",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "@tanstack/react-virtual": "^3.13.19",
35
35
  "recharts": "^3.7.0",
36
36
  "@kopai/core": "0.7.0",
37
- "@kopai/sdk": "0.4.0"
37
+ "@kopai/sdk": "0.5.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "^19.2.4",
@@ -23,6 +23,11 @@ function createMockClient(): MockClient {
23
23
  searchLogs: vi.fn().mockResolvedValue({ data: [] }),
24
24
  searchMetrics: vi.fn().mockResolvedValue({ data: [] }),
25
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* () {})()),
26
31
  };
27
32
  }
28
33
 
@@ -96,12 +96,26 @@ function buildMetrics(rows: OtelMetricsRow[]): ParsedMetricGroup[] {
96
96
  for (const row of rows) {
97
97
  const name = row.MetricName ?? "unknown";
98
98
  const type = row.MetricType;
99
- if (
99
+
100
+ // Extract scalar value depending on metric type
101
+ let value: number | undefined;
102
+ if (type === "Gauge" || type === "Sum") {
103
+ value = "Value" in row ? row.Value : undefined;
104
+ } else if (
100
105
  type === "Histogram" ||
101
106
  type === "ExponentialHistogram" ||
102
107
  type === "Summary"
103
- )
104
- continue; // TimeSeries only handles Gauge/Sum
108
+ ) {
109
+ // Use mean (Sum/Count) for distribution metrics
110
+ const sum = "Sum" in row ? (row as { Sum?: number }).Sum : undefined;
111
+ const count =
112
+ "Count" in row ? (row as { Count?: number }).Count : undefined;
113
+ if (sum != null && count != null && count > 0) {
114
+ value = sum / count;
115
+ }
116
+ }
117
+
118
+ if (value === undefined) continue;
105
119
 
106
120
  if (!metricMap.has(name)) metricMap.set(name, new Map());
107
121
  if (!metricMeta.has(name))
@@ -136,8 +150,6 @@ function buildMetrics(rows: OtelMetricsRow[]): ParsedMetricGroup[] {
136
150
  });
137
151
  }
138
152
 
139
- if (!("Value" in row)) continue;
140
- const value = row.Value;
141
153
  const timestamp = parseInt(row.TimeUnix, 10) / 1e6;
142
154
  seriesMap.get(seriesKey)!.dataPoints.push({ timestamp, value });
143
155
  }
@@ -18,11 +18,15 @@ export function OtelLogTimeline(props: Props) {
18
18
 
19
19
  const response = props.data as { data?: OtelLogsRow[] } | null;
20
20
 
21
+ const height = props.element.props.height ?? 600;
22
+
21
23
  return (
22
- <LogTimeline
23
- rows={response?.data ?? []}
24
- isLoading={props.loading}
25
- error={props.error ?? undefined}
26
- />
24
+ <div style={{ height }} className="flex flex-col min-h-0">
25
+ <LogTimeline
26
+ rows={response?.data ?? []}
27
+ isLoading={props.loading}
28
+ error={props.error ?? undefined}
29
+ />
30
+ </div>
27
31
  );
28
32
  }
@@ -18,6 +18,7 @@ const createMockClient = () => ({
18
18
  searchMetricsPage: vi.fn(),
19
19
  getTrace: vi.fn(),
20
20
  discoverMetrics: vi.fn(),
21
+ getDashboard: vi.fn(),
21
22
  });
22
23
 
23
24
  type MockClient = ReturnType<typeof createMockClient>;
@@ -21,6 +21,7 @@ const createMockClient = () => ({
21
21
  searchMetricsPage: vi.fn(),
22
22
  getTrace: vi.fn(),
23
23
  discoverMetrics: vi.fn(),
24
+ getDashboard: vi.fn(),
24
25
  });
25
26
 
26
27
  function wrapper(client: KopaiClient) {
@@ -23,6 +23,11 @@ function createMockClient(): MockClient {
23
23
  searchLogs: vi.fn().mockResolvedValue({ data: [] }),
24
24
  searchMetrics: vi.fn().mockResolvedValue({ data: [] }),
25
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* () {})()),
26
31
  };
27
32
  }
28
33
 
@@ -72,12 +77,7 @@ describe("useDashboardTree validation", () => {
72
77
  }
73
78
 
74
79
  it("renders DynamicDashboard when API returns a valid uiTree", async () => {
75
- vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
76
- new Response(JSON.stringify({ uiTree: VALID_TREE }), {
77
- status: 200,
78
- headers: { "Content-Type": "application/json" },
79
- })
80
- );
80
+ mockClient.getDashboard.mockResolvedValueOnce({ uiTree: VALID_TREE });
81
81
 
82
82
  setURL("?tab=metrics&dashboardId=abc");
83
83
 
@@ -91,6 +91,10 @@ describe("useDashboardTree validation", () => {
91
91
  expect(screen.getByText("Test Dashboard")).toBeTruthy();
92
92
  });
93
93
 
94
+ expect(mockClient.getDashboard).toHaveBeenCalledWith(
95
+ "abc",
96
+ expect.anything()
97
+ );
94
98
  expect(screen.queryByText(/invalid layout/i)).toBeNull();
95
99
  });
96
100
 
@@ -102,12 +106,7 @@ describe("useDashboardTree validation", () => {
102
106
  },
103
107
  };
104
108
 
105
- vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
106
- new Response(JSON.stringify({ uiTree: invalidTree }), {
107
- status: 200,
108
- headers: { "Content-Type": "application/json" },
109
- })
110
- );
109
+ mockClient.getDashboard.mockResolvedValueOnce({ uiTree: invalidTree });
111
110
 
112
111
  setURL("?tab=metrics&dashboardId=def");
113
112
 
@@ -120,5 +119,10 @@ describe("useDashboardTree validation", () => {
120
119
  await waitFor(() => {
121
120
  expect(screen.getByText(/invalid layout/i)).toBeTruthy();
122
121
  });
122
+
123
+ expect(mockClient.getDashboard).toHaveBeenCalledWith(
124
+ "def",
125
+ expect.anything()
126
+ );
123
127
  });
124
128
  });
@@ -663,8 +663,6 @@ function ServicesTab({
663
663
  // Metrics tab — DynamicDashboard
664
664
  // ---------------------------------------------------------------------------
665
665
 
666
- const DASHBOARDS_API_BASE = "/dashboards";
667
-
668
666
  const METRICS_TREE = {
669
667
  root: "root",
670
668
  elements: {
@@ -715,16 +713,17 @@ const METRICS_TREE = {
715
713
  },
716
714
  };
717
715
 
718
- function useDashboardTree(dashboardId: string | null) {
716
+ function useDashboardTree(
717
+ client: Pick<KopaiClient, "getDashboard">,
718
+ dashboardId: string | null
719
+ ) {
719
720
  const { data, isFetching, error } = useQuery<UITree, Error>({
720
721
  queryKey: ["dashboard-tree", dashboardId],
721
722
  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);
723
+ const dashboard = await client.getDashboard(dashboardId!, { signal });
724
+ const parsed = observabilityCatalog.uiTreeSchema.safeParse(
725
+ dashboard.uiTree
726
+ );
728
727
  if (!parsed.success) {
729
728
  const issue = parsed.error.issues[0];
730
729
  const path = issue?.path.length ? issue.path.join(".") + ": " : "";
@@ -747,7 +746,7 @@ function useDashboardTree(dashboardId: string | null) {
747
746
  function MetricsTab() {
748
747
  const kopaiClient = useKopaiSDK();
749
748
  const { dashboardId } = useURLState();
750
- const { loading, error, tree } = useDashboardTree(dashboardId);
749
+ const { loading, error, tree } = useDashboardTree(kopaiClient, dashboardId);
751
750
 
752
751
  if (loading)
753
752
  return (
@@ -9,6 +9,7 @@ export type KopaiClient = Pick<
9
9
  | "searchMetricsPage"
10
10
  | "getTrace"
11
11
  | "discoverMetrics"
12
+ | "getDashboard"
12
13
  >;
13
14
 
14
15
  interface KopaiSDKContextValue {