@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/dist/index.cjs +20 -14
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +20 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/components/observability/MetricTimeSeries/index.tsx +17 -5
- package/src/components/observability/renderers/OtelLogTimeline.tsx +9 -5
- package/src/hooks/use-kopai-data.test.ts +1 -0
- package/src/hooks/use-live-logs.test.ts +1 -0
- package/src/pages/observability.test.tsx +11 -12
- package/src/pages/observability.tsx +9 -10
- package/src/providers/kopai-provider.tsx +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kopai/ui",
|
|
3
|
-
"version": "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/
|
|
37
|
-
"@kopai/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
}
|
|
@@ -77,12 +77,7 @@ describe("useDashboardTree validation", () => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
it("renders DynamicDashboard when API returns a valid uiTree", async () => {
|
|
80
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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 (
|