mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[AO] Fix showing temporary data view in the list of data views for the new threshold rule (#161462)
Closes #159774, closes #159778, closes #159779, closes #159776 ## Summary This PR fixes the data view list in the new metric threshold. Also, it adds an API integration test to check the reference in the rule saved object. Also, this PR improves the error handling and loading of the data view, similar to what we have for elasticsearch rule data view. |Threshold rule error|Elasticsearch query error| |---|---| || ## How to test - Make sure `xpack.observability.unsafe.thresholdRule.enabled` is set to true in kibana yml config Error handling - Throw an error [here](https://github.com/elastic/kibana/pull/161462/files#diff-4f65f6debaf6457d4b0400a27c1ea57ba52bfe4426ee40460d43a857c5bd165eL98) and make sure the message is shown correctly in the rule. Temporary data view - Create a temporary data view - Check the list of data views and make sure the new temporary one is added to the list |Adding a new temporary data view|Temporary data view in the list| |---|---| ||
This commit is contained in:
parent
7c333cdc33
commit
ac4635417f
7 changed files with 228 additions and 29 deletions
|
@ -5,18 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import Expressions from './expression';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer';
|
||||
import { Comparator } from '../../../../common/threshold_rule/types';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
|
||||
jest.mock('../../../utils/kibana_react');
|
||||
import { Comparator } from '../../../common/threshold_rule/types';
|
||||
import { MetricsExplorerMetric } from '../../../common/threshold_rule/metrics_explorer';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { kibanaStartMock } from '../../utils/kibana_react.mock';
|
||||
import Expressions from './threshold_rule_expression';
|
||||
|
||||
jest.mock('../../utils/kibana_react');
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
|
||||
|
@ -106,4 +106,44 @@ describe('Expression', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should show the error message', async () => {
|
||||
const currentOptions = {
|
||||
groupBy: 'host.hostname',
|
||||
filterQuery: 'foo',
|
||||
metrics: [
|
||||
{ aggregation: 'avg', field: 'system.load.1' },
|
||||
{ aggregation: 'cardinality', field: 'system.cpu.user.pct' },
|
||||
] as MetricsExplorerMetric[],
|
||||
};
|
||||
const errorMessage = 'Error in searchSource create';
|
||||
const kibanaMock = kibanaStartMock.startContract();
|
||||
useKibanaMock.mockReturnValue({
|
||||
...kibanaMock,
|
||||
services: {
|
||||
...kibanaMock.services,
|
||||
data: {
|
||||
dataViews: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
query: {
|
||||
timefilter: {
|
||||
timefilter: jest.fn(),
|
||||
},
|
||||
},
|
||||
search: {
|
||||
searchSource: {
|
||||
create: jest.fn(() => {
|
||||
throw new Error(errorMessage);
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const { wrapper } = await setup(currentOptions);
|
||||
expect(wrapper.find(`[data-test-subj="thresholdRuleExpressionError"]`).first().text()).toBe(
|
||||
errorMessage
|
||||
);
|
||||
});
|
||||
});
|
|
@ -6,14 +6,18 @@
|
|||
*/
|
||||
|
||||
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import {
|
||||
EuiAccordion,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiCheckbox,
|
||||
EuiEmptyPrompt,
|
||||
EuiFieldSearch,
|
||||
EuiFormRow,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiLoadingSpinner,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
|
@ -25,23 +29,24 @@ import { DataViewBase } from '@kbn/es-query';
|
|||
import { DataViewSelectPopover } from '@kbn/stack-alerts-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { debounce } from 'lodash';
|
||||
import {
|
||||
ForLastExpression,
|
||||
IErrorObject,
|
||||
RuleTypeParams,
|
||||
RuleTypeParamsExpressionProps,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { Aggregators, Comparator, QUERY_INVALID } from '../../../../common/threshold_rule/types';
|
||||
import { TimeUnitChar } from '../../../../common/utils/formatters/duration';
|
||||
import { AlertContextMeta, AlertParams, MetricExpression } from '../types';
|
||||
import { ExpressionChart } from './expression_chart';
|
||||
import { ExpressionRow } from './expression_row';
|
||||
import { MetricsExplorerKueryBar } from './kuery_bar';
|
||||
import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options';
|
||||
import { convertKueryToElasticSearchQuery } from '../helpers/kuery';
|
||||
import { MetricsExplorerGroupBy } from './group_by';
|
||||
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { Aggregators, Comparator, QUERY_INVALID } from '../../../common/threshold_rule/types';
|
||||
import { TimeUnitChar } from '../../../common/utils/formatters/duration';
|
||||
import { AlertContextMeta, AlertParams, MetricExpression } from './types';
|
||||
import { ExpressionChart } from './components/expression_chart';
|
||||
import { ExpressionRow } from './components/expression_row';
|
||||
import { MetricsExplorerKueryBar } from './components/kuery_bar';
|
||||
import { MetricsExplorerGroupBy } from './components/group_by';
|
||||
import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options';
|
||||
import { convertKueryToElasticSearchQuery } from './helpers/kuery';
|
||||
|
||||
const FILTER_TYPING_DEBOUNCE_MS = 500;
|
||||
|
||||
type Props = Omit<
|
||||
|
@ -66,6 +71,7 @@ export default function Expressions(props: Props) {
|
|||
const [timeUnit, setTimeUnit] = useState<TimeUnitChar | undefined>('m');
|
||||
const [dataView, setDataView] = useState<DataView>();
|
||||
const [searchSource, setSearchSource] = useState<ISearchSource>();
|
||||
const [paramsError, setParamsError] = useState<Error>();
|
||||
const derivedIndexPattern = useMemo<DataViewBase>(
|
||||
() => ({
|
||||
fields: dataView?.fields || [],
|
||||
|
@ -97,8 +103,7 @@ export default function Expressions(props: Props) {
|
|||
setSearchSource(createdSearchSource);
|
||||
setDataView(createdSearchSource.getField('index'));
|
||||
} catch (error) {
|
||||
// TODO Handle error
|
||||
console.log('error:', error);
|
||||
setParamsError(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -335,11 +340,32 @@ export default function Expressions(props: Props) {
|
|||
.filter((g) => typeof g === 'string') as string[];
|
||||
}, [ruleParams, groupByFilterTestPatterns]);
|
||||
|
||||
if (paramsError) {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut color="danger" iconType="warning" data-test-subj="thresholdRuleExpressionError">
|
||||
<p>{paramsError.message}</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size={'m'} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!searchSource) {
|
||||
return (
|
||||
<>
|
||||
<EuiEmptyPrompt title={<EuiLoadingSpinner size="xl" />} />
|
||||
<EuiSpacer size={'m'} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataViewSelectPopover
|
||||
dependencies={{ dataViews, dataViewEditor }}
|
||||
dataView={dataView}
|
||||
metadata={{ adHocDataViewList: metadata?.adHocDataViewList || [] }}
|
||||
onSelectDataView={onSelectDataView}
|
||||
onChangeMetaData={({ adHocDataViewList }) => {
|
||||
onChangeMetaData({ ...metadata, adHocDataViewList });
|
|
@ -72,7 +72,9 @@ const data = {
|
|||
},
|
||||
},
|
||||
search: {
|
||||
searchSource: jest.fn(),
|
||||
searchSource: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -93,7 +93,7 @@ export const registerObservabilityRuleTypes = (
|
|||
documentationUrl(docLinks) {
|
||||
return `${docLinks.links.observability.threshold}`;
|
||||
},
|
||||
ruleParamsExpression: lazy(() => import('../components/threshold/components/expression')),
|
||||
ruleParamsExpression: lazy(() => import('../components/threshold/threshold_rule_expression')),
|
||||
validate: validateMetricThreshold,
|
||||
defaultActionMessage: i18n.translate(
|
||||
'xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage',
|
||||
|
|
|
@ -11,6 +11,7 @@ export default function ({ loadTestFile }: any) {
|
|||
describe('MetricsUI Endpoints', () => {
|
||||
loadTestFile(require.resolve('./metric_threshold_rule'));
|
||||
loadTestFile(require.resolve('./threshold_rule'));
|
||||
loadTestFile(require.resolve('./threshold_rule_data_view'));
|
||||
});
|
||||
|
||||
describe('Synthetics', () => {
|
||||
|
|
|
@ -4,12 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
/*
|
||||
* 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 moment from 'moment';
|
||||
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
|
||||
|
||||
import { FtrProviderContext } from '../common/ftr_provider_context';
|
||||
import { getUrlPrefix, ObjectRemover } from '../common/lib';
|
||||
import { createRule } from './helpers/alerting_api_helper';
|
||||
import { createDataView, deleteDataView } from './helpers/data_view';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
const es = getService('es');
|
||||
|
||||
describe('Threshold rule data view >', () => {
|
||||
const DATA_VIEW_ID = 'data-view-id';
|
||||
|
||||
let ruleId: string;
|
||||
|
||||
const searchRule = () =>
|
||||
es.search<{ references: unknown; alert: { params: any } }>({
|
||||
index: '.kibana*',
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
_id: `alert:${ruleId}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: ['alert.params', 'references'],
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
await createDataView({
|
||||
supertest,
|
||||
name: 'test-data-view',
|
||||
id: DATA_VIEW_ID,
|
||||
title: 'random-index*',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
objectRemover.removeAll();
|
||||
await deleteDataView({
|
||||
supertest,
|
||||
id: DATA_VIEW_ID,
|
||||
});
|
||||
});
|
||||
|
||||
describe('save data view in rule correctly', () => {
|
||||
it('create a threshold rule', async () => {
|
||||
const createdRule = await createRule({
|
||||
supertest,
|
||||
tags: ['observability'],
|
||||
consumer: 'alerts',
|
||||
name: 'Threshold rule',
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
params: {
|
||||
criteria: [
|
||||
{
|
||||
aggType: Aggregators.CUSTOM,
|
||||
comparator: Comparator.GT,
|
||||
threshold: [7500000],
|
||||
timeSize: 5,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [
|
||||
{ name: 'A', field: 'span.self_time.sum.us', aggType: Aggregators.AVERAGE },
|
||||
],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: {
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
index: DATA_VIEW_ID,
|
||||
},
|
||||
},
|
||||
actions: [],
|
||||
});
|
||||
ruleId = createdRule.id;
|
||||
expect(ruleId).not.to.be(undefined);
|
||||
});
|
||||
|
||||
it('should have correct data view reference before and after edit', async () => {
|
||||
const {
|
||||
hits: { hits: alertHitsV1 },
|
||||
} = await searchRule();
|
||||
|
||||
await supertest
|
||||
.post(`${getUrlPrefix('default')}/internal/alerting/rules/_bulk_edit`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
ids: [ruleId],
|
||||
operations: [{ operation: 'set', field: 'apiKey' }],
|
||||
})
|
||||
.expect(200);
|
||||
objectRemover.add('default', ruleId, 'rule', 'alerting');
|
||||
|
||||
const {
|
||||
hits: { hits: alertHitsV2 },
|
||||
} = await searchRule();
|
||||
|
||||
expect(alertHitsV1[0]?._source?.references).to.eql([
|
||||
{
|
||||
name: 'param:kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
id: 'data-view-id',
|
||||
},
|
||||
]);
|
||||
expect(alertHitsV1[0]?._source?.alert?.params?.searchConfiguration).to.eql({
|
||||
query: { query: '', language: 'kuery' },
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
});
|
||||
expect(alertHitsV1[0].fields).to.eql(alertHitsV2[0].fields);
|
||||
expect(alertHitsV1[0]?._source?.references ?? true).to.eql(
|
||||
alertHitsV2[0]?._source?.references ?? false
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue