@kopai/ui 0.7.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.7.0",
3
+ "version": "0.8.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Vladimir Adamic",
6
6
  "repository": {
@@ -33,8 +33,8 @@
33
33
  "@tanstack/react-query": "^5",
34
34
  "@tanstack/react-virtual": "^3.13.19",
35
35
  "recharts": "^3.7.0",
36
- "@kopai/sdk": "0.5.0",
37
- "@kopai/core": "0.7.0"
36
+ "@kopai/core": "0.7.0",
37
+ "@kopai/sdk": "0.5.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "^19.2.4",
@@ -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) {
@@ -77,12 +77,7 @@ describe("useDashboardTree validation", () => {
77
77
  }
78
78
 
79
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
- );
80
+ mockClient.getDashboard.mockResolvedValueOnce({ uiTree: VALID_TREE });
86
81
 
87
82
  setURL("?tab=metrics&dashboardId=abc");
88
83
 
@@ -96,6 +91,10 @@ describe("useDashboardTree validation", () => {
96
91
  expect(screen.getByText("Test Dashboard")).toBeTruthy();
97
92
  });
98
93
 
94
+ expect(mockClient.getDashboard).toHaveBeenCalledWith(
95
+ "abc",
96
+ expect.anything()
97
+ );
99
98
  expect(screen.queryByText(/invalid layout/i)).toBeNull();
100
99
  });
101
100
 
@@ -107,12 +106,7 @@ describe("useDashboardTree validation", () => {
107
106
  },
108
107
  };
109
108
 
110
- vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(
111
- new Response(JSON.stringify({ uiTree: invalidTree }), {
112
- status: 200,
113
- headers: { "Content-Type": "application/json" },
114
- })
115
- );
109
+ mockClient.getDashboard.mockResolvedValueOnce({ uiTree: invalidTree });
116
110
 
117
111
  setURL("?tab=metrics&dashboardId=def");
118
112
 
@@ -125,5 +119,10 @@ describe("useDashboardTree validation", () => {
125
119
  await waitFor(() => {
126
120
  expect(screen.getByText(/invalid layout/i)).toBeTruthy();
127
121
  });
122
+
123
+ expect(mockClient.getDashboard).toHaveBeenCalledWith(
124
+ "def",
125
+ expect.anything()
126
+ );
128
127
  });
129
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 {