Alerting: Create alertingAlertRuleFormSchema in restrictedGrafanaApis (#112794)
* create navigateToAlertFormSchemaApi in restrictedGrafanaApis and use it in formDefaults.ts * update alertingSchemaApi * fix test * move alertingSchemaApi.ts and the alerting section in README.md to a new alerting folder * move cloud rules form schema outside restrictedGrafanaApis * remove cloud fields in schema and use values from existing enums for some fields * refactor RestrictedGrafanaApisContextTypeInternalenrichment type adding a high level entry for alerting * refactor: change naming and come back to one single level in the RestrictedGrafanaApisContextType * update README.md
This commit is contained in:
@@ -6,9 +6,15 @@ import {
|
||||
useRestrictedGrafanaApis,
|
||||
} from './RestrictedGrafanaApis';
|
||||
|
||||
// Mock schema for testing
|
||||
const mockAlertRuleFormSchema = {
|
||||
parse: jest.fn((data: unknown) => data),
|
||||
safeParse: jest.fn((data: unknown) => ({ success: true, data })),
|
||||
};
|
||||
|
||||
describe('RestrictedGrafanaApis', () => {
|
||||
const apis: RestrictedGrafanaApisContextType = {
|
||||
addPanel: () => {},
|
||||
alertingAlertRuleFormSchema: mockAlertRuleFormSchema,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -21,16 +27,15 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'grafana-test-app'}
|
||||
apiAllowList={{ addPanel: ['grafana-test-app'] }}
|
||||
apiAllowList={{ alertingAlertRuleFormSchema: ['grafana-test-app'] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.current.addPanel).toEqual(apis.addPanel);
|
||||
expect(Object.keys(result.current)).toEqual(['addPanel']);
|
||||
expect(result.current.alertingAlertRuleFormSchema).toEqual(apis.alertingAlertRuleFormSchema);
|
||||
expect(Object.keys(result.current)).toEqual(['alertingAlertRuleFormSchema']);
|
||||
});
|
||||
|
||||
it('should share an API if the plugin is allowed using a regexp', () => {
|
||||
@@ -39,16 +44,15 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'grafana-test-app'}
|
||||
apiAllowList={{ addPanel: [/^grafana-/] }}
|
||||
apiAllowList={{ alertingAlertRuleFormSchema: [/^grafana-/] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.current.addPanel).toEqual(apis.addPanel);
|
||||
expect(Object.keys(result.current)).toEqual(['addPanel']);
|
||||
expect(result.current.alertingAlertRuleFormSchema).toEqual(apis.alertingAlertRuleFormSchema);
|
||||
expect(Object.keys(result.current)).toEqual(['alertingAlertRuleFormSchema']);
|
||||
});
|
||||
|
||||
it('should not share an API if the plugin is not directly allowed and no allow regexp matches it', () => {
|
||||
@@ -57,15 +61,14 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'myorg-test-app'}
|
||||
apiAllowList={{ addPanel: [/^grafana-/] }}
|
||||
apiAllowList={{ alertingAlertRuleFormSchema: [/^grafana-/] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.current.addPanel).not.toBeDefined();
|
||||
expect(result.current.alertingAlertRuleFormSchema).not.toBeDefined();
|
||||
});
|
||||
|
||||
// Ideally the `allowList` and the `blockList` are not used together
|
||||
@@ -75,17 +78,16 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'grafana-test-app'}
|
||||
apiAllowList={{ addPanel: ['grafana-test-app'] }}
|
||||
apiBlockList={{ addPanel: ['grafana-test-app'] }}
|
||||
apiAllowList={{ alertingAlertRuleFormSchema: ['grafana-test-app'] }}
|
||||
apiBlockList={{ alertingAlertRuleFormSchema: ['grafana-test-app'] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.current.addPanel).toEqual(apis.addPanel);
|
||||
expect(Object.keys(result.current)).toEqual(['addPanel']);
|
||||
expect(result.current.alertingAlertRuleFormSchema).toEqual(apis.alertingAlertRuleFormSchema);
|
||||
expect(Object.keys(result.current)).toEqual(['alertingAlertRuleFormSchema']);
|
||||
});
|
||||
|
||||
it('should share an API with allowed plugins (testing multiple plugins)', () => {
|
||||
@@ -97,14 +99,13 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'grafana-test-app'}
|
||||
apiAllowList={{ addPanel: ['grafana-test-app', 'grafana-assistant-app'] }}
|
||||
apiAllowList={{ alertingAlertRuleFormSchema: ['grafana-test-app', 'grafana-assistant-app'] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.result.current.addPanel).toEqual(apis.addPanel);
|
||||
expect(result.result.current.alertingAlertRuleFormSchema).toEqual(apis.alertingAlertRuleFormSchema);
|
||||
|
||||
// 2. Second app
|
||||
result = renderHook(() => useRestrictedGrafanaApis(), {
|
||||
@@ -112,14 +113,13 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'grafana-assistant-app'}
|
||||
apiAllowList={{ addPanel: ['grafana-test-app', 'grafana-assistant-app'] }}
|
||||
apiAllowList={{ alertingAlertRuleFormSchema: ['grafana-test-app', 'grafana-assistant-app'] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.result.current.addPanel).toEqual(apis.addPanel);
|
||||
expect(result.result.current.alertingAlertRuleFormSchema).toEqual(apis.alertingAlertRuleFormSchema);
|
||||
});
|
||||
|
||||
it('should not share APIs with plugins that are not allowed', () => {
|
||||
@@ -128,15 +128,14 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'grafana-restricted-app'}
|
||||
apiAllowList={{ addPanel: ['grafana-authorised-app'] }}
|
||||
apiAllowList={{ alertingAlertRuleFormSchema: ['grafana-authorised-app'] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.current.addPanel).not.toBeDefined();
|
||||
expect(result.current.alertingAlertRuleFormSchema).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should not share APIs with anyone if both the allowList and the blockList are empty', () => {
|
||||
@@ -144,13 +143,16 @@ describe('RestrictedGrafanaApis', () => {
|
||||
|
||||
result = renderHook(() => useRestrictedGrafanaApis(), {
|
||||
wrapper: ({ children }: { children: React.ReactNode }) => (
|
||||
<RestrictedGrafanaApisContextProvider apis={apis} pluginId={'grafana-test-app'} apiAllowList={{ addPanel: [] }}>
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'grafana-test-app'}
|
||||
apiAllowList={{ alertingAlertRuleFormSchema: [] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.result.current.addPanel).not.toBeDefined();
|
||||
expect(result.result.current.alertingAlertRuleFormSchema).not.toBeDefined();
|
||||
|
||||
result = renderHook(() => useRestrictedGrafanaApis(), {
|
||||
wrapper: ({ children }: { children: React.ReactNode }) => (
|
||||
@@ -159,8 +161,7 @@ describe('RestrictedGrafanaApis', () => {
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.result.current.addPanel).not.toBeDefined();
|
||||
expect(result.result.current.alertingAlertRuleFormSchema).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should not share APIs with blocked plugins', () => {
|
||||
@@ -169,14 +170,13 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'grafana-test-app'}
|
||||
apiBlockList={{ addPanel: ['grafana-test-app'] }}
|
||||
apiBlockList={{ alertingAlertRuleFormSchema: ['grafana-test-app'] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.current.addPanel).not.toBeDefined();
|
||||
expect(result.current.alertingAlertRuleFormSchema).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should not share APIs with plugins that match any block list regexes', () => {
|
||||
@@ -185,13 +185,12 @@ describe('RestrictedGrafanaApis', () => {
|
||||
<RestrictedGrafanaApisContextProvider
|
||||
apis={apis}
|
||||
pluginId={'myorg-test-app'}
|
||||
apiBlockList={{ addPanel: [/^myorg-/] }}
|
||||
apiBlockList={{ alertingAlertRuleFormSchema: [/^myorg-/] }}
|
||||
>
|
||||
{children}
|
||||
</RestrictedGrafanaApisContextProvider>
|
||||
),
|
||||
});
|
||||
// @ts-expect-error No APIs are defined yet
|
||||
expect(result.current.addPanel).not.toBeDefined();
|
||||
expect(result.current.alertingAlertRuleFormSchema).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { createContext, ReactElement, PropsWithChildren, useMemo, useContext } from 'react';
|
||||
|
||||
// Generic schema type to avoid zod dependency in @grafana/data
|
||||
interface ZodSchema {
|
||||
parse: (data: unknown) => unknown;
|
||||
safeParse: (data: unknown) => { success: boolean; data?: unknown; error?: unknown };
|
||||
}
|
||||
|
||||
export interface RestrictedGrafanaApisContextTypeInternal {
|
||||
// Add types for restricted Grafana APIs here
|
||||
// (Make sure that they are typed as optional properties)
|
||||
// e.g. addPanel?: (vizPanel: VizPanel) => void;
|
||||
alertingAlertRuleFormSchema?: ZodSchema;
|
||||
}
|
||||
|
||||
// We are exposing this through a "type validation", to make sure that all APIs are optional (which helps plugins catering for scenarios when they are not available).
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { clamp } from 'lodash';
|
||||
import { z } from 'zod';
|
||||
import z from 'zod';
|
||||
|
||||
import { config, getDataSourceSrv } from '@grafana/runtime';
|
||||
import { alertingAlertRuleFormSchema } from 'app/features/plugins/components/restrictedGrafanaApis/alerting/alertRuleFormSchema';
|
||||
import { RuleWithLocation } from 'app/types/unified-alerting';
|
||||
import { GrafanaAlertStateDecision, RulerRuleDTO } from 'app/types/unified-alerting-dto';
|
||||
|
||||
@@ -156,47 +157,11 @@ export function formValuesFromQueryParams(ruleDefinition: string, type: RuleForm
|
||||
)
|
||||
);
|
||||
}
|
||||
// schema for cloud rule form values. This is necessary because the cloud rule form values are not the same as the grafana rule form values.
|
||||
// schema for grafana rule values is navigateToAlertFormSchema , shared in the restrictedGrafanaApis.
|
||||
// TODO: add this to the DMA new plugin.
|
||||
|
||||
export function formValuesFromPrefill(rule: Partial<RuleFormValues>): RuleFormValues {
|
||||
// coerce prefill params to a valid RuleFormValues interface
|
||||
const parsedRule = ruleFormValuesSchema.parse(rule);
|
||||
|
||||
return revealHiddenQueries({
|
||||
...getDefaultFormValues(rule.type),
|
||||
...parsedRule,
|
||||
});
|
||||
}
|
||||
|
||||
export function formValuesFromExistingRule(rule: RuleWithLocation<RulerRuleDTO>) {
|
||||
return revealHiddenQueries(rulerRuleToFormValues(rule));
|
||||
}
|
||||
|
||||
export function defaultFormValuesForRuleType(ruleType: RuleFormType): RuleFormValues {
|
||||
return {
|
||||
...getDefaultFormValues(ruleType),
|
||||
condition: 'C',
|
||||
queries: getDefaultQueries(isGrafanaRecordingRuleByType(ruleType)),
|
||||
type: ruleType,
|
||||
evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO This function is not 100% valid. There is no support for cloud form type because
|
||||
// it's not valid from the path param point of view.
|
||||
export function translateRouteParamToRuleType(param = ''): RuleFormType {
|
||||
if (param === 'recording') {
|
||||
return RuleFormType.cloudRecording;
|
||||
}
|
||||
|
||||
if (param === 'grafana-recording') {
|
||||
return RuleFormType.grafanaRecording;
|
||||
}
|
||||
|
||||
return RuleFormType.grafana;
|
||||
}
|
||||
|
||||
// we use this schema to coerce prefilled query params into a valid "FormValues" interface
|
||||
const ruleFormValuesSchema = z.looseObject({
|
||||
const cloudRuleFormValuesSchema = z.looseObject({
|
||||
name: z.string().optional(),
|
||||
type: z.enum(RuleFormType).catch(RuleFormType.grafana),
|
||||
dataSourceName: z.string().optional().default(''),
|
||||
@@ -273,3 +238,49 @@ const ruleFormValuesSchema = z.looseObject({
|
||||
expression: z.string().optional(),
|
||||
missingSeriesEvalsToResolve: z.number().optional(),
|
||||
});
|
||||
|
||||
export function formValuesFromPrefill(rule: Partial<RuleFormValues>): RuleFormValues {
|
||||
let parsedRule: z.infer<typeof alertingAlertRuleFormSchema> | z.infer<typeof cloudRuleFormValuesSchema>;
|
||||
// differencitate between cloud and grafana prefill
|
||||
if (rule.type === RuleFormType.cloudAlerting) {
|
||||
// we use this schema to coerce prefilled query params into a valid "FormValues" interface
|
||||
parsedRule = cloudRuleFormValuesSchema.parse(rule);
|
||||
} else {
|
||||
// grafana prefill
|
||||
// coerce prefill params to a valid RuleFormValues interface
|
||||
parsedRule = alertingAlertRuleFormSchema.parse(rule);
|
||||
}
|
||||
|
||||
return revealHiddenQueries({
|
||||
...getDefaultFormValues(rule.type),
|
||||
...parsedRule,
|
||||
});
|
||||
}
|
||||
|
||||
export function formValuesFromExistingRule(rule: RuleWithLocation<RulerRuleDTO>) {
|
||||
return revealHiddenQueries(rulerRuleToFormValues(rule));
|
||||
}
|
||||
|
||||
export function defaultFormValuesForRuleType(ruleType: RuleFormType): RuleFormValues {
|
||||
return {
|
||||
...getDefaultFormValues(ruleType),
|
||||
condition: 'C',
|
||||
queries: getDefaultQueries(isGrafanaRecordingRuleByType(ruleType)),
|
||||
type: ruleType,
|
||||
evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO This function is not 100% valid. There is no support for cloud form type because
|
||||
// it's not valid from the path param point of view.
|
||||
export function translateRouteParamToRuleType(param = ''): RuleFormType {
|
||||
if (param === 'recording') {
|
||||
return RuleFormType.cloudRecording;
|
||||
}
|
||||
|
||||
if (param === 'grafana-recording') {
|
||||
return RuleFormType.grafanaRecording;
|
||||
}
|
||||
|
||||
return RuleFormType.grafana;
|
||||
}
|
||||
|
||||
+2
@@ -2,11 +2,13 @@ import { PropsWithChildren, ReactElement } from 'react';
|
||||
|
||||
import { RestrictedGrafanaApisContextProvider, RestrictedGrafanaApisContextType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { alertingAlertRuleFormSchemaApi } from 'app/features/plugins/components/restrictedGrafanaApis/alerting/alertRuleFormSchema';
|
||||
|
||||
const restrictedGrafanaApis: RestrictedGrafanaApisContextType = config.featureToggles.restrictedPluginApis
|
||||
? {
|
||||
// Add your restricted APIs here
|
||||
// (APIs that should be availble to ALL plugins should be shared via our packages, e.g. @grafana/data.)
|
||||
alertingAlertRuleFormSchema: alertingAlertRuleFormSchemaApi.alertingAlertRuleFormSchema,
|
||||
}
|
||||
: {};
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
## Navigate to Alert Form Schema API
|
||||
|
||||
The `alertingAlertRuleFormSchema` API provides a Zod schema for navigating to the Grafana alert form with pre-filled data. This API is useful for plugins that need to validate data before navigating to the alert creation form.
|
||||
|
||||
### Available Schema
|
||||
|
||||
- `alertingAlertRuleFormSchema` - Schema for data used to navigate to the alert form with pre-filled values
|
||||
|
||||
### Usage Example
|
||||
|
||||
```ts
|
||||
import { useRestrictedGrafanaApis } from "@grafana/data";
|
||||
|
||||
function MyAlertingPlugin() {
|
||||
const { alertingAlertRuleFormSchema } = useRestrictedGrafanaApis();
|
||||
|
||||
const validateAndNavigateToAlertForm = (data: unknown) => {
|
||||
if (!alertingAlertRuleFormSchema) {
|
||||
console.warn('Navigate to alert form schema API not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate using the navigate to alert form schema
|
||||
const result = alertingAlertRuleFormSchema.safeParse(data);
|
||||
|
||||
if (result.success) {
|
||||
console.log('Valid navigation data:', result.data);
|
||||
// Proceed with navigating to the alert form
|
||||
} else {
|
||||
console.error('Validation failed:', result.error.errors);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => validateAndNavigateToAlertForm(someNavigationData)}>Navigate to Alert Form</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
To enable the navigate to alert form schema API for specific plugins, add the following to your Grafana configuration:
|
||||
|
||||
```ini
|
||||
[plugins.restricted_apis_allowlist]
|
||||
# Allow specific plugins to access the navigate to alert form schema API
|
||||
alertingAlertRuleFormSchema = "myorg-alerting-plugin, grafana-enterprise-.*"
|
||||
```
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import alertDef from 'app/features/alerting/state/alertDef';
|
||||
import { RuleFormType } from 'app/features/alerting/unified/types/rule-form';
|
||||
import { ExpressionQueryType } from 'app/features/expressions/types';
|
||||
import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto';
|
||||
|
||||
// Schema for __expr__ type queries (reduce, threshold, etc.)
|
||||
export const exprQuerySchema = z.object({
|
||||
refId: z.string().describe('Reference ID for the query, e.g., "B", "C", etc.'),
|
||||
type: z.enum(ExpressionQueryType).describe('Expression type'),
|
||||
datasource: z.object({
|
||||
uid: z.literal('__expr__').describe('Must be "__expr__" for expression queries'),
|
||||
type: z.literal('__expr__').describe('Must be "__expr__" for expression queries'),
|
||||
}),
|
||||
conditions: z
|
||||
.array(
|
||||
z.object({
|
||||
type: z.string().describe('Condition type, e.g., "query"'),
|
||||
evaluator: z.object({
|
||||
params: z.array(z.any()).describe('Parameters for the evaluator'),
|
||||
type: z.enum(alertDef.evalFunctions.map((ef) => ef.value)).describe('Evaluator type'),
|
||||
}),
|
||||
operator: z.object({
|
||||
type: z.enum(alertDef.evalOperators.map((eo) => eo.value)).describe('Operator type'),
|
||||
}),
|
||||
query: z.object({
|
||||
params: z.array(z.string()).describe('Query parameters, typically the refId to evaluate'),
|
||||
}),
|
||||
reducer: z.object({
|
||||
params: z.array(z.any()).describe('Parameters for the reducer'),
|
||||
type: z.string().describe('Reducer type, e.g., "last", "avg", "sum", "count", "min", "max"'),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.describe('Conditions for the expression query'),
|
||||
reducer: z.string().optional().describe('Reducer function, e.g., "last", "avg", "sum"'),
|
||||
expression: z.string().optional().describe('Expression referencing other queries, e.g., "A"'),
|
||||
math: z.string().optional().describe('Math expression for math type queries'),
|
||||
});
|
||||
|
||||
// Schema for regular datasource queries
|
||||
export const alertingQuerySchema = z.object({
|
||||
refId: z.string().describe('Reference ID for the query, e.g., "A", "B", etc.'),
|
||||
queryType: z.string().optional().default('alerting').describe('Type of query (e.g., "alerting", "recording")'),
|
||||
expression: z
|
||||
.string()
|
||||
.optional()
|
||||
.default('')
|
||||
.describe('Query expression to be executed. This can not include variables (e.g. $var).'),
|
||||
instant: z.boolean().optional().default(true).describe('Whether the query is an instant query'),
|
||||
range: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe('Whether the query is a range query, should be false if instant is true'),
|
||||
datasource: z.object({
|
||||
type: z.string().optional().describe('Datasource type or "__expr__" when it is an expression query'),
|
||||
uid: z.string().optional().describe('Datasource UID'),
|
||||
}),
|
||||
});
|
||||
|
||||
// Combined schema that supports both regular and expression queries
|
||||
export const alertingModelSchema = z.union([alertingQuerySchema, exprQuerySchema]);
|
||||
|
||||
// Main navigate to alert form schema - merged from both alertingSchemaApi and formDefaults
|
||||
export const alertingAlertRuleFormSchema = z.object({
|
||||
// Common fields
|
||||
name: z.string().optional().describe('Name of the alert rule'),
|
||||
type: z.enum(RuleFormType).optional().catch(RuleFormType.grafana).describe('Type of the alert rule'),
|
||||
dataSourceName: z.string().optional().default(''),
|
||||
group: z.string().optional().describe('Alert group name'),
|
||||
|
||||
// Labels and annotations
|
||||
labels: z
|
||||
.array(
|
||||
z.object({
|
||||
key: z.string().describe('Label key'),
|
||||
value: z.string().describe('Label value'),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.default([])
|
||||
.describe('Labels for the alert rule'),
|
||||
annotations: z
|
||||
.array(
|
||||
z.object({
|
||||
key: z
|
||||
.string()
|
||||
.describe('Annotation key; for dashboard panel annotations, use "__dashboardUid__" or "__panelId__"'),
|
||||
value: z.string().describe('Annotation value'),
|
||||
})
|
||||
)
|
||||
.optional().describe(`Optional annotations for the alert rule. When creating alerts from a dashboard panel, include:
|
||||
- {"key": "__dashboardUid__", "value": "<dashboard-uid>"}
|
||||
- {"key": "__panelId__", "value": "<panel-id>"}
|
||||
These annotations link the alert back to the source dashboard and panel.`),
|
||||
|
||||
// Folder configuration
|
||||
folder: z
|
||||
.object({
|
||||
kind: z.enum(['folder']).default('folder'),
|
||||
uid: z.string().describe('Folder UID where the alert rule will be created'),
|
||||
title: z.string().optional().default('').describe('Folder title'),
|
||||
})
|
||||
.optional()
|
||||
.describe('Folder configuration for the alert rule'),
|
||||
|
||||
// Queries
|
||||
queries: z
|
||||
.array(
|
||||
z.object({
|
||||
refId: z.string().describe('Reference ID for the query (e.g., "A", "B", "C")'),
|
||||
queryType: z.string().optional().default('instant').describe('Type of query (e.g., "instant")'),
|
||||
relativeTimeRange: z
|
||||
.object({
|
||||
from: z.number().describe('Relative time from in seconds (e.g., 3600 for 1 hour)'),
|
||||
to: z.number().default(0).describe('Relative time to in seconds (usually 0 for "now")'),
|
||||
})
|
||||
.optional(),
|
||||
datasourceUid: z.string().describe('Datasource UID for the query'),
|
||||
model: alertingModelSchema.describe('Query model containing the actual query configuration'),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.default([])
|
||||
.describe('Array of queries that form the alert rule'),
|
||||
|
||||
// Alert rule configuration
|
||||
condition: z.string().optional().describe('Reference ID of the query that acts as the condition (e.g., "C")'),
|
||||
noDataState: z.enum(GrafanaAlertStateDecision).optional().describe('State when no data is available'),
|
||||
execErrState: z.enum(GrafanaAlertStateDecision).optional().describe('State when there is an execution error'),
|
||||
evaluateEvery: z.string().optional().describe('Evaluation interval'),
|
||||
evaluateFor: z.string().optional().describe('Evaluation duration'),
|
||||
keepFiringFor: z.string().optional().describe('Keep firing duration'),
|
||||
isPaused: z.boolean().optional().default(false).describe('Whether the rule is paused'),
|
||||
|
||||
// Manual routing and contact points
|
||||
manualRouting: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe('Whether to use manual routing. If true, contactPoints are used.'),
|
||||
contactPoints: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
selectedContactPoint: z.string().describe('Selected contact point to send the alert to'),
|
||||
overrideGrouping: z.boolean().describe('Whether to override the default grouping'),
|
||||
groupBy: z.array(z.string()).describe('Group by labels'),
|
||||
overrideTimings: z.boolean().describe('Whether to override the default timings'),
|
||||
groupWaitValue: z.string().describe('Group wait value'),
|
||||
groupIntervalValue: z.string().describe('Group interval value'),
|
||||
repeatIntervalValue: z.string().describe('Repeat interval value'),
|
||||
muteTimeIntervals: z.array(z.string()).describe('Mute time intervals'),
|
||||
activeTimeIntervals: z.array(z.string()).describe('Active time intervals'),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.default({
|
||||
GRAFANA_RULES_SOURCE_NAME: {
|
||||
selectedContactPoint: 'default',
|
||||
overrideGrouping: false,
|
||||
groupBy: [],
|
||||
overrideTimings: false,
|
||||
groupWaitValue: '',
|
||||
groupIntervalValue: '',
|
||||
repeatIntervalValue: '',
|
||||
muteTimeIntervals: [],
|
||||
activeTimeIntervals: [],
|
||||
},
|
||||
})
|
||||
.describe('Contact points configuration'),
|
||||
|
||||
// Editor settings
|
||||
editorSettings: z
|
||||
.object({
|
||||
simplifiedQueryEditor: z.boolean(),
|
||||
simplifiedNotificationEditor: z.boolean(),
|
||||
})
|
||||
.optional()
|
||||
.default({ simplifiedQueryEditor: true, simplifiedNotificationEditor: true })
|
||||
.describe('Editor settings'),
|
||||
|
||||
// Additional fields
|
||||
metric: z.string().optional().describe('Metric name for Grafana recording rules'),
|
||||
targetDatasourceUid: z.string().optional().describe('Target datasource UID for Grafana recording rules'),
|
||||
|
||||
// Navigation
|
||||
returnTo: z.string().optional().describe('Optional URL to return to after creating the alert'),
|
||||
});
|
||||
|
||||
// Export types for use in plugins
|
||||
export type AlertingAlertRuleFormSchemaType = z.infer<typeof alertingAlertRuleFormSchema>;
|
||||
export type AlertingQuerySchemaType = z.infer<typeof alertingQuerySchema>;
|
||||
export type ExprQuerySchemaType = z.infer<typeof exprQuerySchema>;
|
||||
export type AlertingModelSchemaType = z.infer<typeof alertingModelSchema>;
|
||||
|
||||
// Simple API that only exposes the navigate to alert rule form schema
|
||||
export const alertingAlertRuleFormSchemaApi = {
|
||||
alertingAlertRuleFormSchema,
|
||||
};
|
||||
Reference in New Issue
Block a user