@checkstack/anomaly-frontend 0.2.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/CHANGELOG.md +50 -0
- package/package.json +35 -0
- package/src/components/AnomalyConfigPanel.tsx +154 -0
- package/src/components/AnomalyFieldOverridesEditor.tsx +362 -0
- package/src/components/AnomalySettingsForm.tsx +211 -0
- package/src/components/AnomalyTemplatePanel.tsx +133 -0
- package/src/components/SystemAnomalyBadge.tsx +71 -0
- package/src/components/SystemAnomalyWidget.tsx +283 -0
- package/src/components/useAnomalyFields.ts +70 -0
- package/src/index.tsx +1 -0
- package/src/plugin.tsx +78 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { usePluginClient } from "@checkstack/frontend-api";
|
|
3
|
+
import { HealthCheckApi } from "@checkstack/healthcheck-common";
|
|
4
|
+
import { useStrategySchemas } from "@checkstack/healthcheck-frontend";
|
|
5
|
+
|
|
6
|
+
import type { AnomalyDirection } from "@checkstack/anomaly-common";
|
|
7
|
+
|
|
8
|
+
export type AnomalyFieldMeta = {
|
|
9
|
+
path: string;
|
|
10
|
+
type: string;
|
|
11
|
+
defaultEnabled: boolean;
|
|
12
|
+
defaultDirection?: AnomalyDirection;
|
|
13
|
+
defaultSensitivity?: number;
|
|
14
|
+
defaultConfirmationWindow?: number;
|
|
15
|
+
defaultDriftEnabled?: boolean;
|
|
16
|
+
defaultDriftThreshold?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function useAnomalyFields(configurationId: string | undefined) {
|
|
20
|
+
const healthCheckClient = usePluginClient(HealthCheckApi);
|
|
21
|
+
|
|
22
|
+
const { data: hcConfig } = healthCheckClient.getConfiguration.useQuery(
|
|
23
|
+
{ id: configurationId ?? "" },
|
|
24
|
+
{ enabled: !!configurationId }
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const { schemas } = useStrategySchemas(hcConfig?.strategyId ?? "");
|
|
28
|
+
|
|
29
|
+
const availableFields = useMemo(() => {
|
|
30
|
+
if (!schemas?.resultSchema) return [];
|
|
31
|
+
|
|
32
|
+
type JsonSchemaNode = {
|
|
33
|
+
type?: string;
|
|
34
|
+
properties?: Record<string, JsonSchemaNode>;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const schema = schemas.resultSchema as JsonSchemaNode;
|
|
39
|
+
|
|
40
|
+
const extractFields = (obj: JsonSchemaNode | undefined, prefix = ""): AnomalyFieldMeta[] => {
|
|
41
|
+
const keys: AnomalyFieldMeta[] = [];
|
|
42
|
+
if (obj?.properties) {
|
|
43
|
+
for (const [key, value] of Object.entries(obj.properties)) {
|
|
44
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
45
|
+
if (value.type === "object" || value.properties) {
|
|
46
|
+
keys.push(...extractFields(value, path));
|
|
47
|
+
} else {
|
|
48
|
+
// Default to true unless explicitly disabled in schema
|
|
49
|
+
const defaultEnabled = value["x-anomaly-enabled"] !== false;
|
|
50
|
+
keys.push({
|
|
51
|
+
path,
|
|
52
|
+
type: value.type || "string",
|
|
53
|
+
defaultEnabled,
|
|
54
|
+
defaultDirection: value["x-anomaly-direction"] as AnomalyDirection | undefined,
|
|
55
|
+
defaultSensitivity: typeof value["x-anomaly-sensitivity"] === "number" ? value["x-anomaly-sensitivity"] : undefined,
|
|
56
|
+
defaultConfirmationWindow: typeof value["x-anomaly-confirmation-window"] === "number" ? value["x-anomaly-confirmation-window"] : undefined,
|
|
57
|
+
defaultDriftEnabled: typeof value["x-anomaly-drift-enabled"] === "boolean" ? value["x-anomaly-drift-enabled"] : undefined,
|
|
58
|
+
defaultDriftThreshold: typeof value["x-anomaly-drift-threshold"] === "number" ? value["x-anomaly-drift-threshold"] : undefined,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return keys;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return extractFields(schema).filter(k => k.path !== "error");
|
|
67
|
+
}, [schemas]);
|
|
68
|
+
|
|
69
|
+
return availableFields;
|
|
70
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { plugin as default } from "./plugin";
|
package/src/plugin.tsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { definePluginMetadata } from "@checkstack/common";
|
|
2
|
+
import { FrontendPlugin, createSlotExtension } from "@checkstack/frontend-api";
|
|
3
|
+
import {
|
|
4
|
+
AssignmentIDENodeSlot,
|
|
5
|
+
AssignmentIDEPanelSlot,
|
|
6
|
+
HealthCheckConfigIDENodeSlot,
|
|
7
|
+
HealthCheckConfigIDEPanelSlot,
|
|
8
|
+
type AssignmentIDEContext,
|
|
9
|
+
type HealthCheckConfigIDEContext
|
|
10
|
+
} from "@checkstack/healthcheck-frontend";
|
|
11
|
+
import { SystemStateBadgesSlot, SystemDetailsSlot } from "@checkstack/catalog-common";
|
|
12
|
+
import { AnomalyConfigPanel } from "./components/AnomalyConfigPanel";
|
|
13
|
+
import { AnomalyTemplatePanel } from "./components/AnomalyTemplatePanel";
|
|
14
|
+
import { SystemAnomalyBadge } from "./components/SystemAnomalyBadge";
|
|
15
|
+
import { SystemAnomalyWidget } from "./components/SystemAnomalyWidget";
|
|
16
|
+
import { IDETreeNode } from "@checkstack/ui";
|
|
17
|
+
import { Activity } from "lucide-react";
|
|
18
|
+
|
|
19
|
+
const pluginMetadata = definePluginMetadata({
|
|
20
|
+
pluginId: "anomaly",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const plugin: FrontendPlugin = {
|
|
24
|
+
metadata: pluginMetadata,
|
|
25
|
+
extensions: [
|
|
26
|
+
createSlotExtension(SystemStateBadgesSlot, {
|
|
27
|
+
id: "anomaly.system-badge",
|
|
28
|
+
component: SystemAnomalyBadge,
|
|
29
|
+
}),
|
|
30
|
+
createSlotExtension(SystemDetailsSlot, {
|
|
31
|
+
id: "anomaly.system-details.widget",
|
|
32
|
+
component: SystemAnomalyWidget,
|
|
33
|
+
}),
|
|
34
|
+
createSlotExtension(HealthCheckConfigIDENodeSlot, {
|
|
35
|
+
id: "anomaly.config-ide.node",
|
|
36
|
+
component: (context: HealthCheckConfigIDEContext) => (
|
|
37
|
+
<IDETreeNode
|
|
38
|
+
nodeId={`anomaly-template:${context.configurationId}`}
|
|
39
|
+
label="Anomaly Defaults"
|
|
40
|
+
icon={Activity}
|
|
41
|
+
selected={context.selectedNode === `anomaly-template:${context.configurationId}`}
|
|
42
|
+
onClick={() => context.onSelectNode(`anomaly-template:${context.configurationId}`)}
|
|
43
|
+
/>
|
|
44
|
+
),
|
|
45
|
+
}),
|
|
46
|
+
createSlotExtension(HealthCheckConfigIDEPanelSlot, {
|
|
47
|
+
id: "anomaly.config-ide.panel",
|
|
48
|
+
component: (context: HealthCheckConfigIDEContext) => {
|
|
49
|
+
if (!context.selectedNode?.startsWith("anomaly-template:")) {
|
|
50
|
+
return <></>;
|
|
51
|
+
}
|
|
52
|
+
return <AnomalyTemplatePanel context={context} />;
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
createSlotExtension(AssignmentIDENodeSlot, {
|
|
56
|
+
id: "anomaly.assignment-ide.node",
|
|
57
|
+
component: (context: AssignmentIDEContext) => (
|
|
58
|
+
<IDETreeNode
|
|
59
|
+
nodeId={`anomaly:${context.configurationId}`}
|
|
60
|
+
label="Anomaly Exceptions"
|
|
61
|
+
icon={Activity}
|
|
62
|
+
selected={context.selectedNode === `anomaly:${context.configurationId}`}
|
|
63
|
+
onClick={() => context.onSelectNode(`anomaly:${context.configurationId}`)}
|
|
64
|
+
indent
|
|
65
|
+
/>
|
|
66
|
+
),
|
|
67
|
+
}),
|
|
68
|
+
createSlotExtension(AssignmentIDEPanelSlot, {
|
|
69
|
+
id: "anomaly.assignment-ide.panel",
|
|
70
|
+
component: (context: AssignmentIDEContext) => {
|
|
71
|
+
if (!context.selectedNode?.startsWith("anomaly:")) {
|
|
72
|
+
return <></>;
|
|
73
|
+
}
|
|
74
|
+
return <AnomalyConfigPanel context={context} />;
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
};
|