Skip to content

Commit

Permalink
[Logs UI] Reference a LogViewConfiguration as Log threshold rule param (
Browse files Browse the repository at this point in the history
#148897)

## 📓 Summary

Closes #120928 

These changes allow passing to the log threshold rule executor the
logViewId as a parameter, enabling the user to select what LogView the
alert should be associated with.
It also includes displaying the current LogView name in the rule
parameters and the log view reference is handled as a saved object
reference.

**_Disclaimer_**:
The `LogViewSwitcher` component will allow, with another implementation,
to switch between the available logViews with the support of the
multi-logView concept.
It currently renders a read-only expression to tell the user to which
logView is the new alert associated.

## 🧪 Testing

Navigate to Log UI, click on _Alerts and rules_ on the top-right menu,
then _Create rule_, you should see the current log view name in the
default expression. It is currently only a label, it'll be a dropdown
selector in future.

<img width="1502" alt="after"
src="https://user-images.githubusercontent.com/34506779/212651986-8c1b4a1d-356d-4966-aa30-f1af3171be72.png">

---------

Co-authored-by: Marco Antonio Ghiani <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored Jan 31, 2023
1 parent 830d9bd commit ae07320
Show file tree
Hide file tree
Showing 17 changed files with 494 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@

import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server';
import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server';
import { createEsoMigration, isEsQueryRuleType, pipeMigrations } from '../utils';
import { extractedSavedObjectParamReferenceNamePrefix } from '../../../rules_client/common/constants';
import {
createEsoMigration,
isEsQueryRuleType,
isLogThresholdRuleType,
pipeMigrations,
} from '../utils';
import { RawRule } from '../../../types';

function addGroupByToEsQueryRule(
Expand All @@ -31,9 +37,42 @@ function addGroupByToEsQueryRule(
return doc;
}

function addLogViewRefToLogThresholdRule(
doc: SavedObjectUnsanitizedDoc<RawRule>
): SavedObjectUnsanitizedDoc<RawRule> {
if (isLogThresholdRuleType(doc)) {
const references = doc.references ?? [];
const logViewId = 'log-view-reference-0';

return {
...doc,
attributes: {
...doc.attributes,
params: {
...doc.attributes.params,
logView: {
logViewId,
type: 'log-view-reference',
},
},
},
references: [
...references,
{
name: `${extractedSavedObjectParamReferenceNamePrefix}${logViewId}`,
type: 'infrastructure-monitoring-log-view',
id: 'default',
},
],
};
}

return doc;
}

export const getMigrations870 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) =>
createEsoMigration(
encryptedSavedObjects,
(doc): doc is SavedObjectUnsanitizedDoc<RawRule> => isEsQueryRuleType(doc),
pipeMigrations(addGroupByToEsQueryRule)
(doc: SavedObjectUnsanitizedDoc<RawRule>): doc is SavedObjectUnsanitizedDoc<RawRule> => true,
pipeMigrations(addGroupByToEsQueryRule, addLogViewRefToLogThresholdRule)
);
114 changes: 90 additions & 24 deletions x-pack/plugins/alerting/server/saved_objects/migrations/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2509,37 +2509,102 @@ describe('successful migrations', () => {
});

describe('8.7.0', () => {
test('migrates es_query rule params and adds group by fields', () => {
const migration870 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.7.0'];
const rule = getMockData(
{
params: { esQuery: '{ "query": "test-query" }', searchType: 'esQuery' },
alertTypeId: '.es-query',
},
true
);
const migratedAlert870 = migration870(rule, migrationContext);
describe('es_query rule', () => {
test('migrates es_query rule params and adds group by fields', () => {
const migration870 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[
'8.7.0'
];
const rule = getMockData(
{
params: { esQuery: '{ "query": "test-query" }', searchType: 'esQuery' },
alertTypeId: '.es-query',
},
true
);
const migratedAlert870 = migration870(rule, migrationContext);

expect(migratedAlert870.attributes.params).toEqual({
esQuery: '{ "query": "test-query" }',
searchType: 'esQuery',
aggType: 'count',
groupBy: 'all',
expect(migratedAlert870.attributes.params).toEqual({
esQuery: '{ "query": "test-query" }',
searchType: 'esQuery',
aggType: 'count',
groupBy: 'all',
});
});

test('does not migrate rule params if rule is not es query', () => {
const migration870 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[
'8.7.0'
];
const rule = getMockData(
{
params: { foo: true },
alertTypeId: '.not-es-query',
},
true
);
const migratedAlert870 = migration870(rule, migrationContext);

expect(migratedAlert870.attributes.params).toEqual({ foo: true });
});
});

test('does not migrate rule params if rule is not es query', () => {
const migration870 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.7.0'];
const rule = getMockData(
describe('log threshold rule', () => {
const logThresholdAlertTypeId = 'logs.alert.document.count';
const logViewId = 'log-view-reference-0';

const params = {
timeSize: 5,
timeUnit: 'm',
count: {
value: 75,
comparator: 'more than',
},
criteria: [
{
field: 'log.level',
comparator: 'equals',
value: 'error',
},
],
};

const logView = {
logViewId,
type: 'log-view-reference',
};

const references = [
{
params: { foo: true },
alertTypeId: '.not-es-query',
name: `param:${logViewId}`,
type: 'infrastructure-monitoring-log-view',
id: 'default',
},
true
);
const migratedAlert870 = migration870(rule, migrationContext);
];

test('should migrate and add the logView param and its reference', () => {
const migration870 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[
'8.7.0'
];
const rule = getMockData({ params, alertTypeId: logThresholdAlertTypeId }, true);
const migratedAlert870 = migration870(rule, migrationContext);

expect(migratedAlert870.attributes.params).toEqual({
...params,
logView,
});
expect(migratedAlert870.references).toEqual(references);
});

expect(migratedAlert870.attributes.params).toEqual({ foo: true });
test('should not migrate the rule if is not of type logs.alert.document.count', () => {
const migration870 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[
'8.7.0'
];
const rule = getMockData({ params, alertTypeId: `not-${logThresholdAlertTypeId}` }, true);
const migratedAlert870 = migration870(rule, migrationContext);

expect(migratedAlert870.attributes.params).toEqual(params);
expect(migratedAlert870.references).toEqual([]);
});
});
});

Expand Down Expand Up @@ -2891,6 +2956,7 @@ function getMockData(
],
...overwrites,
},
references: [],
updated_at: withSavedObjectUpdatedAt ? getUpdatedAt() : undefined,
id: uuidv4(),
type: 'alert',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ export const isDetectionEngineAADRuleType = (doc: SavedObjectUnsanitizedDoc<RawR
export const isSecuritySolutionLegacyNotification = (
doc: SavedObjectUnsanitizedDoc<RawRule>
): boolean => doc.attributes.alertTypeId === 'siem.notifications';

export const isLogThresholdRuleType = (doc: SavedObjectUnsanitizedDoc<RawRule>) =>
doc.attributes.alertTypeId === 'logs.alert.document.count';
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { i18n } from '@kbn/i18n';
import * as rt from 'io-ts';
import { logViewReferenceRT } from '../../../log_views';
import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types';

export const LOG_DOCUMENT_COUNT_RULE_TYPE_ID = 'logs.alert.document.count';
Expand Down Expand Up @@ -180,6 +181,7 @@ const RequiredRuleParamsRT = rt.type({
count: ThresholdRT,
timeUnit: timeUnitRT,
timeSize: timeSizeRT,
logView: logViewReferenceRT, // In future, this should be a union of logViewReferenceRT and inlineLogViewRT
});

const partialRequiredRuleParamsRT = rt.partial(RequiredRuleParamsRT.props);
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/infra/common/log_views/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,10 @@ export const logViewStatusRT = rt.strict({
index: logViewIndexStatusRT,
});
export type LogViewStatus = rt.TypeOf<typeof logViewStatusRT>;

export const logViewReferenceRT = rt.type({
logViewId: rt.string,
type: rt.literal('log-view-reference'),
});

export type LogViewReference = rt.TypeOf<typeof logViewReferenceRT>;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
ForLastExpression,
RuleTypeParamsExpressionProps,
} from '@kbn/triggers-actions-ui-plugin/public';
import { ResolvedLogViewField } from '../../../../../common/log_views';
import { LogViewReference, ResolvedLogViewField } from '../../../../../common/log_views';
import {
Comparator,
isOptimizableGroupedThreshold,
Expand All @@ -35,6 +35,7 @@ import { errorsRT } from '../../validation';
import { Criteria } from './criteria';
import { Threshold } from './threshold';
import { TypeSwitcher } from './type_switcher';
import { LogViewSwitcher } from './log_view_switcher';

export interface ExpressionCriteria {
field?: string;
Expand All @@ -53,6 +54,11 @@ const DEFAULT_BASE_EXPRESSION = {

const DEFAULT_FIELD = 'log.level';

const createLogViewReference = (logViewId: string): LogViewReference => ({
logViewId,
type: 'log-view-reference',
});

const createDefaultCriterion = (
availableFields: ResolvedLogViewField[],
value: ExpressionCriteria['value']
Expand All @@ -62,9 +68,11 @@ const createDefaultCriterion = (
: { field: undefined, comparator: undefined, value: undefined };

const createDefaultCountRuleParams = (
availableFields: ResolvedLogViewField[]
availableFields: ResolvedLogViewField[],
logView: LogViewReference
): PartialCountRuleParams => ({
...DEFAULT_BASE_EXPRESSION,
logView,
count: {
value: 75,
comparator: Comparator.GT,
Expand All @@ -73,9 +81,11 @@ const createDefaultCountRuleParams = (
});

const createDefaultRatioRuleParams = (
availableFields: ResolvedLogViewField[]
availableFields: ResolvedLogViewField[],
logView: LogViewReference
): PartialRatioRuleParams => ({
...DEFAULT_BASE_EXPRESSION,
logView,
count: {
value: 2,
comparator: Comparator.GT,
Expand Down Expand Up @@ -216,20 +226,24 @@ export const Editor: React.FC<RuleTypeParamsExpressionProps<PartialRuleParams, L
[setRuleParams]
);

const logViewReferemnce = useMemo(() => createLogViewReference(logViewId), [logViewId]);

const defaultCountAlertParams = useMemo(
() => createDefaultCountRuleParams(supportedFields),
[supportedFields]
() => createDefaultCountRuleParams(supportedFields, logViewReferemnce),
[supportedFields, logViewReferemnce]
);

const updateType = useCallback(
(type: ThresholdType) => {
const defaults =
type === 'count' ? defaultCountAlertParams : createDefaultRatioRuleParams(supportedFields);
type === 'count'
? defaultCountAlertParams
: createDefaultRatioRuleParams(supportedFields, logViewReferemnce);
// Reset properties that don't make sense switching from one context to the other
setRuleParams('count', defaults.count);
setRuleParams('criteria', defaults.criteria);
},
[defaultCountAlertParams, setRuleParams, supportedFields]
[defaultCountAlertParams, setRuleParams, supportedFields, logViewReferemnce]
);

useMount(() => {
Expand Down Expand Up @@ -268,6 +282,8 @@ export const Editor: React.FC<RuleTypeParamsExpressionProps<PartialRuleParams, L

return (
<>
{resolvedLogView && <LogViewSwitcher logView={resolvedLogView} />}

<TypeSwitcher criteria={ruleParams.criteria || []} updateType={updateType} />

{ruleParams.criteria && !isRatioRule(ruleParams.criteria) && criteriaComponent}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFlexItem, EuiFlexGroup, EuiExpression, EuiToolTip } from '@elastic/eui';
import { ResolvedLogView } from '../../../../../common/log_views';

const description = i18n.translate('xpack.infra.logs.alertFlyout.logViewDescription', {
defaultMessage: 'Log View',
});

interface LogViewSwitcherProps {
logView: ResolvedLogView;
}

/**
* TODO: this component is called LogViewSwitcher because it will allow,
* in a following implementation, to switch between the available logViews
* with the support of multi-logView concept.
* It currently renders a read-only expression to tell the user to which logView
* is the new alert associated with.
*/
export const LogViewSwitcher: React.FC<LogViewSwitcherProps> = ({ logView }) => {
return (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiToolTip content={logView.indices}>
<EuiExpression description={description} value={logView.name} />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,11 @@ const expectedNegativeFilterClauses = [
},
];

const baseRuleParams: Pick<RuleParams, 'count' | 'timeSize' | 'timeUnit'> = {
const baseRuleParams: Pick<RuleParams, 'count' | 'timeSize' | 'timeUnit' | 'logView'> = {
logView: {
logViewId: 'Default',
type: 'log-view-reference',
},
count: {
comparator: Comparator.GT,
value: 5,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,14 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) =>
};

const [, , { logViews }] = await libs.getStartServices();
const { indices, timestampField, runtimeMappings } = await logViews
.getClient(savedObjectsClient, scopedClusterClient.asCurrentUser)
.getResolvedLogView('default'); // TODO: move to params

try {
const validatedParams = decodeOrThrow(ruleParamsRT)(params);

const { indices, timestampField, runtimeMappings } = await logViews
.getClient(savedObjectsClient, scopedClusterClient.asCurrentUser)
.getResolvedLogView(validatedParams.logView.logViewId);

if (!isRatioRuleParams(validatedParams)) {
await executeAlert(
validatedParams,
Expand Down
Loading

0 comments on commit ae07320

Please sign in to comment.