Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AO] - Add scaffolding and the main chart to the Logs threshold Alert Details page #153081

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,8 @@ export const isOptimizableGroupedThreshold = (
return false;
}
};

export interface ExecutionTimeRange {
gte?: number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is not used on the server side, it's used only if we define it for the Logs chart.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maryam-saeidi, only the Alert Details page (frontend) will fill up the gte, which is sent to the server side.
Something to mention is ExecutionTimeRange is not only used to get the chart data (where we could use gte). But also in the log threshold executor (where gte is not used)

lte: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ export const getLogAlertsChartPreviewDataRequestPayloadRT = rt.type({
logView: persistedLogViewReferenceRT,
alertParams: getLogAlertsChartPreviewDataAlertParamsSubsetRT,
buckets: rt.number,
executionTimeRange: rt.union([
rt.undefined,
rt.type({
gte: rt.number,
lte: rt.number,
}),
]),
}),
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { ALERT_DURATION, ALERT_END } from '@kbn/rule-data-utils';
import moment from 'moment';
import React from 'react';
import { type PartialCriterion } from '../../../../../common/alerting/logs/log_threshold';
import { CriterionPreview } from '../expression_editor/criterion_preview_chart';
import { AlertDetailsAppSectionProps } from './types';

const AlertDetailsAppSection = ({ rule, alert }: AlertDetailsAppSectionProps) => {
const ruleWindowSizeMS = moment
.duration(rule.params.timeSize, rule.params.timeUnit)
.asMilliseconds();
const alertDurationMS = alert.fields[ALERT_DURATION]! / 1000;
const TWENTY_TIMES_RULE_WINDOW_MS = 20 * ruleWindowSizeMS;
/**
* This is part or the requirements (RFC).
* If the alert is less than 20 units of `FOR THE LAST <x> <units>` then we should draw a time range of 20 units.
* IE. The user set "FOR THE LAST 5 minutes" at a minimum we should show 100 minutes.
*/
const rangeFrom =
alertDurationMS < TWENTY_TIMES_RULE_WINDOW_MS
? Number(moment(alert.start).subtract(TWENTY_TIMES_RULE_WINDOW_MS, 'millisecond').format('x'))
: Number(moment(alert.start).subtract(ruleWindowSizeMS, 'millisecond').format('x'));

const rangeTo = alert.active
? Date.now()
: Number(moment(alert.fields[ALERT_END]).add(ruleWindowSizeMS, 'millisecond').format('x'));

return (
// Create a chart per-criteria
<EuiFlexGroup>
{rule.params.criteria.map((criteria) => {
fkanout marked this conversation as resolved.
Show resolved Hide resolved
const chartCriterion = criteria as PartialCriterion;
return (
<EuiFlexItem>
<CriterionPreview
key={chartCriterion.field}
ruleParams={rule.params}
sourceId={rule.params.logView.logViewId}
chartCriterion={chartCriterion}
showThreshold={true}
executionTimeRange={{ gte: rangeFrom, lte: rangeTo }}
/>
</EuiFlexItem>
);
})}
</EuiFlexGroup>
);
};
// eslint-disable-next-line import/no-default-export
export default AlertDetailsAppSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 { Rule } from '@kbn/alerting-plugin/common';
import { TopAlert } from '@kbn/observability-plugin/public';
import { PartialRuleParams } from '../../../../../common/alerting/logs/log_threshold';

export interface AlertDetailsAppSectionProps {
rule: Rule<PartialRuleParams>;
alert: TopAlert;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ExecutionTimeRange } from '../../../../types';
import {
ChartContainer,
LoadingState,
Expand Down Expand Up @@ -56,13 +57,15 @@ interface Props {
chartCriterion: Partial<Criterion>;
sourceId: string;
showThreshold: boolean;
executionTimeRange?: ExecutionTimeRange;
}

export const CriterionPreview: React.FC<Props> = ({
ruleParams,
chartCriterion,
sourceId,
showThreshold,
executionTimeRange,
}) => {
const chartAlertParams: GetLogAlertsChartPreviewDataAlertParamsSubset | null = useMemo(() => {
const { field, comparator, value } = chartCriterion;
Expand Down Expand Up @@ -106,6 +109,7 @@ export const CriterionPreview: React.FC<Props> = ({
threshold={ruleParams.count}
chartAlertParams={chartAlertParams}
showThreshold={showThreshold}
executionTimeRange={executionTimeRange}
/>
);
};
Expand All @@ -116,6 +120,7 @@ interface ChartProps {
threshold?: Threshold;
chartAlertParams: GetLogAlertsChartPreviewDataAlertParamsSubset;
showThreshold: boolean;
executionTimeRange?: ExecutionTimeRange;
}

const CriterionPreviewChart: React.FC<ChartProps> = ({
Expand All @@ -124,6 +129,7 @@ const CriterionPreviewChart: React.FC<ChartProps> = ({
threshold,
chartAlertParams,
showThreshold,
executionTimeRange,
}) => {
const { uiSettings } = useKibana().services;
const isDarkMode = uiSettings?.get('theme:darkMode') || false;
Expand All @@ -138,6 +144,7 @@ const CriterionPreviewChart: React.FC<ChartProps> = ({
sourceId,
ruleParams: chartAlertParams,
buckets,
executionTimeRange,
});

useDebounce(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { useState, useMemo } from 'react';
import { HttpHandler } from '@kbn/core/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ExecutionTimeRange } from '../../../../../types';
import { useTrackedPromise } from '../../../../../utils/use_tracked_promise';
import {
GetLogAlertsChartPreviewDataSuccessResponsePayload,
Expand All @@ -22,11 +23,16 @@ interface Options {
sourceId: string;
ruleParams: GetLogAlertsChartPreviewDataAlertParamsSubset;
buckets: number;
executionTimeRange?: ExecutionTimeRange;
}

export const useChartPreviewData = ({ sourceId, ruleParams, buckets }: Options) => {
export const useChartPreviewData = ({
sourceId,
ruleParams,
buckets,
executionTimeRange,
}: Options) => {
const { http } = useKibana().services;

const [chartPreviewData, setChartPreviewData] = useState<
GetLogAlertsChartPreviewDataSuccessResponsePayload['data']['series']
>([]);
Expand All @@ -36,7 +42,13 @@ export const useChartPreviewData = ({ sourceId, ruleParams, buckets }: Options)
cancelPreviousOn: 'creation',
createPromise: async () => {
setHasError(false);
return await callGetChartPreviewDataAPI(sourceId, http!.fetch, ruleParams, buckets);
return await callGetChartPreviewDataAPI(
sourceId,
http!.fetch,
ruleParams,
buckets,
executionTimeRange
);
},
onResolve: ({ data: { series } }) => {
setHasError(false);
Expand Down Expand Up @@ -66,7 +78,8 @@ export const callGetChartPreviewDataAPI = async (
sourceId: string,
fetch: HttpHandler,
alertParams: GetLogAlertsChartPreviewDataAlertParamsSubset,
buckets: number
buckets: number,
executionTimeRange?: ExecutionTimeRange
) => {
const response = await fetch(LOG_ALERTS_CHART_PREVIEW_DATA_PATH, {
method: 'POST',
Expand All @@ -76,6 +89,7 @@ export const callGetChartPreviewDataAPI = async (
logView: { type: 'log-view-reference', logViewId: sourceId },
alertParams,
buckets,
executionTimeRange,
},
})
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { i18n } from '@kbn/i18n';
import { ObservabilityRuleTypeModel } from '@kbn/observability-plugin/public';
import { lazy } from 'react';
import {
LOG_DOCUMENT_COUNT_RULE_TYPE_ID,
PartialRuleParams,
Expand All @@ -33,6 +34,7 @@ export function createLogThresholdRuleType(
documentationUrl(docLinks) {
return `${docLinks.links.observability.logsThreshold}`;
},
alertDetailsAppSection: lazy(() => import('./components/alert_details_app_section')),
ruleParamsExpression,
validate: validateExpression,
defaultActionMessage: i18n.translate(
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/infra/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ export interface LensOptions {
breakdownSize: number;
}

export interface ExecutionTimeRange {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this interface needed? There is also one in alerting/logs/log_threshold/types

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. One with gte required a type for the frontend, and the other one gte is optional for backend.

gte: number;
lte: number;
}

type PropsOf<T> = T extends React.ComponentType<infer ComponentProps> ? ComponentProps : never;
type FirstArgumentOf<Func> = Func extends (arg1: infer FirstArgument, ...rest: any[]) => any
? FirstArgument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { i18n } from '@kbn/i18n';
import {
ExecutionTimeRange,
GroupedSearchQueryResponse,
GroupedSearchQueryResponseRT,
isOptimizedGroupedSearchQueryResponse,
Expand Down Expand Up @@ -35,7 +36,8 @@ export async function getChartPreviewData(
resolvedLogView: ResolvedLogView,
callWithRequest: KibanaFramework['callWithRequest'],
alertParams: GetLogAlertsChartPreviewDataAlertParamsSubset,
buckets: number
buckets: number,
executionTimeRange?: ExecutionTimeRange
) {
const { indices, timestampField, runtimeMappings } = resolvedLogView;
const { groupBy, timeSize, timeUnit } = alertParams;
Expand All @@ -47,11 +49,10 @@ export async function getChartPreviewData(
timeSize: timeSize * buckets,
};

const executionTimestamp = Date.now();
const { rangeFilter } = buildFiltersFromCriteria(
expandedAlertParams,
timestampField,
executionTimestamp
executionTimeRange
);

const query = isGrouped
Expand All @@ -60,14 +61,14 @@ export async function getChartPreviewData(
timestampField,
indices,
runtimeMappings,
executionTimestamp
executionTimeRange
)
: getUngroupedESQuery(
expandedAlertParams,
timestampField,
indices,
runtimeMappings,
executionTimestamp
executionTimeRange
);

if (!query) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ const baseRuleParams: Pick<RuleParams, 'count' | 'timeSize' | 'timeUnit' | 'logV

const TIMESTAMP_FIELD = '@timestamp';
const FILEBEAT_INDEX = 'filebeat-*';
const EXECUTION_TIMESTAMP = new Date('2022-01-01T00:00:00.000Z').valueOf();
const EXECUTION_TIMERANGE = {
lte: new Date('2022-01-01T00:00:00.000Z').valueOf(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we might have gte shall we add a test for that case too?

  const to = executionTimeRange?.lte || Date.now();
  const from = executionTimeRange?.gte || to - intervalAsMs;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maryam-saeidi, no need here. The executor doesn't use gte

};

const runtimeMappings: estypes.MappingRuntimeFields = {
runtime_field: {
Expand Down Expand Up @@ -173,7 +175,7 @@ describe('Log threshold executor', () => {
...baseRuleParams,
criteria: positiveCriteria,
};
const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD, EXECUTION_TIMESTAMP);
const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD, EXECUTION_TIMERANGE);
expect(filters.mustFilters).toEqual(expectedPositiveFilterClauses);
});

Expand All @@ -182,14 +184,14 @@ describe('Log threshold executor', () => {
...baseRuleParams,
criteria: negativeCriteria,
};
const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD, EXECUTION_TIMESTAMP);
const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD, EXECUTION_TIMERANGE);

expect(filters.mustNotFilters).toEqual(expectedNegativeFilterClauses);
});

test('Handles time range', () => {
const ruleParams: RuleParams = { ...baseRuleParams, criteria: [] };
const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD, EXECUTION_TIMESTAMP);
const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD, EXECUTION_TIMERANGE);
expect(typeof filters.rangeFilter.range[TIMESTAMP_FIELD].gte).toBe('number');
expect(typeof filters.rangeFilter.range[TIMESTAMP_FIELD].lte).toBe('number');
expect(filters.rangeFilter.range[TIMESTAMP_FIELD].format).toBe('epoch_millis');
Expand All @@ -212,7 +214,7 @@ describe('Log threshold executor', () => {
TIMESTAMP_FIELD,
FILEBEAT_INDEX,
runtimeMappings,
EXECUTION_TIMESTAMP
EXECUTION_TIMERANGE
);
expect(query).toEqual({
index: 'filebeat-*',
Expand Down Expand Up @@ -264,7 +266,7 @@ describe('Log threshold executor', () => {
TIMESTAMP_FIELD,
FILEBEAT_INDEX,
runtimeMappings,
EXECUTION_TIMESTAMP
EXECUTION_TIMERANGE
);

expect(query).toEqual({
Expand Down Expand Up @@ -344,7 +346,7 @@ describe('Log threshold executor', () => {
TIMESTAMP_FIELD,
FILEBEAT_INDEX,
runtimeMappings,
EXECUTION_TIMESTAMP
EXECUTION_TIMERANGE
);

expect(query).toEqual({
Expand Down
Loading