update logic for expression parsing from panel to alerting
This commit is contained in:
+2
-2
@@ -70,7 +70,7 @@ exports[`PanelAlertTabContent Will render alerts belonging to panel and a button
|
||||
"refId": "B",
|
||||
"type": "reduce",
|
||||
},
|
||||
"queryType": "",
|
||||
"queryType": "expression",
|
||||
"refId": "B",
|
||||
},
|
||||
{
|
||||
@@ -105,7 +105,7 @@ exports[`PanelAlertTabContent Will render alerts belonging to panel and a button
|
||||
"refId": "C",
|
||||
"type": "threshold",
|
||||
},
|
||||
"queryType": "",
|
||||
"queryType": "expression",
|
||||
"refId": "C",
|
||||
},
|
||||
],
|
||||
|
||||
+14
@@ -24,6 +24,20 @@ jest.mock('react-use', () => ({
|
||||
useAsync: () => ({ loading: false, value: {} }),
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
config: {
|
||||
...jest.requireActual('@grafana/runtime').config,
|
||||
featureToggles: {
|
||||
createAlertRuleFromPanel: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../components/AlertRuleDrawerForm', () => ({
|
||||
AlertRuleDrawerForm: () => null,
|
||||
}));
|
||||
|
||||
describe('Analytics', () => {
|
||||
it('Sends log info when creating an alert rule from a panel', async () => {
|
||||
const panel = new PanelModel({
|
||||
|
||||
+5
-1
@@ -10,6 +10,7 @@ import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
import { useSelector } from 'app/types/store';
|
||||
|
||||
import { LogMessages, logInfo } from '../../Analytics';
|
||||
import { AlertRuleDrawerForm } from '../../components/AlertRuleDrawerForm';
|
||||
import { createPanelAlertRuleNavigation } from '../../utils/navigation';
|
||||
import { panelToRuleFormValues } from '../../utils/rule-form';
|
||||
@@ -72,7 +73,10 @@ export const NewRuleFromPanelButton = ({ dashboard, panel, className }: Props) =
|
||||
icon="bell"
|
||||
className={className}
|
||||
data-testid="create-alert-rule-button-drawer"
|
||||
onClick={() => setIsOpen(true)}
|
||||
onClick={() => {
|
||||
logInfo(LogMessages.alertRuleFromPanel);
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="alerting.new-rule-from-panel-button.new-alert-rule">New alert rule</Trans>
|
||||
</Button>
|
||||
|
||||
+8
-4
@@ -81,7 +81,9 @@ exports[`RuleEditor grafana managed rules can create new grafana managed alert 1
|
||||
"type": "and",
|
||||
},
|
||||
"query": {
|
||||
"params": [],
|
||||
"params": [
|
||||
"B",
|
||||
],
|
||||
},
|
||||
"reducer": {
|
||||
"params": [],
|
||||
@@ -99,7 +101,7 @@ exports[`RuleEditor grafana managed rules can create new grafana managed alert 1
|
||||
"refId": "B",
|
||||
"type": "reduce",
|
||||
},
|
||||
"queryType": "",
|
||||
"queryType": "expression",
|
||||
"refId": "B",
|
||||
},
|
||||
{
|
||||
@@ -117,7 +119,9 @@ exports[`RuleEditor grafana managed rules can create new grafana managed alert 1
|
||||
"type": "and",
|
||||
},
|
||||
"query": {
|
||||
"params": [],
|
||||
"params": [
|
||||
"C",
|
||||
],
|
||||
},
|
||||
"reducer": {
|
||||
"params": [],
|
||||
@@ -134,7 +138,7 @@ exports[`RuleEditor grafana managed rules can create new grafana managed alert 1
|
||||
"refId": "C",
|
||||
"type": "threshold",
|
||||
},
|
||||
"queryType": "",
|
||||
"queryType": "expression",
|
||||
"refId": "C",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -27,17 +27,28 @@ export function setQueryEditorSettings(values: RuleFormValues): RuleFormValues {
|
||||
// data queries only
|
||||
const dataQueries = values.queries.filter((query) => !isExpressionQuery(query.model));
|
||||
|
||||
// expression queries only
|
||||
const expressionQueries = values.queries.filter((query) => isExpressionQueryInAlert(query));
|
||||
// expression queries only - but filter out invalid ones that don't have a type field
|
||||
const expressionQueries = values.queries.filter((query): query is AlertQuery<ExpressionQuery> => {
|
||||
if (!isExpressionQueryInAlert(query)) {
|
||||
return false;
|
||||
}
|
||||
// Check if the expression has a valid type field
|
||||
// React Hook Form might strip the type field, so we need to check it exists
|
||||
return 'type' in query.model && query.model.type !== undefined;
|
||||
});
|
||||
|
||||
// If we have data queries but no expressions (e.g., coming from dashboard panel),
|
||||
// default to simplified mode so the form can create appropriate expressions
|
||||
// If we have data queries but no VALID expressions (e.g., coming from dashboard panel with malformed expressions),
|
||||
// remove the invalid expressions and set condition to empty so simplified mode can regenerate them
|
||||
const hasDataQueries = dataQueries.length > 0;
|
||||
const hasExpressions = expressionQueries.length > 0;
|
||||
const hasValidExpressions = expressionQueries.length > 0;
|
||||
const totalExpressions = values.queries.filter((query) => isExpressionQueryInAlert(query)).length;
|
||||
const hasInvalidExpressions = totalExpressions > expressionQueries.length;
|
||||
|
||||
if (hasDataQueries && !hasExpressions) {
|
||||
if (hasDataQueries && (!hasValidExpressions || hasInvalidExpressions)) {
|
||||
return {
|
||||
...values,
|
||||
queries: dataQueries, // Only keep data queries, remove invalid expressions
|
||||
condition: '', // Clear condition so simplified editor can set it
|
||||
editorSettings: {
|
||||
simplifiedQueryEditor: true,
|
||||
simplifiedNotificationEditor: true,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ScopedVars,
|
||||
TimeRange,
|
||||
getDefaultRelativeTimeRange,
|
||||
getNextRefId,
|
||||
rangeUtil,
|
||||
} from '@grafana/data';
|
||||
import { PromQuery } from '@grafana/prometheus';
|
||||
@@ -558,14 +559,85 @@ export const getDefaultRecordingRulesQueries = (
|
||||
];
|
||||
};
|
||||
|
||||
export const getDefaultExpressions = (...refIds: [string, string]) => {
|
||||
const getDefaultExpressions = (...refIds: [string, string] | [string, string, string]): AlertQuery[] => {
|
||||
const refOne = refIds[0];
|
||||
const refTwo = refIds[1];
|
||||
// If a third parameter is provided, use it as the source query refId, otherwise default to 'A'
|
||||
const sourceRefId = refIds.length === 3 ? refIds[2] : 'A';
|
||||
|
||||
const reduceQuery = getDefaultReduceExpression({ inputRefId: 'A', reduceRefId: refOne });
|
||||
const thresholdQuery = getDefaultThresholdExpression({ inputRefId: refOne, thresholdRefId: refTwo });
|
||||
const reduceExpression: ExpressionQuery = {
|
||||
refId: refIds[0],
|
||||
type: ExpressionQueryType.reduce,
|
||||
datasource: {
|
||||
uid: ExpressionDatasourceUID,
|
||||
type: ExpressionDatasourceRef.type,
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
type: 'query',
|
||||
evaluator: {
|
||||
params: [],
|
||||
type: EvalFunction.IsAbove,
|
||||
},
|
||||
operator: {
|
||||
type: 'and',
|
||||
},
|
||||
query: {
|
||||
params: [refOne],
|
||||
},
|
||||
reducer: {
|
||||
params: [],
|
||||
type: 'last',
|
||||
},
|
||||
},
|
||||
],
|
||||
reducer: 'last',
|
||||
expression: sourceRefId,
|
||||
};
|
||||
|
||||
return [reduceQuery, thresholdQuery] as const;
|
||||
const thresholdExpression: ExpressionQuery = {
|
||||
refId: refTwo,
|
||||
type: ExpressionQueryType.threshold,
|
||||
datasource: {
|
||||
uid: ExpressionDatasourceUID,
|
||||
type: ExpressionDatasourceRef.type,
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
type: 'query',
|
||||
evaluator: {
|
||||
params: [0],
|
||||
type: EvalFunction.IsAbove,
|
||||
},
|
||||
operator: {
|
||||
type: 'and',
|
||||
},
|
||||
query: {
|
||||
params: [refTwo],
|
||||
},
|
||||
reducer: {
|
||||
params: [],
|
||||
type: 'last',
|
||||
},
|
||||
},
|
||||
],
|
||||
expression: refOne,
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
refId: refOne,
|
||||
datasourceUid: ExpressionDatasourceUID,
|
||||
queryType: 'expression',
|
||||
model: reduceExpression,
|
||||
},
|
||||
{
|
||||
refId: refTwo,
|
||||
datasourceUid: ExpressionDatasourceUID,
|
||||
queryType: 'expression',
|
||||
model: thresholdExpression,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getDefaultExpressionsForRecording = (refOne: string): Array<AlertQuery<ExpressionQuery>> => {
|
||||
@@ -782,6 +854,17 @@ export const panelToRuleFormValues = async (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Add default expression queries if they don't exist
|
||||
if (!queries.find((query) => query.datasourceUid === ExpressionDatasourceUID)) {
|
||||
// Get the last data query's refId to use as the source for the reduce expression
|
||||
const lastDataQueryRefId = queries[queries.length - 1].refId;
|
||||
const reduceRefId = getNextRefId(queries);
|
||||
const queriesWithReduce = [...queries, { refId: reduceRefId, datasourceUid: '', queryType: '', model: {} }];
|
||||
const thresholdRefId = getNextRefId(queriesWithReduce);
|
||||
const expressions = getDefaultExpressions(reduceRefId, thresholdRefId, lastDataQueryRefId);
|
||||
queries.push(...expressions);
|
||||
}
|
||||
|
||||
const { folderTitle, folderUid } = dashboard.meta;
|
||||
const folder =
|
||||
folderUid && folderTitle
|
||||
@@ -797,8 +880,7 @@ export const panelToRuleFormValues = async (
|
||||
folder,
|
||||
queries,
|
||||
name: panel.title,
|
||||
// Condition left empty - expressions will be created in the alert rule form under advanced options
|
||||
condition: '',
|
||||
condition: queries[queries.length - 1].refId,
|
||||
annotations: [
|
||||
{
|
||||
key: Annotation.dashboardUID,
|
||||
@@ -850,6 +932,17 @@ export const scenesPanelToRuleFormValues = async (vizPanel: VizPanel): Promise<P
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Add default expression queries if they don't exist
|
||||
if (!grafanaQueries.find((query) => query.datasourceUid === ExpressionDatasourceUID)) {
|
||||
// Get the last data query's refId to use as the source for the reduce expression
|
||||
const lastDataQueryRefId = grafanaQueries[grafanaQueries.length - 1].refId;
|
||||
const reduceRefId = getNextRefId(grafanaQueries);
|
||||
const queriesWithReduce = [...grafanaQueries, { refId: reduceRefId, datasourceUid: '', queryType: '', model: {} }];
|
||||
const thresholdRefId = getNextRefId(queriesWithReduce);
|
||||
const expressions = getDefaultExpressions(reduceRefId, thresholdRefId, lastDataQueryRefId);
|
||||
grafanaQueries.push(...expressions);
|
||||
}
|
||||
|
||||
const { folderTitle, folderUid } = dashboard.state.meta;
|
||||
|
||||
const folder =
|
||||
@@ -866,8 +959,7 @@ export const scenesPanelToRuleFormValues = async (vizPanel: VizPanel): Promise<P
|
||||
folder,
|
||||
queries: grafanaQueries,
|
||||
name: vizPanel.state.title,
|
||||
// Condition left empty - expressions will be created in the alert rule form under advanced options
|
||||
condition: '',
|
||||
condition: grafanaQueries[grafanaQueries.length - 1].refId,
|
||||
annotations: [
|
||||
{
|
||||
key: Annotation.dashboardUID,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Trans, t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { Alert, Button } from '@grafana/ui';
|
||||
import { LogMessages, logInfo } from 'app/features/alerting/unified/Analytics';
|
||||
import { AlertRuleDrawerForm } from 'app/features/alerting/unified/components/AlertRuleDrawerForm';
|
||||
import { createPanelAlertRuleNavigation } from 'app/features/alerting/unified/utils/navigation';
|
||||
import { scenesPanelToRuleFormValues } from 'app/features/alerting/unified/utils/rule-form';
|
||||
@@ -59,6 +60,7 @@ export const ScenesNewRuleFromPanelButton = ({ panel, className }: ScenesNewRule
|
||||
className={className}
|
||||
data-testid="create-alert-rule-button-drawer"
|
||||
onClick={() => {
|
||||
logInfo(LogMessages.alertRuleFromPanel);
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
|
||||
+2
-2
@@ -65,7 +65,7 @@ exports[`PanelAlertTabContent Will render alerts belonging to panel and a button
|
||||
"refId": "B",
|
||||
"type": "reduce",
|
||||
},
|
||||
"queryType": "",
|
||||
"queryType": "expression",
|
||||
"refId": "B",
|
||||
},
|
||||
{
|
||||
@@ -100,7 +100,7 @@ exports[`PanelAlertTabContent Will render alerts belonging to panel and a button
|
||||
"refId": "C",
|
||||
"type": "threshold",
|
||||
},
|
||||
"queryType": "",
|
||||
"queryType": "expression",
|
||||
"refId": "C",
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user