mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Remove reducer when creating and instant (#94246)
* Remove reducer when using a non complex query that is instant * remove reducer when changing data source * Fix whenField * use DataSourceType instead of string literal * add reducer when using range * add tests * use an object for SimpleCondition refids identifiers * fix threshold expression to point to B after switching back to range * address pr review comments * refactor: extract reducer optimization to the reducer * fix tests * fix snapshot * rename constants
This commit is contained in:
parent
20837d3837
commit
76a3d79231
@ -17,6 +17,8 @@ import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOp
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { getInstantFromDataQuery } from '../../utils/rule-form';
|
||||
|
||||
import { AlertQueryOptions, EmptyQueryWrapper, QueryWrapper } from './QueryWrapper';
|
||||
import { errorFromCurrentCondition, errorFromPreviewData, getThresholdsForQueries } from './util';
|
||||
|
||||
@ -234,6 +236,7 @@ function copyModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit
|
||||
}
|
||||
|
||||
function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit<AlertQuery, 'datasource'> {
|
||||
const isInstant = getInstantFromDataQuery(item.model, settings.type);
|
||||
return {
|
||||
refId: item.refId,
|
||||
relativeTimeRange: item.relativeTimeRange,
|
||||
@ -243,6 +246,7 @@ function newModel(item: AlertQuery, settings: DataSourceInstanceSettings): Omit<
|
||||
refId: item.refId,
|
||||
hide: false,
|
||||
datasource: getDataSourceRef(settings),
|
||||
instant: isInstant,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -54,13 +54,7 @@ import { RuleEditorSection } from '../RuleEditorSection';
|
||||
import { errorFromCurrentCondition, errorFromPreviewData, findRenamedDataQueryReferences, refIdExists } from '../util';
|
||||
|
||||
import { CloudDataSourceSelector } from './CloudDataSourceSelector';
|
||||
import {
|
||||
getSimpleConditionFromExpressions,
|
||||
SIMPLE_CONDITION_QUERY_ID,
|
||||
SIMPLE_CONDITION_REDUCER_ID,
|
||||
SIMPLE_CONDITION_THRESHOLD_ID,
|
||||
SimpleConditionEditor,
|
||||
} from './SimpleCondition';
|
||||
import { getSimpleConditionFromExpressions, SimpleConditionEditor, SimpleConditionIdentifier } from './SimpleCondition';
|
||||
import { SmartAlertTypeDetector } from './SmartAlertTypeDetector';
|
||||
import { DESCRIPTIONS } from './descriptions';
|
||||
import {
|
||||
@ -68,6 +62,7 @@ import {
|
||||
addNewDataQuery,
|
||||
addNewExpression,
|
||||
duplicateQuery,
|
||||
optimizeReduceExpression,
|
||||
queriesAndExpressionsReducer,
|
||||
removeExpression,
|
||||
removeExpressions,
|
||||
@ -90,19 +85,21 @@ export function areQueriesTransformableToSimpleCondition(
|
||||
if (dataQueries.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const singleReduceExpressionInInstantQuery =
|
||||
'instant' in dataQueries[0].model && dataQueries[0].model.instant && expressionQueries.length === 1;
|
||||
|
||||
if (expressionQueries.length !== 2) {
|
||||
if (expressionQueries.length !== 2 && !singleReduceExpressionInInstantQuery) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const query = dataQueries[0];
|
||||
|
||||
if (query.refId !== SIMPLE_CONDITION_QUERY_ID) {
|
||||
if (query.refId !== SimpleConditionIdentifier.queryId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const reduceExpressionIndex = expressionQueries.findIndex(
|
||||
(query) => query.model.type === ExpressionQueryType.reduce && query.refId === SIMPLE_CONDITION_REDUCER_ID
|
||||
(query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId
|
||||
);
|
||||
const reduceExpression = expressionQueries.at(reduceExpressionIndex);
|
||||
const reduceOk =
|
||||
@ -112,13 +109,16 @@ export function areQueriesTransformableToSimpleCondition(
|
||||
reduceExpression.model.settings?.mode === undefined);
|
||||
|
||||
const thresholdExpressionIndex = expressionQueries.findIndex(
|
||||
(query) => query.model.type === ExpressionQueryType.threshold && query.refId === SIMPLE_CONDITION_THRESHOLD_ID
|
||||
(query) =>
|
||||
query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId
|
||||
);
|
||||
const thresholdExpression = expressionQueries.at(thresholdExpressionIndex);
|
||||
const conditions = thresholdExpression?.model.conditions ?? [];
|
||||
const thresholdOk =
|
||||
thresholdExpression && thresholdExpressionIndex === 1 && conditions[0]?.unloadEvaluator === undefined;
|
||||
return Boolean(reduceOk) && Boolean(thresholdOk);
|
||||
const thresholdIndexOk = singleReduceExpressionInInstantQuery
|
||||
? thresholdExpressionIndex === 0
|
||||
: thresholdExpressionIndex === 1;
|
||||
const thresholdOk = thresholdExpression && thresholdIndexOk && conditions[0]?.unloadEvaluator === undefined;
|
||||
return (Boolean(reduceOk) || Boolean(singleReduceExpressionInInstantQuery)) && Boolean(thresholdOk);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@ -200,8 +200,8 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P
|
||||
}
|
||||
// we need to be sure the condition is set once we switch to simple mode
|
||||
if (!isAdvancedMode) {
|
||||
setValue('condition', SIMPLE_CONDITION_THRESHOLD_ID);
|
||||
runQueries(getValues('queries'), SIMPLE_CONDITION_THRESHOLD_ID);
|
||||
setValue('condition', SimpleConditionIdentifier.thresholdId);
|
||||
runQueries(getValues('queries'), SimpleConditionIdentifier.thresholdId);
|
||||
} else {
|
||||
runQueries(getValues('queries'), condition || (getValues('condition') ?? ''));
|
||||
}
|
||||
@ -285,6 +285,12 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P
|
||||
setValue('queries', [...updatedQueries, ...expressionQueries], { shouldValidate: false });
|
||||
updateExpressionAndDatasource(updatedQueries);
|
||||
|
||||
// we only remove or add the reducer(optimize reducer) expression when creating a new alert.
|
||||
// When editing an alert, we assume the user wants to manually adjust expressions and queries for more control and customization.
|
||||
if (!editingExistingRule) {
|
||||
dispatch(optimizeReduceExpression({ updatedQueries, expressionQueries }));
|
||||
}
|
||||
|
||||
dispatch(setDataQueries(updatedQueries));
|
||||
dispatch(updateExpressionTimeRange());
|
||||
|
||||
@ -294,7 +300,7 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P
|
||||
dispatch(rewireExpressions({ oldRefId, newRefId }));
|
||||
}
|
||||
},
|
||||
[queries, updateExpressionAndDatasource, getValues, setValue]
|
||||
[queries, updateExpressionAndDatasource, getValues, setValue, editingExistingRule]
|
||||
);
|
||||
|
||||
const onChangeRecordingRulesQueries = useCallback(
|
||||
|
@ -17,12 +17,13 @@ import { ExpressionResult } from '../../expressions/Expression';
|
||||
|
||||
import { updateExpression } from './reducer';
|
||||
|
||||
export const SIMPLE_CONDITION_QUERY_ID = 'A';
|
||||
export const SIMPLE_CONDITION_REDUCER_ID = 'B';
|
||||
export const SIMPLE_CONDITION_THRESHOLD_ID = 'C';
|
||||
|
||||
export const SimpleConditionIdentifier = {
|
||||
queryId: 'A',
|
||||
reducerId: 'B',
|
||||
thresholdId: 'C',
|
||||
} as const;
|
||||
export interface SimpleCondition {
|
||||
whenField: string;
|
||||
whenField?: string;
|
||||
evaluator: {
|
||||
params: number[];
|
||||
type: EvalFunction;
|
||||
@ -106,15 +107,17 @@ export const SimpleConditionEditor = ({
|
||||
</Text>
|
||||
</header>
|
||||
<InlineFieldRow className={styles.condition.container}>
|
||||
<InlineField label="WHEN">
|
||||
<Select
|
||||
options={reducerTypes}
|
||||
value={reducerTypes.find((o) => o.value === simpleCondition.whenField)}
|
||||
onChange={onReducerTypeChange}
|
||||
width={20}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="OF QUERY">
|
||||
{simpleCondition.whenField && (
|
||||
<InlineField label="WHEN">
|
||||
<Select
|
||||
options={reducerTypes}
|
||||
value={reducerTypes.find((o) => o.value === simpleCondition.whenField)}
|
||||
onChange={onReducerTypeChange}
|
||||
width={20}
|
||||
/>
|
||||
</InlineField>
|
||||
)}
|
||||
<InlineField label={simpleCondition.whenField ? 'OF QUERY' : 'WHEN QUERY'}>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<ThresholdSelect onChange={onEvalFunctionChange} value={thresholdFunction} />
|
||||
{isRange ? (
|
||||
@ -156,7 +159,8 @@ function updateReduceExpression(
|
||||
dispatch: Dispatch<UnknownAction>
|
||||
) {
|
||||
const reduceExpression = expressionQueriesList.find(
|
||||
(query) => query.model.type === ExpressionQueryType.reduce && query.model.refId === SIMPLE_CONDITION_REDUCER_ID
|
||||
(query) =>
|
||||
query.model.type === ExpressionQueryType.reduce && query.model.refId === SimpleConditionIdentifier.reducerId
|
||||
);
|
||||
|
||||
const newReduceExpression = reduceExpression
|
||||
@ -176,7 +180,8 @@ function updateThresholdFunction(
|
||||
dispatch: Dispatch<UnknownAction>
|
||||
) {
|
||||
const thresholdExpression = expressionQueriesList.find(
|
||||
(query) => query.model.type === ExpressionQueryType.threshold && query.model.refId === SIMPLE_CONDITION_THRESHOLD_ID
|
||||
(query) =>
|
||||
query.model.type === ExpressionQueryType.threshold && query.model.refId === SimpleConditionIdentifier.thresholdId
|
||||
);
|
||||
|
||||
const newThresholdExpression = produce(thresholdExpression, (draft) => {
|
||||
@ -194,7 +199,8 @@ function updateThresholdValue(
|
||||
dispatch: Dispatch<UnknownAction>
|
||||
) {
|
||||
const thresholdExpression = expressionQueriesList.find(
|
||||
(query) => query.model.type === ExpressionQueryType.threshold && query.model.refId === SIMPLE_CONDITION_THRESHOLD_ID
|
||||
(query) =>
|
||||
query.model.type === ExpressionQueryType.threshold && query.model.refId === SimpleConditionIdentifier.thresholdId
|
||||
);
|
||||
|
||||
const newThresholdExpression = produce(thresholdExpression, (draft) => {
|
||||
@ -207,13 +213,14 @@ function updateThresholdValue(
|
||||
|
||||
export function getSimpleConditionFromExpressions(expressions: Array<AlertQuery<ExpressionQuery>>): SimpleCondition {
|
||||
const reduceExpression = expressions.find(
|
||||
(query) => query.model.type === ExpressionQueryType.reduce && query.refId === SIMPLE_CONDITION_REDUCER_ID
|
||||
(query) => query.model.type === ExpressionQueryType.reduce && query.refId === SimpleConditionIdentifier.reducerId
|
||||
);
|
||||
const thresholdExpression = expressions.find(
|
||||
(query) => query.model.type === ExpressionQueryType.threshold && query.refId === SIMPLE_CONDITION_THRESHOLD_ID
|
||||
(query) =>
|
||||
query.model.type === ExpressionQueryType.threshold && query.refId === SimpleConditionIdentifier.thresholdId
|
||||
);
|
||||
const conditionsFromThreshold = thresholdExpression?.model.conditions ?? [];
|
||||
const whenField = reduceExpression?.model.reducer ?? ReducerID.last;
|
||||
const whenField = reduceExpression?.model.reducer;
|
||||
const params = conditionsFromThreshold[0]?.evaluator?.params
|
||||
? [...conditionsFromThreshold[0]?.evaluator?.params]
|
||||
: [0];
|
||||
|
@ -1,5 +1,3 @@
|
||||
// QueryAndExpressionsStep.test.tsx
|
||||
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { EvalFunction } from 'app/features/alerting/state/alertDef';
|
||||
@ -24,7 +22,7 @@ describe('areQueriesTransformableToSimpleCondition', () => {
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the dataQuery refId does not match SIMPLE_CONDITION_QUERY_ID', () => {
|
||||
it('should return false if the dataQuery refId does not match SimpleConditionIdentifier.queryId', () => {
|
||||
const dataQueries: Array<AlertQuery<AlertDataQuery | ExpressionQuery>> = [
|
||||
{ refId: 'notSimpleCondition', datasourceUid: 'abc123', queryType: '', model: { refId: 'notSimpleCondition' } },
|
||||
];
|
||||
|
@ -86,6 +86,70 @@ exports[`Query and expressions reducer should add query 1`] = `
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Query and expressions reducer should add reduce expression if there is no reduce expression and the query is not instant 1`] = `
|
||||
{
|
||||
"queries": [
|
||||
{
|
||||
"datasourceUid": "abc123",
|
||||
"model": {
|
||||
"instant": false,
|
||||
"refId": "A",
|
||||
},
|
||||
"queryType": "query",
|
||||
"refId": "A",
|
||||
},
|
||||
{
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"conditions": [
|
||||
{
|
||||
"evaluator": {
|
||||
"params": [
|
||||
0,
|
||||
0,
|
||||
],
|
||||
"type": "gt",
|
||||
},
|
||||
"operator": {
|
||||
"type": "and",
|
||||
},
|
||||
"query": {
|
||||
"params": [],
|
||||
},
|
||||
"reducer": {
|
||||
"params": [],
|
||||
"type": "avg",
|
||||
},
|
||||
"type": "query",
|
||||
},
|
||||
],
|
||||
"datasource": {
|
||||
"name": "Expression",
|
||||
"type": "__expr__",
|
||||
"uid": "__expr__",
|
||||
},
|
||||
"expression": "A",
|
||||
"reducer": "last",
|
||||
"refId": "B",
|
||||
"type": "reduce",
|
||||
},
|
||||
"queryType": "expression",
|
||||
"refId": "B",
|
||||
},
|
||||
{
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "B",
|
||||
"refId": "C",
|
||||
"type": "threshold",
|
||||
},
|
||||
"queryType": "expression",
|
||||
"refId": "C",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Query and expressions reducer should duplicate query 1`] = `
|
||||
{
|
||||
"queries": [
|
||||
@ -129,6 +193,31 @@ exports[`Query and expressions reducer should remove an expression or alert quer
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Query and expressions reducer should remove first reducer 1`] = `
|
||||
{
|
||||
"queries": [
|
||||
{
|
||||
"datasourceUid": "abc123",
|
||||
"model": {
|
||||
"refId": "A",
|
||||
},
|
||||
"queryType": "query",
|
||||
"refId": "A",
|
||||
},
|
||||
{
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "A",
|
||||
"refId": "C",
|
||||
"type": "threshold",
|
||||
},
|
||||
"queryType": "expression",
|
||||
"refId": "C",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Query and expressions reducer should rewire expressions 1`] = `
|
||||
{
|
||||
"queries": [
|
||||
|
@ -1,14 +1,21 @@
|
||||
import { getDefaultRelativeTimeRange, RelativeTimeRange } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime/src/services/__mocks__/dataSourceSrv';
|
||||
import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
||||
import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types';
|
||||
import {
|
||||
ExpressionDatasourceUID,
|
||||
ExpressionQuery,
|
||||
ExpressionQueryType,
|
||||
ReducerMode,
|
||||
} from 'app/features/expressions/types';
|
||||
import { defaultCondition } from 'app/features/expressions/utils/expressionTypes';
|
||||
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { SimpleConditionIdentifier } from './SimpleCondition';
|
||||
import {
|
||||
addNewDataQuery,
|
||||
addNewExpression,
|
||||
duplicateQuery,
|
||||
optimizeReduceExpression,
|
||||
queriesAndExpressionsReducer,
|
||||
QueriesAndExpressionsState,
|
||||
removeExpression,
|
||||
@ -20,6 +27,26 @@ import {
|
||||
updateExpressionType,
|
||||
} from './reducer';
|
||||
|
||||
const reduceExpression: AlertQuery<ExpressionQuery> = {
|
||||
refId: SimpleConditionIdentifier.reducerId,
|
||||
queryType: 'expression',
|
||||
datasourceUid: '__expr__',
|
||||
model: {
|
||||
type: ExpressionQueryType.reduce,
|
||||
refId: SimpleConditionIdentifier.reducerId,
|
||||
settings: { mode: ReducerMode.Strict },
|
||||
},
|
||||
};
|
||||
const thresholdExpression: AlertQuery<ExpressionQuery> = {
|
||||
refId: SimpleConditionIdentifier.thresholdId,
|
||||
queryType: 'expression',
|
||||
datasourceUid: '__expr__',
|
||||
model: {
|
||||
type: ExpressionQueryType.threshold,
|
||||
refId: SimpleConditionIdentifier.thresholdId,
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getDataSourceSrv: getDataSourceSrv,
|
||||
@ -358,4 +385,70 @@ describe('Query and expressions reducer', () => {
|
||||
|
||||
expect(newState).toMatchSnapshot();
|
||||
});
|
||||
it('should remove first reducer', () => {
|
||||
const initialState: QueriesAndExpressionsState = {
|
||||
queries: [alertQuery, reduceExpression, thresholdExpression],
|
||||
};
|
||||
|
||||
const newState = queriesAndExpressionsReducer(
|
||||
initialState,
|
||||
optimizeReduceExpression({
|
||||
updatedQueries: [alertQuery],
|
||||
expressionQueries: [reduceExpression, thresholdExpression],
|
||||
})
|
||||
);
|
||||
expect(newState).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should not remove first reducer if reducer is not the first expression', () => {
|
||||
const initialState: QueriesAndExpressionsState = {
|
||||
queries: [alertQuery, thresholdExpression, reduceExpression],
|
||||
};
|
||||
|
||||
const newState = queriesAndExpressionsReducer(
|
||||
initialState,
|
||||
optimizeReduceExpression({
|
||||
updatedQueries: [alertQuery],
|
||||
expressionQueries: [thresholdExpression, reduceExpression],
|
||||
})
|
||||
);
|
||||
expect(newState).toEqual(initialState);
|
||||
});
|
||||
|
||||
it('should not remove first reducer if reducer is not the second query', () => {
|
||||
const initialState: QueriesAndExpressionsState = {
|
||||
queries: [alertQuery, alertQuery, reduceExpression, thresholdExpression],
|
||||
};
|
||||
|
||||
const newState = queriesAndExpressionsReducer(
|
||||
initialState,
|
||||
optimizeReduceExpression({
|
||||
updatedQueries: [alertQuery, alertQuery],
|
||||
expressionQueries: [reduceExpression, thresholdExpression],
|
||||
})
|
||||
);
|
||||
expect(newState).toEqual(initialState);
|
||||
});
|
||||
|
||||
it('should add reduce expression if there is no reduce expression and the query is not instant', () => {
|
||||
const alertQuery: AlertQuery = {
|
||||
refId: 'A',
|
||||
queryType: 'query',
|
||||
datasourceUid: 'abc123',
|
||||
model: {
|
||||
refId: 'A',
|
||||
instant: false,
|
||||
},
|
||||
};
|
||||
|
||||
const initialState: QueriesAndExpressionsState = {
|
||||
queries: [alertQuery, thresholdExpression],
|
||||
};
|
||||
|
||||
const newState = queriesAndExpressionsReducer(
|
||||
initialState,
|
||||
optimizeReduceExpression({ updatedQueries: [alertQuery], expressionQueries: [thresholdExpression] })
|
||||
);
|
||||
expect(newState).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@ -6,8 +6,10 @@ import {
|
||||
getDefaultRelativeTimeRange,
|
||||
getNextRefId,
|
||||
rangeUtil,
|
||||
ReducerID,
|
||||
RelativeTimeRange,
|
||||
} from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
||||
import { isExpressionQuery } from 'app/features/expressions/guards';
|
||||
import { ExpressionDatasourceUID, ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/types';
|
||||
@ -15,11 +17,13 @@ import { defaultCondition } from 'app/features/expressions/utils/expressionTypes
|
||||
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { logError } from '../../../Analytics';
|
||||
import { getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource';
|
||||
import { getDefaultQueries } from '../../../utils/rule-form';
|
||||
import { DataSourceType, getDefaultOrFirstCompatibleDataSource } from '../../../utils/datasource';
|
||||
import { getDefaultQueries, getInstantFromDataQuery } from '../../../utils/rule-form';
|
||||
import { createDagFromQueries, getOriginOfRefId } from '../dag';
|
||||
import { queriesWithUpdatedReferences, refIdExists } from '../util';
|
||||
|
||||
import { SimpleConditionIdentifier } from './SimpleCondition';
|
||||
|
||||
export interface QueriesAndExpressionsState {
|
||||
queries: AlertQuery[];
|
||||
}
|
||||
@ -60,7 +64,9 @@ export const updateMaxDataPoints = createAction<{ refId: string; maxDataPoints:
|
||||
export const updateMinInterval = createAction<{ refId: string; minInterval: string }>('updateMinInterval');
|
||||
|
||||
export const resetToSimpleCondition = createAction('resetToSimpleCondition');
|
||||
|
||||
export const optimizeReduceExpression = createAction<{ updatedQueries: AlertQuery[]; expressionQueries: AlertQuery[] }>(
|
||||
'optimizeReduceExpression'
|
||||
);
|
||||
export const setRecordingRulesQueries = createAction<{ recordingRuleQueries: AlertQuery[]; expression: string }>(
|
||||
'setRecordingRulesQueries'
|
||||
);
|
||||
@ -225,6 +231,71 @@ export const queriesAndExpressionsReducer = createReducer(initialState, (builder
|
||||
.addCase(rewireExpressions, (state, { payload }) => {
|
||||
state.queries = queriesWithUpdatedReferences(state.queries, payload.oldRefId, payload.newRefId);
|
||||
})
|
||||
.addCase(optimizeReduceExpression, (state, { payload }) => {
|
||||
const { updatedQueries, expressionQueries } = payload;
|
||||
|
||||
if (updatedQueries.length !== 1) {
|
||||
// we only optimize when we have one data query
|
||||
return;
|
||||
}
|
||||
|
||||
//sometimes we dont have data source in the model yet
|
||||
const getDataSourceSettingsForFirstQuery = getDataSourceSrv().getInstanceSettings(
|
||||
updatedQueries[0].datasourceUid
|
||||
);
|
||||
|
||||
if (!getDataSourceSettingsForFirstQuery) {
|
||||
return;
|
||||
}
|
||||
const type = getDataSourceSettingsForFirstQuery?.type;
|
||||
|
||||
const firstQueryIsPromOrLoki = type === DataSourceType.Prometheus || type === DataSourceType.Loki;
|
||||
|
||||
const isInstant = getInstantFromDataQuery(updatedQueries[0].model, type);
|
||||
|
||||
const shouldRemoveReducer =
|
||||
firstQueryIsPromOrLoki && updatedQueries.length === 1 && isInstant && expressionQueries.length === 2;
|
||||
|
||||
const onlyOneExpressionNotReducer =
|
||||
expressionQueries.length === 1 &&
|
||||
'type' in expressionQueries[0].model &&
|
||||
expressionQueries[0].model.type !== ExpressionQueryType.reduce;
|
||||
|
||||
// we only add the reduce expression if we have one data query and one expression query. For other cases we don't do anything,
|
||||
// and let the user add the reducer manually.
|
||||
const shouldAddReduceExpression =
|
||||
firstQueryIsPromOrLoki && updatedQueries.length === 1 && !isInstant && onlyOneExpressionNotReducer;
|
||||
|
||||
if (shouldRemoveReducer) {
|
||||
const reduceExpressionIndex = state.queries.findIndex(
|
||||
(query) => isExpressionQuery(query.model) && query.model.type === ExpressionQueryType.reduce
|
||||
);
|
||||
|
||||
if (reduceExpressionIndex === 1) {
|
||||
// means the reduce expression is the second query
|
||||
state.queries.splice(reduceExpressionIndex, 1);
|
||||
state.queries[1].model.expression = SimpleConditionIdentifier.queryId;
|
||||
}
|
||||
}
|
||||
if (shouldAddReduceExpression) {
|
||||
// add reducer to the second position
|
||||
// we only update the refid and the model to point to the reducer expression
|
||||
state.queries[1].model.expression = SimpleConditionIdentifier.reducerId;
|
||||
// insert in second position the reducer expression
|
||||
state.queries.splice(1, 0, {
|
||||
datasourceUid: ExpressionDatasourceUID,
|
||||
model: expressionDatasource.newQuery({
|
||||
type: ExpressionQueryType.reduce,
|
||||
reducer: ReducerID.last,
|
||||
conditions: [{ ...defaultCondition, query: { params: [] } }],
|
||||
expression: SimpleConditionIdentifier.queryId,
|
||||
refId: SimpleConditionIdentifier.reducerId,
|
||||
}),
|
||||
refId: SimpleConditionIdentifier.reducerId,
|
||||
queryType: 'expression',
|
||||
});
|
||||
}
|
||||
})
|
||||
.addCase(updateExpressionType, (state, action) => {
|
||||
state.queries = state.queries.map((query) => {
|
||||
return query.refId === action.payload.refId
|
||||
|
@ -64,11 +64,7 @@ import {
|
||||
|
||||
import { DashboardSearchItem, DashboardSearchItemType } from '../../search/types';
|
||||
|
||||
import {
|
||||
SIMPLE_CONDITION_QUERY_ID,
|
||||
SIMPLE_CONDITION_REDUCER_ID,
|
||||
SIMPLE_CONDITION_THRESHOLD_ID,
|
||||
} from './components/rule-editor/query-and-alert-condition/SimpleCondition';
|
||||
import { SimpleConditionIdentifier } from './components/rule-editor/query-and-alert-condition/SimpleCondition';
|
||||
import { parsePromQLStyleMatcherLooseSafe } from './utils/matchers';
|
||||
|
||||
let nextDataSourceId = 1;
|
||||
@ -855,29 +851,29 @@ export function mockDashboardDto(
|
||||
}
|
||||
|
||||
export const dataQuery: AlertQuery<AlertDataQuery | ExpressionQuery> = {
|
||||
refId: SIMPLE_CONDITION_QUERY_ID,
|
||||
refId: SimpleConditionIdentifier.queryId,
|
||||
datasourceUid: 'abc123',
|
||||
queryType: '',
|
||||
model: { refId: SIMPLE_CONDITION_QUERY_ID },
|
||||
model: { refId: SimpleConditionIdentifier.queryId },
|
||||
};
|
||||
|
||||
export const reduceExpression: AlertQuery<ExpressionQuery> = {
|
||||
refId: SIMPLE_CONDITION_REDUCER_ID,
|
||||
refId: SimpleConditionIdentifier.reducerId,
|
||||
queryType: 'expression',
|
||||
datasourceUid: '__expr__',
|
||||
model: {
|
||||
type: ExpressionQueryType.reduce,
|
||||
refId: SIMPLE_CONDITION_REDUCER_ID,
|
||||
refId: SimpleConditionIdentifier.reducerId,
|
||||
settings: { mode: ReducerMode.Strict },
|
||||
reducer: ReducerID.last,
|
||||
},
|
||||
};
|
||||
export const thresholdExpression: AlertQuery<ExpressionQuery> = {
|
||||
refId: SIMPLE_CONDITION_THRESHOLD_ID,
|
||||
refId: SimpleConditionIdentifier.thresholdId,
|
||||
queryType: 'expression',
|
||||
datasourceUid: '__expr__',
|
||||
model: {
|
||||
type: ExpressionQueryType.threshold,
|
||||
refId: SIMPLE_CONDITION_THRESHOLD_ID,
|
||||
refId: SimpleConditionIdentifier.thresholdId,
|
||||
},
|
||||
};
|
||||
|
@ -53,7 +53,12 @@ import {
|
||||
|
||||
import { getRulesAccess } from './access-control';
|
||||
import { Annotation, defaultAnnotations } from './constants';
|
||||
import { getDefaultOrFirstCompatibleDataSource, GRAFANA_RULES_SOURCE_NAME, isGrafanaRulesSource } from './datasource';
|
||||
import {
|
||||
DataSourceType,
|
||||
getDefaultOrFirstCompatibleDataSource,
|
||||
GRAFANA_RULES_SOURCE_NAME,
|
||||
isGrafanaRulesSource,
|
||||
} from './datasource';
|
||||
import { arrayToRecord, recordToArray } from './misc';
|
||||
import {
|
||||
isAlertingRulerRule,
|
||||
@ -499,7 +504,6 @@ export function recordingRulerRuleToRuleForm(
|
||||
|
||||
export const getDefaultQueries = (isRecordingRule = false): AlertQuery[] => {
|
||||
const dataSource = getDefaultOrFirstCompatibleDataSource();
|
||||
|
||||
if (!dataSource) {
|
||||
const expressions = isRecordingRule ? getDefaultExpressionsForRecording('A') : getDefaultExpressions('A', 'B');
|
||||
return [...expressions];
|
||||
@ -507,6 +511,7 @@ export const getDefaultQueries = (isRecordingRule = false): AlertQuery[] => {
|
||||
const relativeTimeRange = getDefaultRelativeTimeRange();
|
||||
|
||||
const expressions = isRecordingRule ? getDefaultExpressionsForRecording('B') : getDefaultExpressions('B', 'C');
|
||||
const isLokiOrPrometheus = dataSource?.type === DataSourceType.Prometheus || dataSource?.type === DataSourceType.Loki;
|
||||
return [
|
||||
{
|
||||
refId: 'A',
|
||||
@ -515,6 +520,7 @@ export const getDefaultQueries = (isRecordingRule = false): AlertQuery[] => {
|
||||
relativeTimeRange,
|
||||
model: {
|
||||
refId: 'A',
|
||||
instant: isLokiOrPrometheus ? true : undefined,
|
||||
},
|
||||
},
|
||||
...expressions,
|
||||
@ -899,3 +905,24 @@ export const ignoreHiddenQueries = (ruleDefinition: RuleFormValues): RuleFormVal
|
||||
export function formValuesFromExistingRule(rule: RuleWithLocation<RulerRuleDTO>) {
|
||||
return ignoreHiddenQueries(rulerRuleToFormValues(rule));
|
||||
}
|
||||
|
||||
export function getInstantFromDataQuery(model: AlertDataQuery, type: string): boolean | undefined {
|
||||
// if the datasource is not prometheus or loki, instant is defined in the model or defaults to undefined
|
||||
if (type !== DataSourceType.Prometheus && type !== DataSourceType.Loki) {
|
||||
if ('instant' in model) {
|
||||
return model.instant;
|
||||
} else {
|
||||
if ('queryType' in model) {
|
||||
return model.queryType === 'instant';
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if the datasource is prometheus or loki, instant is defined in the model, or defaults to true
|
||||
const isInstantForPrometheus = 'instant' in model && model.instant !== undefined ? model.instant : true;
|
||||
const isInstantForLoki = 'queryType' in model && model.queryType !== undefined ? model.queryType === 'instant' : true;
|
||||
|
||||
const isInstant = type === DataSourceType.Prometheus ? isInstantForPrometheus : isInstantForLoki;
|
||||
return isInstant;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user