mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[AO] Add data view to the new threshold rule (#159479)
Closes #158840
## Summary
This PR adds selecting a persisted data view and using it in the new
threshold rule.
|Flyout|Rule saved object|
|---|---|
||
## 🧪 How to test
- Create a threshold rule with a persisted data view
- Make sure the related feature flag is configured:
`xpack.observability.unsafe.thresholdRule.enabled: true`
- Check whether the triggered alert matches the expectation related to
that data view
- Check the rule saved object to ensure data is saved there correctly
## What is not covered in this PR
I will follow up on the following list in future PRs:
- [Temporary data view](https://github.com/elastic/kibana/issues/159774)
- [Initial loading](https://github.com/elastic/kibana/issues/159779)
- [Setting a timeField beside the
timestamp](https://github.com/elastic/kibana/issues/159777)
- [Error handling](https://github.com/elastic/kibana/issues/159776)
- [Testing](https://github.com/elastic/kibana/issues/159778)
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a705225f6f
commit
87b80cb21b
81 changed files with 231 additions and 659 deletions
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as rt from 'io-ts';
|
import * as rt from 'io-ts';
|
||||||
import { indexPatternRt } from '@kbn/io-ts-utils';
|
|
||||||
import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold';
|
import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold';
|
||||||
import { values } from 'lodash';
|
import { values } from 'lodash';
|
||||||
import { Color } from './color_palette';
|
import { Color } from './color_palette';
|
||||||
|
@ -94,68 +93,14 @@ export const logDataViewReferenceRT = rt.type({
|
||||||
dataViewId: rt.string,
|
dataViewId: rt.string,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type LogDataViewReference = rt.TypeOf<typeof logDataViewReferenceRT>;
|
|
||||||
|
|
||||||
// Index name
|
// Index name
|
||||||
export const logIndexNameReferenceRT = rt.type({
|
export const logIndexNameReferenceRT = rt.type({
|
||||||
type: rt.literal('index_name'),
|
type: rt.literal('index_name'),
|
||||||
indexName: rt.string,
|
indexName: rt.string,
|
||||||
});
|
});
|
||||||
export type LogIndexNameReference = rt.TypeOf<typeof logIndexNameReferenceRT>;
|
|
||||||
|
|
||||||
export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]);
|
export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Properties that represent a full source configuration, which is the result of merging static values with
|
|
||||||
* saved values.
|
|
||||||
*/
|
|
||||||
const SourceConfigurationFieldsRT = rt.type({
|
|
||||||
message: rt.array(rt.string),
|
|
||||||
});
|
|
||||||
export const SourceConfigurationRT = rt.type({
|
|
||||||
name: rt.string,
|
|
||||||
description: rt.string,
|
|
||||||
metricAlias: rt.string,
|
|
||||||
logIndices: logIndexReferenceRT,
|
|
||||||
inventoryDefaultView: rt.string,
|
|
||||||
metricsExplorerDefaultView: rt.string,
|
|
||||||
fields: SourceConfigurationFieldsRT,
|
|
||||||
logColumns: rt.array(SourceConfigurationColumnRuntimeType),
|
|
||||||
anomalyThreshold: rt.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metricsSourceConfigurationPropertiesRT = rt.strict({
|
|
||||||
name: SourceConfigurationRT.props.name,
|
|
||||||
description: SourceConfigurationRT.props.description,
|
|
||||||
metricAlias: SourceConfigurationRT.props.metricAlias,
|
|
||||||
inventoryDefaultView: SourceConfigurationRT.props.inventoryDefaultView,
|
|
||||||
metricsExplorerDefaultView: SourceConfigurationRT.props.metricsExplorerDefaultView,
|
|
||||||
anomalyThreshold: rt.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type MetricsSourceConfigurationProperties = rt.TypeOf<
|
|
||||||
typeof metricsSourceConfigurationPropertiesRT
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const partialMetricsSourceConfigurationReqPayloadRT = rt.partial({
|
|
||||||
...metricsSourceConfigurationPropertiesRT.type.props,
|
|
||||||
metricAlias: indexPatternRt,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const partialMetricsSourceConfigurationPropertiesRT = rt.partial({
|
|
||||||
...metricsSourceConfigurationPropertiesRT.type.props,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type PartialMetricsSourceConfigurationProperties = rt.TypeOf<
|
|
||||||
typeof partialMetricsSourceConfigurationPropertiesRT
|
|
||||||
>;
|
|
||||||
|
|
||||||
const metricsSourceConfigurationOriginRT = rt.keyof({
|
|
||||||
fallback: null,
|
|
||||||
internal: null,
|
|
||||||
stored: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source status
|
* Source status
|
||||||
*/
|
*/
|
||||||
|
@ -180,32 +125,6 @@ export const metricsSourceStatusRT = rt.strict({
|
||||||
|
|
||||||
export type MetricsSourceStatus = rt.TypeOf<typeof metricsSourceStatusRT>;
|
export type MetricsSourceStatus = rt.TypeOf<typeof metricsSourceStatusRT>;
|
||||||
|
|
||||||
export const metricsSourceConfigurationRT = rt.exact(
|
|
||||||
rt.intersection([
|
|
||||||
rt.type({
|
|
||||||
id: rt.string,
|
|
||||||
origin: metricsSourceConfigurationOriginRT,
|
|
||||||
configuration: metricsSourceConfigurationPropertiesRT,
|
|
||||||
}),
|
|
||||||
rt.partial({
|
|
||||||
updatedAt: rt.number,
|
|
||||||
version: rt.string,
|
|
||||||
status: metricsSourceStatusRT,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
export type MetricsSourceConfiguration = rt.TypeOf<typeof metricsSourceConfigurationRT>;
|
|
||||||
export type PartialMetricsSourceConfiguration = DeepPartial<MetricsSourceConfiguration>;
|
|
||||||
|
|
||||||
export const metricsSourceConfigurationResponseRT = rt.type({
|
|
||||||
source: metricsSourceConfigurationRT,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type MetricsSourceConfigurationResponse = rt.TypeOf<
|
|
||||||
typeof metricsSourceConfigurationResponseRT
|
|
||||||
>;
|
|
||||||
|
|
||||||
export enum Comparator {
|
export enum Comparator {
|
||||||
GT = '>',
|
GT = '>',
|
||||||
LT = '<',
|
LT = '<',
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"charts",
|
"charts",
|
||||||
"data",
|
"data",
|
||||||
"dataViews",
|
"dataViews",
|
||||||
|
"dataViewEditor",
|
||||||
"embeddable",
|
"embeddable",
|
||||||
"exploratoryView",
|
"exploratoryView",
|
||||||
"features",
|
"features",
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
"visualizations"
|
"visualizations"
|
||||||
],
|
],
|
||||||
"optionalPlugins": ["discover", "home", "licensing", "usageCollection", "cloud", "spaces"],
|
"optionalPlugins": ["discover", "home", "licensing", "usageCollection", "cloud", "spaces"],
|
||||||
"requiredBundles": ["data", "kibanaReact", "kibanaUtils", "unifiedSearch", "cloudChat", "spaces"],
|
"requiredBundles": ["data", "kibanaReact", "kibanaUtils", "unifiedSearch", "cloudChat", "stackAlerts", "spaces"],
|
||||||
"extraPublicDirs": ["common"]
|
"extraPublicDirs": ["common"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ Array [
|
||||||
"chartType": "line",
|
"chartType": "line",
|
||||||
"derivedIndexPattern": Object {
|
"derivedIndexPattern": Object {
|
||||||
"fields": Array [],
|
"fields": Array [],
|
||||||
"title": "metricbeat-*",
|
"title": "unknown-index",
|
||||||
},
|
},
|
||||||
"expression": Object {
|
"expression": Object {
|
||||||
"aggType": "count",
|
"aggType": "count",
|
||||||
|
@ -35,9 +35,6 @@ Array [
|
||||||
"host.hostname",
|
"host.hostname",
|
||||||
],
|
],
|
||||||
"hideTitle": true,
|
"hideTitle": true,
|
||||||
"source": Object {
|
|
||||||
"id": "default",
|
|
||||||
},
|
|
||||||
"timeRange": Object {
|
"timeRange": Object {
|
||||||
"from": "2023-03-28T10:43:13.802Z",
|
"from": "2023-03-28T10:43:13.802Z",
|
||||||
"to": "2023-03-29T13:14:09.581Z",
|
"to": "2023-03-29T13:14:09.581Z",
|
|
@ -16,7 +16,7 @@ import {
|
||||||
buildMetricThresholdAlert,
|
buildMetricThresholdAlert,
|
||||||
buildMetricThresholdRule,
|
buildMetricThresholdRule,
|
||||||
} from '../mocks/metric_threshold_rule';
|
} from '../mocks/metric_threshold_rule';
|
||||||
import { AlertDetailsAppSection } from './alert_details_app_section';
|
import AlertDetailsAppSection from './alert_details_app_section';
|
||||||
import { ExpressionChart } from './expression_chart';
|
import { ExpressionChart } from './expression_chart';
|
||||||
|
|
||||||
const mockedChartStartContract = chartPluginMock.createStartContract();
|
const mockedChartStartContract = chartPluginMock.createStartContract();
|
||||||
|
@ -43,14 +43,6 @@ jest.mock('../../../utils/kibana_react', () => ({
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../helpers/source', () => ({
|
|
||||||
withSourceProvider: () => jest.fn,
|
|
||||||
useSourceContext: () => ({
|
|
||||||
source: { id: 'default' },
|
|
||||||
createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('AlertDetailsAppSection', () => {
|
describe('AlertDetailsAppSection', () => {
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
const mockedSetAlertSummaryFields = jest.fn();
|
const mockedSetAlertSummaryFields = jest.fn();
|
|
@ -5,6 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { DataViewBase } from '@kbn/es-query';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
@ -35,7 +36,6 @@ import { ExpressionChart } from './expression_chart';
|
||||||
import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart';
|
import { TIME_LABELS } from './criterion_preview_chart/criterion_preview_chart';
|
||||||
import { Threshold } from './threshold';
|
import { Threshold } from './threshold';
|
||||||
import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options';
|
import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options';
|
||||||
import { useSourceContext, withSourceProvider } from '../helpers/source';
|
|
||||||
import { MetricThresholdRuleTypeParams } from '../types';
|
import { MetricThresholdRuleTypeParams } from '../types';
|
||||||
|
|
||||||
// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690
|
// TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690
|
||||||
|
@ -58,19 +58,23 @@ interface AppSectionProps {
|
||||||
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
|
setAlertSummaryFields: React.Dispatch<React.SetStateAction<AlertSummaryField[] | undefined>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AlertDetailsAppSection({
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default function AlertDetailsAppSection({
|
||||||
alert,
|
alert,
|
||||||
rule,
|
rule,
|
||||||
ruleLink,
|
ruleLink,
|
||||||
setAlertSummaryFields,
|
setAlertSummaryFields,
|
||||||
}: AppSectionProps) {
|
}: AppSectionProps) {
|
||||||
const { uiSettings, charts } = useKibana().services;
|
const { uiSettings, charts } = useKibana().services;
|
||||||
const { source, createDerivedIndexPattern } = useSourceContext();
|
|
||||||
const { euiTheme } = useEuiTheme();
|
const { euiTheme } = useEuiTheme();
|
||||||
|
|
||||||
const derivedIndexPattern = useMemo(
|
// TODO Use rule data view
|
||||||
() => createDerivedIndexPattern(),
|
const derivedIndexPattern = useMemo<DataViewBase>(
|
||||||
[createDerivedIndexPattern]
|
() => ({
|
||||||
|
fields: [],
|
||||||
|
title: 'unknown-index',
|
||||||
|
}),
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
const chartProps = {
|
const chartProps = {
|
||||||
theme: charts.theme.useChartsTheme(),
|
theme: charts.theme.useChartsTheme(),
|
||||||
|
@ -162,7 +166,6 @@ export function AlertDetailsAppSection({
|
||||||
filterQuery={rule.params.filterQueryText}
|
filterQuery={rule.params.filterQueryText}
|
||||||
groupBy={rule.params.groupBy}
|
groupBy={rule.params.groupBy}
|
||||||
hideTitle
|
hideTitle
|
||||||
source={source}
|
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
@ -173,5 +176,3 @@ export function AlertDetailsAppSection({
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line import/no-default-export
|
|
||||||
export default withSourceProvider<AppSectionProps>(AlertDetailsAppSection)('default');
|
|
|
@ -5,34 +5,35 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useKibana } from '../../../utils/kibana_react';
|
||||||
|
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
// We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock`
|
|
||||||
import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
|
|
||||||
|
|
||||||
import { Expressions } from './expression';
|
import Expressions from './expression';
|
||||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||||
import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer';
|
import { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer';
|
||||||
import { Comparator } from '../../../../common/threshold_rule/types';
|
import { Comparator } from '../../../../common/threshold_rule/types';
|
||||||
|
|
||||||
jest.mock('../helpers/source', () => ({
|
jest.mock('../../../utils/kibana_react');
|
||||||
withSourceProvider: () => jest.fn,
|
|
||||||
useSourceContext: () => ({
|
|
||||||
source: { id: 'default' },
|
|
||||||
createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../utils/kibana_react', () => ({
|
const useKibanaMock = useKibana as jest.Mock;
|
||||||
useKibana: () => ({
|
|
||||||
services: mockCoreMock.createStart(),
|
const mockKibana = () => {
|
||||||
}),
|
useKibanaMock.mockReturnValue({
|
||||||
}));
|
...kibanaStartMock.startContract(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const dataViewMock = dataViewPluginMocks.createStartContract();
|
const dataViewMock = dataViewPluginMocks.createStartContract();
|
||||||
|
|
||||||
describe('Expression', () => {
|
describe('Expression', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockKibana();
|
||||||
|
});
|
||||||
|
|
||||||
async function setup(currentOptions: {
|
async function setup(currentOptions: {
|
||||||
metrics?: MetricsExplorerMetric[];
|
metrics?: MetricsExplorerMetric[];
|
||||||
filterQuery?: string;
|
filterQuery?: string;
|
||||||
|
@ -43,6 +44,7 @@ describe('Expression', () => {
|
||||||
groupBy: undefined,
|
groupBy: undefined,
|
||||||
filterQueryText: '',
|
filterQueryText: '',
|
||||||
sourceId: 'default',
|
sourceId: 'default',
|
||||||
|
searchConfiguration: {},
|
||||||
};
|
};
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
||||||
<Expressions
|
<Expressions
|
||||||
|
@ -55,8 +57,10 @@ describe('Expression', () => {
|
||||||
setRuleProperty={() => {}}
|
setRuleProperty={() => {}}
|
||||||
metadata={{
|
metadata={{
|
||||||
currentOptions,
|
currentOptions,
|
||||||
|
adHocDataViewList: [],
|
||||||
}}
|
}}
|
||||||
dataViews={dataViewMock}
|
dataViews={dataViewMock}
|
||||||
|
onChangeMetaData={jest.fn()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
EuiAccordion,
|
EuiAccordion,
|
||||||
|
@ -18,6 +19,10 @@ import {
|
||||||
EuiText,
|
EuiText,
|
||||||
EuiToolTip,
|
EuiToolTip,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
import { ISearchSource } from '@kbn/data-plugin/common';
|
||||||
|
import { DataView } from '@kbn/data-views-plugin/common';
|
||||||
|
import { DataViewBase } from '@kbn/es-query';
|
||||||
|
import { DataViewSelectPopover } from '@kbn/stack-alerts-plugin/public';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
@ -36,13 +41,12 @@ import { ExpressionRow } from './expression_row';
|
||||||
import { MetricsExplorerKueryBar } from './kuery_bar';
|
import { MetricsExplorerKueryBar } from './kuery_bar';
|
||||||
import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options';
|
import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options';
|
||||||
import { convertKueryToElasticSearchQuery } from '../helpers/kuery';
|
import { convertKueryToElasticSearchQuery } from '../helpers/kuery';
|
||||||
import { useSourceContext, withSourceProvider } from '../helpers/source';
|
|
||||||
import { MetricsExplorerGroupBy } from './group_by';
|
import { MetricsExplorerGroupBy } from './group_by';
|
||||||
const FILTER_TYPING_DEBOUNCE_MS = 500;
|
const FILTER_TYPING_DEBOUNCE_MS = 500;
|
||||||
|
|
||||||
type Props = Omit<
|
type Props = Omit<
|
||||||
RuleTypeParamsExpressionProps<RuleTypeParams & AlertParams, AlertContextMeta>,
|
RuleTypeParamsExpressionProps<RuleTypeParams & AlertParams, AlertContextMeta>,
|
||||||
'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch' | 'onChangeMetaData'
|
'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const defaultExpression = {
|
export const defaultExpression = {
|
||||||
|
@ -53,18 +57,55 @@ export const defaultExpression = {
|
||||||
timeUnit: 'm',
|
timeUnit: 'm',
|
||||||
} as MetricExpression;
|
} as MetricExpression;
|
||||||
|
|
||||||
export function Expressions(props: Props) {
|
// eslint-disable-next-line import/no-default-export
|
||||||
const { setRuleParams, ruleParams, errors, metadata } = props;
|
export default function Expressions(props: Props) {
|
||||||
const { docLinks } = useKibana().services;
|
const { setRuleParams, ruleParams, errors, metadata, onChangeMetaData } = props;
|
||||||
const { source, createDerivedIndexPattern } = useSourceContext();
|
const { data, dataViews, dataViewEditor, docLinks } = useKibana().services;
|
||||||
|
|
||||||
const [timeSize, setTimeSize] = useState<number | undefined>(1);
|
const [timeSize, setTimeSize] = useState<number | undefined>(1);
|
||||||
const [timeUnit, setTimeUnit] = useState<TimeUnitChar | undefined>('m');
|
const [timeUnit, setTimeUnit] = useState<TimeUnitChar | undefined>('m');
|
||||||
const derivedIndexPattern = useMemo(
|
const [dataView, setDataView] = useState<DataView>();
|
||||||
() => createDerivedIndexPattern(),
|
const [searchSource, setSearchSource] = useState<ISearchSource>();
|
||||||
[createDerivedIndexPattern]
|
const derivedIndexPattern = useMemo<DataViewBase>(
|
||||||
|
() => ({
|
||||||
|
fields: dataView?.fields || [],
|
||||||
|
title: dataView?.getIndexPattern() || 'unknown-index',
|
||||||
|
}),
|
||||||
|
[dataView]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initSearchSource = async () => {
|
||||||
|
let initialSearchConfiguration = ruleParams.searchConfiguration;
|
||||||
|
|
||||||
|
if (!ruleParams.searchConfiguration) {
|
||||||
|
const newSearchSource = data.search.searchSource.createEmpty();
|
||||||
|
newSearchSource.setField('query', data.query.queryString.getDefaultQuery());
|
||||||
|
const defaultDataView = await data.dataViews.getDefaultDataView();
|
||||||
|
if (defaultDataView) {
|
||||||
|
newSearchSource.setField('index', defaultDataView);
|
||||||
|
setDataView(defaultDataView);
|
||||||
|
}
|
||||||
|
initialSearchConfiguration = newSearchSource.getSerializedFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdSearchSource = await data.search.searchSource.create(
|
||||||
|
initialSearchConfiguration
|
||||||
|
);
|
||||||
|
setRuleParams('searchConfiguration', initialSearchConfiguration);
|
||||||
|
setSearchSource(createdSearchSource);
|
||||||
|
setDataView(createdSearchSource.getField('index'));
|
||||||
|
} catch (error) {
|
||||||
|
// TODO Handle error
|
||||||
|
console.log('error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initSearchSource();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [data.search.searchSource, data.dataViews]);
|
||||||
|
|
||||||
const options = useMemo<MetricsExplorerOptions>(() => {
|
const options = useMemo<MetricsExplorerOptions>(() => {
|
||||||
if (metadata?.currentOptions?.metrics) {
|
if (metadata?.currentOptions?.metrics) {
|
||||||
return metadata.currentOptions as MetricsExplorerOptions;
|
return metadata.currentOptions as MetricsExplorerOptions;
|
||||||
|
@ -76,31 +117,49 @@ export function Expressions(props: Props) {
|
||||||
}
|
}
|
||||||
}, [metadata]);
|
}, [metadata]);
|
||||||
|
|
||||||
|
const onSelectDataView = useCallback(
|
||||||
|
(newDataView: DataView) => {
|
||||||
|
const ruleCriteria = (ruleParams.criteria ? ruleParams.criteria.slice() : []).map(
|
||||||
|
(criterion) => {
|
||||||
|
criterion.customMetrics?.forEach((metric) => {
|
||||||
|
metric.field = undefined;
|
||||||
|
});
|
||||||
|
return criterion;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setRuleParams('criteria', ruleCriteria);
|
||||||
|
searchSource?.setParent(undefined).setField('index', newDataView);
|
||||||
|
setRuleParams('searchConfiguration', searchSource?.getSerializedFields());
|
||||||
|
setDataView(newDataView);
|
||||||
|
},
|
||||||
|
[ruleParams.criteria, searchSource, setRuleParams]
|
||||||
|
);
|
||||||
|
|
||||||
const updateParams = useCallback(
|
const updateParams = useCallback(
|
||||||
(id, e: MetricExpression) => {
|
(id, e: MetricExpression) => {
|
||||||
const exp = ruleParams.criteria ? ruleParams.criteria.slice() : [];
|
const ruleCriteria = ruleParams.criteria ? ruleParams.criteria.slice() : [];
|
||||||
exp[id] = e;
|
ruleCriteria[id] = e;
|
||||||
setRuleParams('criteria', exp);
|
setRuleParams('criteria', ruleCriteria);
|
||||||
},
|
},
|
||||||
[setRuleParams, ruleParams.criteria]
|
[setRuleParams, ruleParams.criteria]
|
||||||
);
|
);
|
||||||
|
|
||||||
const addExpression = useCallback(() => {
|
const addExpression = useCallback(() => {
|
||||||
const exp = ruleParams.criteria?.slice() || [];
|
const ruleCriteria = ruleParams.criteria?.slice() || [];
|
||||||
exp.push({
|
ruleCriteria.push({
|
||||||
...defaultExpression,
|
...defaultExpression,
|
||||||
timeSize: timeSize ?? defaultExpression.timeSize,
|
timeSize: timeSize ?? defaultExpression.timeSize,
|
||||||
timeUnit: timeUnit ?? defaultExpression.timeUnit,
|
timeUnit: timeUnit ?? defaultExpression.timeUnit,
|
||||||
});
|
});
|
||||||
setRuleParams('criteria', exp);
|
setRuleParams('criteria', ruleCriteria);
|
||||||
}, [setRuleParams, ruleParams.criteria, timeSize, timeUnit]);
|
}, [setRuleParams, ruleParams.criteria, timeSize, timeUnit]);
|
||||||
|
|
||||||
const removeExpression = useCallback(
|
const removeExpression = useCallback(
|
||||||
(id: number) => {
|
(id: number) => {
|
||||||
const exp = ruleParams.criteria?.slice() || [];
|
const ruleCriteria = ruleParams.criteria?.slice() || [];
|
||||||
if (exp.length > 1) {
|
if (ruleCriteria.length > 1) {
|
||||||
exp.splice(id, 1);
|
ruleCriteria.splice(id, 1);
|
||||||
setRuleParams('criteria', exp);
|
setRuleParams('criteria', ruleCriteria);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setRuleParams, ruleParams.criteria]
|
[setRuleParams, ruleParams.criteria]
|
||||||
|
@ -143,26 +202,25 @@ export function Expressions(props: Props) {
|
||||||
|
|
||||||
const updateTimeSize = useCallback(
|
const updateTimeSize = useCallback(
|
||||||
(ts: number | undefined) => {
|
(ts: number | undefined) => {
|
||||||
const criteria =
|
const ruleCriteria =
|
||||||
ruleParams.criteria?.map((c) => ({
|
ruleParams.criteria?.map((c) => ({
|
||||||
...c,
|
...c,
|
||||||
timeSize: ts,
|
timeSize: ts,
|
||||||
})) || [];
|
})) || [];
|
||||||
setTimeSize(ts || undefined);
|
setTimeSize(ts || undefined);
|
||||||
setRuleParams('criteria', criteria);
|
setRuleParams('criteria', ruleCriteria);
|
||||||
},
|
},
|
||||||
[ruleParams.criteria, setRuleParams]
|
[ruleParams.criteria, setRuleParams]
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateTimeUnit = useCallback(
|
const updateTimeUnit = useCallback(
|
||||||
(tu: string) => {
|
(tu: string) => {
|
||||||
const criteria =
|
const ruleCriteria = (ruleParams.criteria?.map((c) => ({
|
||||||
ruleParams.criteria?.map((c) => ({
|
...c,
|
||||||
...c,
|
timeUnit: tu,
|
||||||
timeUnit: tu,
|
})) || []) as AlertParams['criteria'];
|
||||||
})) || [];
|
|
||||||
setTimeUnit(tu as TimeUnitChar);
|
setTimeUnit(tu as TimeUnitChar);
|
||||||
setRuleParams('criteria', criteria as AlertParams['criteria']);
|
setRuleParams('criteria', ruleCriteria);
|
||||||
},
|
},
|
||||||
[ruleParams.criteria, setRuleParams]
|
[ruleParams.criteria, setRuleParams]
|
||||||
);
|
);
|
||||||
|
@ -230,17 +288,13 @@ export function Expressions(props: Props) {
|
||||||
preFillAlertGroupBy();
|
preFillAlertGroupBy();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ruleParams.sourceId) {
|
|
||||||
setRuleParams('sourceId', source?.id || 'default');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof ruleParams.alertOnNoData === 'undefined') {
|
if (typeof ruleParams.alertOnNoData === 'undefined') {
|
||||||
setRuleParams('alertOnNoData', true);
|
setRuleParams('alertOnNoData', true);
|
||||||
}
|
}
|
||||||
if (typeof ruleParams.alertOnGroupDisappear === 'undefined') {
|
if (typeof ruleParams.alertOnGroupDisappear === 'undefined') {
|
||||||
setRuleParams('alertOnGroupDisappear', true);
|
setRuleParams('alertOnGroupDisappear', true);
|
||||||
}
|
}
|
||||||
}, [metadata, source]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [metadata]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const handleFieldSearchChange = useCallback(
|
const handleFieldSearchChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => onFilterChange(e.target.value),
|
(e: ChangeEvent<HTMLInputElement>) => onFilterChange(e.target.value),
|
||||||
|
@ -283,7 +337,15 @@ export function Expressions(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiSpacer size={'m'} />
|
<DataViewSelectPopover
|
||||||
|
dependencies={{ dataViews, dataViewEditor }}
|
||||||
|
dataView={dataView}
|
||||||
|
onSelectDataView={onSelectDataView}
|
||||||
|
onChangeMetaData={({ adHocDataViewList }) => {
|
||||||
|
onChangeMetaData({ ...metadata, adHocDataViewList });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<EuiSpacer size={'s'} />
|
||||||
<EuiText size="xs">
|
<EuiText size="xs">
|
||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -298,7 +360,7 @@ export function Expressions(props: Props) {
|
||||||
return (
|
return (
|
||||||
<ExpressionRow
|
<ExpressionRow
|
||||||
canDelete={(ruleParams.criteria && ruleParams.criteria.length > 1) || false}
|
canDelete={(ruleParams.criteria && ruleParams.criteria.length > 1) || false}
|
||||||
fields={derivedIndexPattern.fields}
|
fields={derivedIndexPattern.fields as any}
|
||||||
remove={removeExpression}
|
remove={removeExpression}
|
||||||
addExpression={addExpression}
|
addExpression={addExpression}
|
||||||
key={idx} // idx's don't usually make good key's but here the index has semantic meaning
|
key={idx} // idx's don't usually make good key's but here the index has semantic meaning
|
||||||
|
@ -308,17 +370,16 @@ export function Expressions(props: Props) {
|
||||||
expression={e || {}}
|
expression={e || {}}
|
||||||
dataView={derivedIndexPattern}
|
dataView={derivedIndexPattern}
|
||||||
>
|
>
|
||||||
|
{/* Preview */}
|
||||||
<ExpressionChart
|
<ExpressionChart
|
||||||
expression={e}
|
expression={e}
|
||||||
derivedIndexPattern={derivedIndexPattern}
|
derivedIndexPattern={derivedIndexPattern}
|
||||||
source={source}
|
|
||||||
filterQuery={ruleParams.filterQueryText}
|
filterQuery={ruleParams.filterQueryText}
|
||||||
groupBy={ruleParams.groupBy}
|
groupBy={ruleParams.groupBy}
|
||||||
/>
|
/>
|
||||||
</ExpressionRow>
|
</ExpressionRow>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div style={{ marginLeft: 28 }}>
|
<div style={{ marginLeft: 28 }}>
|
||||||
<ForLastExpression
|
<ForLastExpression
|
||||||
timeWindowSize={timeSize}
|
timeWindowSize={timeSize}
|
||||||
|
@ -328,7 +389,6 @@ export function Expressions(props: Props) {
|
||||||
onChangeWindowUnit={updateTimeUnit}
|
onChangeWindowUnit={updateTimeUnit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EuiSpacer size={'m'} />
|
<EuiSpacer size={'m'} />
|
||||||
<div>
|
<div>
|
||||||
<EuiButtonEmpty
|
<EuiButtonEmpty
|
||||||
|
@ -345,7 +405,6 @@ export function Expressions(props: Props) {
|
||||||
/>
|
/>
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<EuiSpacer size={'m'} />
|
<EuiSpacer size={'m'} />
|
||||||
<EuiAccordion
|
<EuiAccordion
|
||||||
id="advanced-options-accordion"
|
id="advanced-options-accordion"
|
||||||
|
@ -387,7 +446,6 @@ export function Expressions(props: Props) {
|
||||||
</EuiPanel>
|
</EuiPanel>
|
||||||
</EuiAccordion>
|
</EuiAccordion>
|
||||||
<EuiSpacer size={'m'} />
|
<EuiSpacer size={'m'} />
|
||||||
|
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
label={i18n.translate('xpack.observability.threshold.rule.alertFlyout.filterLabel', {
|
label={i18n.translate('xpack.observability.threshold.rule.alertFlyout.filterLabel', {
|
||||||
defaultMessage: 'Filter (optional)',
|
defaultMessage: 'Filter (optional)',
|
||||||
|
@ -398,7 +456,7 @@ export function Expressions(props: Props) {
|
||||||
fullWidth
|
fullWidth
|
||||||
display="rowCompressed"
|
display="rowCompressed"
|
||||||
>
|
>
|
||||||
{(metadata && (
|
{(metadata && derivedIndexPattern && (
|
||||||
<MetricsExplorerKueryBar
|
<MetricsExplorerKueryBar
|
||||||
derivedIndexPattern={derivedIndexPattern}
|
derivedIndexPattern={derivedIndexPattern}
|
||||||
onChange={debouncedOnFilterChange}
|
onChange={debouncedOnFilterChange}
|
||||||
|
@ -414,7 +472,6 @@ export function Expressions(props: Props) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
|
|
||||||
<EuiSpacer size={'m'} />
|
<EuiSpacer size={'m'} />
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
label={i18n.translate('xpack.observability.threshold.rule.alertFlyout.createAlertPerText', {
|
label={i18n.translate('xpack.observability.threshold.rule.alertFlyout.createAlertPerText', {
|
||||||
|
@ -432,7 +489,7 @@ export function Expressions(props: Props) {
|
||||||
>
|
>
|
||||||
<MetricsExplorerGroupBy
|
<MetricsExplorerGroupBy
|
||||||
onChange={onGroupByChange}
|
onChange={onGroupByChange}
|
||||||
fields={derivedIndexPattern.fields}
|
fields={derivedIndexPattern.fields as any}
|
||||||
options={{
|
options={{
|
||||||
...options,
|
...options,
|
||||||
groupBy: ruleParams.groupBy || undefined,
|
groupBy: ruleParams.groupBy || undefined,
|
||||||
|
@ -508,7 +565,3 @@ const docCountNoDataDisabledHelpText = i18n.translate(
|
||||||
defaultMessage: '[This setting is not applicable to the Document Count aggregator.]',
|
defaultMessage: '[This setting is not applicable to the Document Count aggregator.]',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// required for dynamic import
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
|
||||||
export default withSourceProvider<Props>(Expressions)('default');
|
|
|
@ -14,11 +14,7 @@ import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||||
import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
|
import { coreMock as mockCoreMock } from '@kbn/core/public/mocks';
|
||||||
import { MetricExpression } from '../types';
|
import { MetricExpression } from '../types';
|
||||||
import { ExpressionChart } from './expression_chart';
|
import { ExpressionChart } from './expression_chart';
|
||||||
import {
|
import { Aggregators, Comparator } from '../../../../common/threshold_rule/types';
|
||||||
Aggregators,
|
|
||||||
Comparator,
|
|
||||||
MetricsSourceConfiguration,
|
|
||||||
} from '../../../../common/threshold_rule/types';
|
|
||||||
|
|
||||||
const mockStartServices = mockCoreMock.createStart();
|
const mockStartServices = mockCoreMock.createStart();
|
||||||
|
|
||||||
|
@ -57,24 +53,10 @@ describe('ExpressionChart', () => {
|
||||||
fields: [],
|
fields: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const source: MetricsSourceConfiguration = {
|
|
||||||
id: 'default',
|
|
||||||
origin: 'fallback',
|
|
||||||
configuration: {
|
|
||||||
name: 'default',
|
|
||||||
description: 'The default configuration',
|
|
||||||
metricAlias: 'metricbeat-*',
|
|
||||||
inventoryDefaultView: 'host',
|
|
||||||
metricsExplorerDefaultView: 'host',
|
|
||||||
anomalyThreshold: 20,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
||||||
<ExpressionChart
|
<ExpressionChart
|
||||||
expression={expression}
|
expression={expression}
|
||||||
derivedIndexPattern={derivedIndexPattern}
|
derivedIndexPattern={derivedIndexPattern}
|
||||||
source={source}
|
|
||||||
filterQuery={filterQuery}
|
filterQuery={filterQuery}
|
||||||
groupBy={groupBy}
|
groupBy={groupBy}
|
||||||
annotations={annotations}
|
annotations={annotations}
|
|
@ -31,7 +31,6 @@ import { Color } from '../../../../common/threshold_rule/color_palette';
|
||||||
import {
|
import {
|
||||||
MetricsExplorerChartType,
|
MetricsExplorerChartType,
|
||||||
MetricsExplorerOptionsMetric,
|
MetricsExplorerOptionsMetric,
|
||||||
MetricsSourceConfiguration,
|
|
||||||
} from '../../../../common/threshold_rule/types';
|
} from '../../../../common/threshold_rule/types';
|
||||||
import { MetricExpression, TimeRange } from '../types';
|
import { MetricExpression, TimeRange } from '../types';
|
||||||
import { createFormatterForMetric } from '../helpers/create_formatter_for_metric';
|
import { createFormatterForMetric } from '../helpers/create_formatter_for_metric';
|
||||||
|
@ -57,7 +56,6 @@ interface Props {
|
||||||
filterQuery?: string;
|
filterQuery?: string;
|
||||||
groupBy?: string | string[];
|
groupBy?: string | string[];
|
||||||
hideTitle?: boolean;
|
hideTitle?: boolean;
|
||||||
source?: MetricsSourceConfiguration;
|
|
||||||
timeRange?: TimeRange;
|
timeRange?: TimeRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,14 +67,12 @@ export function ExpressionChart({
|
||||||
filterQuery,
|
filterQuery,
|
||||||
groupBy,
|
groupBy,
|
||||||
hideTitle = false,
|
hideTitle = false,
|
||||||
source,
|
|
||||||
timeRange,
|
timeRange,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { charts, uiSettings } = useKibana().services;
|
const { charts, uiSettings } = useKibana().services;
|
||||||
const { isLoading, data } = useMetricsExplorerChartData(
|
const { isLoading, data } = useMetricsExplorerChartData(
|
||||||
expression,
|
expression,
|
||||||
derivedIndexPattern,
|
derivedIndexPattern,
|
||||||
source,
|
|
||||||
filterQuery,
|
filterQuery,
|
||||||
groupBy,
|
groupBy,
|
||||||
timeRange
|
timeRange
|
|
@ -13,14 +13,6 @@ import { act } from 'react-dom/test-utils';
|
||||||
import { MetricExpression } from '../types';
|
import { MetricExpression } from '../types';
|
||||||
import { ExpressionRow } from './expression_row';
|
import { ExpressionRow } from './expression_row';
|
||||||
|
|
||||||
jest.mock('../helpers/source', () => ({
|
|
||||||
withSourceProvider: () => jest.fn,
|
|
||||||
useSourceContext: () => ({
|
|
||||||
source: { id: 'default' },
|
|
||||||
createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('ExpressionRow', () => {
|
describe('ExpressionRow', () => {
|
||||||
async function setup(expression: MetricExpression) {
|
async function setup(expression: MetricExpression) {
|
||||||
const wrapper = mountWithIntl(
|
const wrapper = mountWithIntl(
|
|
@ -144,20 +144,6 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
||||||
name: f.name,
|
name: f.name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// for v8.9 we want to show only the Custom Equation. Use EuiExpression instead of WhenExpression */
|
|
||||||
// const updateAggType = useCallback(
|
|
||||||
// (at: string) => {
|
|
||||||
// setRuleParams(expressionId, {
|
|
||||||
// ...expression,
|
|
||||||
// aggType: at as MetricExpression['aggType'],
|
|
||||||
// metric: ['custom', 'count'].includes(at) ? undefined : expression.metric,
|
|
||||||
// customMetrics: at === 'custom' ? expression.customMetrics : undefined,
|
|
||||||
// equation: at === 'custom' ? expression.equation : undefined,
|
|
||||||
// label: at === 'custom' ? expression.label : undefined,
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// [expressionId, expression, setRuleParams]
|
|
||||||
// );
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiFlexGroup gutterSize="xs">
|
<EuiFlexGroup gutterSize="xs">
|
||||||
|
@ -177,12 +163,6 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
||||||
<EuiFlexItem grow>
|
<EuiFlexItem grow>
|
||||||
<StyledExpressionRow style={{ gap: aggType !== 'custom' ? 24 : 12 }}>
|
<StyledExpressionRow style={{ gap: aggType !== 'custom' ? 24 : 12 }}>
|
||||||
<StyledExpression>
|
<StyledExpression>
|
||||||
{/* for v8.9 we want to show only the Custom Equation. Use EuiExpression instead of WhenExpression */}
|
|
||||||
{/* <WhenExpression
|
|
||||||
customAggTypesOptions={aggregationType}
|
|
||||||
aggType={aggType}
|
|
||||||
onChangeSelectedAggType={updateAggType}
|
|
||||||
/> */}
|
|
||||||
<EuiExpression
|
<EuiExpression
|
||||||
data-test-subj="customEquationWhen"
|
data-test-subj="customEquationWhen"
|
||||||
description={i18n.translate(
|
description={i18n.translate(
|
|
@ -9,10 +9,7 @@ import DateMath from '@kbn/datemath';
|
||||||
import { DataViewBase } from '@kbn/es-query';
|
import { DataViewBase } from '@kbn/es-query';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { MetricExplorerCustomMetricAggregations } from '../../../../common/threshold_rule/metrics_explorer';
|
import { MetricExplorerCustomMetricAggregations } from '../../../../common/threshold_rule/metrics_explorer';
|
||||||
import {
|
import { MetricExpressionCustomMetric } from '../../../../common/threshold_rule/types';
|
||||||
MetricExpressionCustomMetric,
|
|
||||||
MetricsSourceConfiguration,
|
|
||||||
} from '../../../../common/threshold_rule/types';
|
|
||||||
import { MetricExpression, TimeRange } from '../types';
|
import { MetricExpression, TimeRange } from '../types';
|
||||||
import { useMetricsExplorerData } from './use_metrics_explorer_data';
|
import { useMetricsExplorerData } from './use_metrics_explorer_data';
|
||||||
|
|
||||||
|
@ -26,7 +23,6 @@ const DEFAULT_TIME_RANGE = {};
|
||||||
export const useMetricsExplorerChartData = (
|
export const useMetricsExplorerChartData = (
|
||||||
expression: MetricExpression,
|
expression: MetricExpression,
|
||||||
derivedIndexPattern: DataViewBase,
|
derivedIndexPattern: DataViewBase,
|
||||||
source?: MetricsSourceConfiguration,
|
|
||||||
filterQuery?: string,
|
filterQuery?: string,
|
||||||
groupBy?: string | string[],
|
groupBy?: string | string[],
|
||||||
timeRange: TimeRange = DEFAULT_TIME_RANGE
|
timeRange: TimeRange = DEFAULT_TIME_RANGE
|
||||||
|
@ -53,11 +49,13 @@ export const useMetricsExplorerChartData = (
|
||||||
],
|
],
|
||||||
aggregation: expression.aggType || 'avg',
|
aggregation: expression.aggType || 'avg',
|
||||||
}),
|
}),
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[
|
[
|
||||||
expression.aggType,
|
expression.aggType,
|
||||||
expression.equation,
|
expression.equation,
|
||||||
expression.metric,
|
expression.metric,
|
||||||
expression.customMetrics,
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
JSON.stringify(expression.customMetrics),
|
||||||
filterQuery,
|
filterQuery,
|
||||||
groupBy,
|
groupBy,
|
||||||
]
|
]
|
||||||
|
@ -74,7 +72,7 @@ export const useMetricsExplorerChartData = (
|
||||||
};
|
};
|
||||||
}, [timeRange, timeSize, timeUnit]);
|
}, [timeRange, timeSize, timeUnit]);
|
||||||
|
|
||||||
return useMetricsExplorerData(options, source?.configuration, derivedIndexPattern, timestamps);
|
return useMetricsExplorerData(options, derivedIndexPattern, timestamps);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapMetricThresholdMetricToMetricsExplorerMetric = (metric: MetricExpressionCustomMetric) => {
|
const mapMetricThresholdMetricToMetricsExplorerMetric = (metric: MetricExpressionCustomMetric) => {
|
|
@ -17,13 +17,11 @@ import {
|
||||||
MetricsExplorerTimestampsRT,
|
MetricsExplorerTimestampsRT,
|
||||||
} from './use_metrics_explorer_options';
|
} from './use_metrics_explorer_options';
|
||||||
import { DataViewBase } from '@kbn/es-query';
|
import { DataViewBase } from '@kbn/es-query';
|
||||||
import { MetricsSourceConfigurationProperties } from '../../../../common/threshold_rule/types';
|
|
||||||
import {
|
import {
|
||||||
createSeries,
|
createSeries,
|
||||||
derivedIndexPattern,
|
derivedIndexPattern,
|
||||||
options,
|
options,
|
||||||
resp,
|
resp,
|
||||||
source,
|
|
||||||
timestamps,
|
timestamps,
|
||||||
} from '../../../utils/metrics_explorer';
|
} from '../../../utils/metrics_explorer';
|
||||||
|
|
||||||
|
@ -54,20 +52,12 @@ const renderUseMetricsExplorerDataHook = () => {
|
||||||
return renderHook(
|
return renderHook(
|
||||||
(props: {
|
(props: {
|
||||||
options: MetricsExplorerOptions;
|
options: MetricsExplorerOptions;
|
||||||
source: MetricsSourceConfigurationProperties | undefined;
|
|
||||||
derivedIndexPattern: DataViewBase;
|
derivedIndexPattern: DataViewBase;
|
||||||
timestamps: MetricsExplorerTimestampsRT;
|
timestamps: MetricsExplorerTimestampsRT;
|
||||||
}) =>
|
}) => useMetricsExplorerData(props.options, props.derivedIndexPattern, props.timestamps),
|
||||||
useMetricsExplorerData(
|
|
||||||
props.options,
|
|
||||||
props.source,
|
|
||||||
props.derivedIndexPattern,
|
|
||||||
props.timestamps
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
initialProps: {
|
initialProps: {
|
||||||
options,
|
options,
|
||||||
source,
|
|
||||||
derivedIndexPattern,
|
derivedIndexPattern,
|
||||||
timestamps,
|
timestamps,
|
||||||
},
|
},
|
||||||
|
@ -163,7 +153,6 @@ describe('useMetricsExplorerData Hook', () => {
|
||||||
aggregation: 'count',
|
aggregation: 'count',
|
||||||
metrics: [{ aggregation: 'count' }],
|
metrics: [{ aggregation: 'count' }],
|
||||||
},
|
},
|
||||||
source,
|
|
||||||
derivedIndexPattern,
|
derivedIndexPattern,
|
||||||
timestamps,
|
timestamps,
|
||||||
});
|
});
|
||||||
|
@ -187,7 +176,6 @@ describe('useMetricsExplorerData Hook', () => {
|
||||||
mockedFetch.mockResolvedValue(resp as any);
|
mockedFetch.mockResolvedValue(resp as any);
|
||||||
rerender({
|
rerender({
|
||||||
options,
|
options,
|
||||||
source,
|
|
||||||
derivedIndexPattern,
|
derivedIndexPattern,
|
||||||
timestamps: { fromTimestamp: 1678378092225, toTimestamp: 1678381693477, interval: '>=10s' },
|
timestamps: { fromTimestamp: 1678378092225, toTimestamp: 1678381693477, interval: '>=10s' },
|
||||||
});
|
});
|
|
@ -9,7 +9,6 @@ import { DataViewBase } from '@kbn/es-query';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||||
import { MetricsSourceConfigurationProperties } from '../../../../common/threshold_rule/types';
|
|
||||||
import {
|
import {
|
||||||
MetricsExplorerResponse,
|
MetricsExplorerResponse,
|
||||||
metricsExplorerResponseRT,
|
metricsExplorerResponseRT,
|
||||||
|
@ -24,7 +23,6 @@ import { decodeOrThrow } from '../helpers/runtime_types';
|
||||||
|
|
||||||
export function useMetricsExplorerData(
|
export function useMetricsExplorerData(
|
||||||
options: MetricsExplorerOptions,
|
options: MetricsExplorerOptions,
|
||||||
source: MetricsSourceConfigurationProperties | undefined,
|
|
||||||
derivedIndexPattern: DataViewBase,
|
derivedIndexPattern: DataViewBase,
|
||||||
{ fromTimestamp, toTimestamp, interval }: MetricsExplorerTimestampsRT,
|
{ fromTimestamp, toTimestamp, interval }: MetricsExplorerTimestampsRT,
|
||||||
enabled = true
|
enabled = true
|
||||||
|
@ -35,7 +33,7 @@ export function useMetricsExplorerData(
|
||||||
MetricsExplorerResponse,
|
MetricsExplorerResponse,
|
||||||
Error
|
Error
|
||||||
>({
|
>({
|
||||||
queryKey: ['metricExplorer', options, fromTimestamp, toTimestamp],
|
queryKey: ['metricExplorer', options, fromTimestamp, toTimestamp, derivedIndexPattern.title],
|
||||||
queryFn: async ({ signal, pageParam = { afterKey: null } }) => {
|
queryFn: async ({ signal, pageParam = { afterKey: null } }) => {
|
||||||
if (!fromTimestamp || !toTimestamp) {
|
if (!fromTimestamp || !toTimestamp) {
|
||||||
throw new Error('Unable to parse timerange');
|
throw new Error('Unable to parse timerange');
|
||||||
|
@ -43,8 +41,8 @@ export function useMetricsExplorerData(
|
||||||
if (!http) {
|
if (!http) {
|
||||||
throw new Error('HTTP service is unavailable');
|
throw new Error('HTTP service is unavailable');
|
||||||
}
|
}
|
||||||
if (!source) {
|
if (!derivedIndexPattern.title) {
|
||||||
throw new Error('Source is unavailable');
|
throw new Error('Data view is unavailable');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { afterKey } = pageParam;
|
const { afterKey } = pageParam;
|
||||||
|
@ -57,7 +55,7 @@ export function useMetricsExplorerData(
|
||||||
groupBy: options.groupBy,
|
groupBy: options.groupBy,
|
||||||
afterKey,
|
afterKey,
|
||||||
limit: options.limit,
|
limit: options.limit,
|
||||||
indexPattern: source.metricAlias,
|
indexPattern: derivedIndexPattern.title,
|
||||||
filterQuery:
|
filterQuery:
|
||||||
(options.filterQuery &&
|
(options.filterQuery &&
|
||||||
convertKueryToElasticSearchQuery(options.filterQuery, derivedIndexPattern)) ||
|
convertKueryToElasticSearchQuery(options.filterQuery, derivedIndexPattern)) ||
|
||||||
|
@ -74,7 +72,7 @@ export function useMetricsExplorerData(
|
||||||
return decodeOrThrow(metricsExplorerResponseRT)(response);
|
return decodeOrThrow(metricsExplorerResponseRT)(response);
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => lastPage.pageInfo,
|
getNextPageParam: (lastPage) => lastPage.pageInfo,
|
||||||
enabled: enabled && !!fromTimestamp && !!toTimestamp && !!http && !!source,
|
enabled: enabled && !!fromTimestamp && !!toTimestamp && !!http && !!derivedIndexPattern.title,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
import * as rt from 'io-ts';
|
import * as rt from 'io-ts';
|
||||||
import { CasesUiStart } from '@kbn/cases-plugin/public';
|
import { CasesUiStart } from '@kbn/cases-plugin/public';
|
||||||
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
import { DataPublicPluginStart, SerializedSearchSourceFields } from '@kbn/data-plugin/public';
|
||||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
import { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||||
import { DiscoverStart } from '@kbn/discover-plugin/public';
|
import { DiscoverStart } from '@kbn/discover-plugin/public';
|
||||||
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||||
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
||||||
|
@ -38,6 +38,7 @@ import { ObservabilityPublicStart } from '../../plugin';
|
||||||
import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options';
|
import { MetricsExplorerOptions } from './hooks/use_metrics_explorer_options';
|
||||||
|
|
||||||
export interface AlertContextMeta {
|
export interface AlertContextMeta {
|
||||||
|
adHocDataViewList: DataView[];
|
||||||
currentOptions?: Partial<MetricsExplorerOptions>;
|
currentOptions?: Partial<MetricsExplorerOptions>;
|
||||||
series?: MetricsExplorerSeries;
|
series?: MetricsExplorerSeries;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +95,7 @@ export interface AlertParams {
|
||||||
filterQueryText?: string;
|
filterQueryText?: string;
|
||||||
alertOnNoData?: boolean;
|
alertOnNoData?: boolean;
|
||||||
alertOnGroupDisappear?: boolean;
|
alertOnGroupDisappear?: boolean;
|
||||||
|
searchConfiguration: SerializedSearchSourceFields;
|
||||||
shouldDropPartialBuckets?: boolean;
|
shouldDropPartialBuckets?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,142 +0,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 createContainer from 'constate';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
|
||||||
import { IHttpFetchError } from '@kbn/core-http-browser';
|
|
||||||
import {
|
|
||||||
MetricsSourceConfiguration,
|
|
||||||
MetricsSourceConfigurationResponse,
|
|
||||||
PartialMetricsSourceConfigurationProperties,
|
|
||||||
} from '../../../../common/threshold_rule/types';
|
|
||||||
import { MissingHttpClientException } from './source_errors';
|
|
||||||
import { useTrackedPromise } from '../hooks/use_tracked_promise';
|
|
||||||
import { useSourceNotifier } from './notifications';
|
|
||||||
|
|
||||||
export const pickIndexPattern = (
|
|
||||||
source: MetricsSourceConfiguration | undefined,
|
|
||||||
type: 'metrics'
|
|
||||||
) => {
|
|
||||||
if (!source) {
|
|
||||||
return 'unknown-index';
|
|
||||||
}
|
|
||||||
if (type === 'metrics') {
|
|
||||||
return source.configuration.metricAlias;
|
|
||||||
}
|
|
||||||
return `${source.configuration.metricAlias}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSource = ({ sourceId }: { sourceId: string }) => {
|
|
||||||
const { services } = useKibana();
|
|
||||||
|
|
||||||
const notify = useSourceNotifier();
|
|
||||||
|
|
||||||
const fetchService = services.http;
|
|
||||||
const API_URL = `/api/metrics/source/${sourceId}`;
|
|
||||||
|
|
||||||
const [source, setSource] = useState<MetricsSourceConfiguration | undefined>(undefined);
|
|
||||||
|
|
||||||
const [loadSourceRequest, loadSource] = useTrackedPromise(
|
|
||||||
{
|
|
||||||
cancelPreviousOn: 'resolution',
|
|
||||||
createPromise: () => {
|
|
||||||
if (!fetchService) {
|
|
||||||
throw new MissingHttpClientException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetchService.fetch<MetricsSourceConfigurationResponse>(API_URL, { method: 'GET' });
|
|
||||||
},
|
|
||||||
onResolve: (response) => {
|
|
||||||
if (response) {
|
|
||||||
setSource(response.source);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[fetchService, sourceId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [createSourceConfigurationRequest, createSourceConfiguration] = useTrackedPromise(
|
|
||||||
{
|
|
||||||
createPromise: async (sourceProperties: PartialMetricsSourceConfigurationProperties) => {
|
|
||||||
if (!fetchService) {
|
|
||||||
throw new MissingHttpClientException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return await fetchService.patch<MetricsSourceConfigurationResponse>(API_URL, {
|
|
||||||
method: 'PATCH',
|
|
||||||
body: JSON.stringify(sourceProperties),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onResolve: (response) => {
|
|
||||||
if (response) {
|
|
||||||
notify.updateSuccess();
|
|
||||||
setSource(response.source);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onReject: (error) => {
|
|
||||||
notify.updateFailure((error as IHttpFetchError<{ message: string }>).body?.message);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[fetchService, sourceId]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadSource();
|
|
||||||
}, [loadSource, sourceId]);
|
|
||||||
|
|
||||||
const createDerivedIndexPattern = () => {
|
|
||||||
return {
|
|
||||||
fields: source?.status ? source.status.indexFields : [],
|
|
||||||
title: pickIndexPattern(source, 'metrics'),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasFailedLoadingSource = loadSourceRequest.state === 'rejected';
|
|
||||||
const isUninitialized = loadSourceRequest.state === 'uninitialized';
|
|
||||||
const isLoadingSource = loadSourceRequest.state === 'pending';
|
|
||||||
const isLoading = isLoadingSource || createSourceConfigurationRequest.state === 'pending';
|
|
||||||
|
|
||||||
const sourceExists = source ? !!source.version : undefined;
|
|
||||||
|
|
||||||
const metricIndicesExist = Boolean(source?.status?.metricIndicesExist);
|
|
||||||
|
|
||||||
const version = source?.version;
|
|
||||||
|
|
||||||
return {
|
|
||||||
createSourceConfiguration,
|
|
||||||
createDerivedIndexPattern,
|
|
||||||
isLoading,
|
|
||||||
isLoadingSource,
|
|
||||||
isUninitialized,
|
|
||||||
hasFailedLoadingSource,
|
|
||||||
loadSource,
|
|
||||||
loadSourceRequest,
|
|
||||||
loadSourceFailureMessage: hasFailedLoadingSource ? `${loadSourceRequest.value}` : undefined,
|
|
||||||
metricIndicesExist,
|
|
||||||
source,
|
|
||||||
sourceExists,
|
|
||||||
sourceId,
|
|
||||||
updateSourceConfiguration: createSourceConfiguration,
|
|
||||||
version,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const [SourceProvider, useSourceContext] = createContainer(useSource);
|
|
||||||
|
|
||||||
export const withSourceProvider =
|
|
||||||
<ComponentProps,>(Component: React.FunctionComponent<ComponentProps>) =>
|
|
||||||
(sourceId = 'default') => {
|
|
||||||
// eslint-disable-next-line react/function-component-definition
|
|
||||||
return function ComponentWithSourceProvider(props: ComponentProps) {
|
|
||||||
return (
|
|
||||||
<SourceProvider sourceId={sourceId}>
|
|
||||||
<Component {...props} />
|
|
||||||
</SourceProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -71,6 +71,33 @@ const data = {
|
||||||
timefilter: jest.fn(),
|
timefilter: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
search: {
|
||||||
|
searchSource: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataViewEditor = {
|
||||||
|
createStart() {
|
||||||
|
return {
|
||||||
|
userPermissions: {
|
||||||
|
editDataView: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataViews = {
|
||||||
|
createStart() {
|
||||||
|
return {
|
||||||
|
getIds: jest.fn().mockImplementation(() => []),
|
||||||
|
get: jest.fn(),
|
||||||
|
create: jest.fn().mockImplementation(() => ({
|
||||||
|
fields: {
|
||||||
|
getByName: jest.fn(),
|
||||||
|
},
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -81,6 +108,8 @@ export const observabilityPublicPluginsStartMock = {
|
||||||
cases: mockCasesContract(),
|
cases: mockCasesContract(),
|
||||||
triggersActionsUi: triggersActionsUiStartMock.createStart(),
|
triggersActionsUi: triggersActionsUiStartMock.createStart(),
|
||||||
data: data.createStart(),
|
data: data.createStart(),
|
||||||
|
dataViews: dataViews.createStart(),
|
||||||
|
dataViewEditor: dataViewEditor.createStart(),
|
||||||
lens: null,
|
lens: null,
|
||||||
discover: null,
|
discover: null,
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { BehaviorSubject, from } from 'rxjs';
|
import { BehaviorSubject, from } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||||
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||||
import {
|
import {
|
||||||
AppDeepLink,
|
AppDeepLink,
|
||||||
|
@ -103,6 +104,7 @@ export interface ObservabilityPublicPluginsStart {
|
||||||
charts: ChartsPluginStart;
|
charts: ChartsPluginStart;
|
||||||
data: DataPublicPluginStart;
|
data: DataPublicPluginStart;
|
||||||
dataViews: DataViewsPublicPluginStart;
|
dataViews: DataViewsPublicPluginStart;
|
||||||
|
dataViewEditor: DataViewEditorStart;
|
||||||
discover: DiscoverStart;
|
discover: DiscoverStart;
|
||||||
embeddable: EmbeddableStart;
|
embeddable: EmbeddableStart;
|
||||||
exploratoryView: ExploratoryViewPublicStart;
|
exploratoryView: ExploratoryViewPublicStart;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { ALERT_REASON } from '@kbn/rule-data-utils';
|
import { ALERT_REASON } from '@kbn/rule-data-utils';
|
||||||
|
@ -16,8 +17,8 @@ import {
|
||||||
SLO_BURN_RATE_RULE_TYPE_ID,
|
SLO_BURN_RATE_RULE_TYPE_ID,
|
||||||
} from '../../common/constants';
|
} from '../../common/constants';
|
||||||
import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation';
|
import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation';
|
||||||
import { validateMetricThreshold } from '../pages/threshold/components/validation';
|
import { validateMetricThreshold } from '../components/threshold/components/validation';
|
||||||
import { formatReason } from '../pages/threshold/rule_data_formatters';
|
import { formatReason } from '../components/threshold/rule_data_formatters';
|
||||||
|
|
||||||
const sloBurnRateDefaultActionMessage = i18n.translate(
|
const sloBurnRateDefaultActionMessage = i18n.translate(
|
||||||
'xpack.observability.slo.rules.burnRate.defaultActionMessage',
|
'xpack.observability.slo.rules.burnRate.defaultActionMessage',
|
||||||
|
@ -91,7 +92,7 @@ export const registerObservabilityRuleTypes = (
|
||||||
documentationUrl(docLinks) {
|
documentationUrl(docLinks) {
|
||||||
return `${docLinks.links.observability.threshold}`;
|
return `${docLinks.links.observability.threshold}`;
|
||||||
},
|
},
|
||||||
ruleParamsExpression: lazy(() => import('../pages/threshold/components/expression')),
|
ruleParamsExpression: lazy(() => import('../components/threshold/components/expression')),
|
||||||
validate: validateMetricThreshold,
|
validate: validateMetricThreshold,
|
||||||
defaultActionMessage: i18n.translate(
|
defaultActionMessage: i18n.translate(
|
||||||
'xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage',
|
'xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage',
|
||||||
|
@ -106,7 +107,7 @@ export const registerObservabilityRuleTypes = (
|
||||||
requiresAppContext: false,
|
requiresAppContext: false,
|
||||||
format: formatReason,
|
format: formatReason,
|
||||||
alertDetailsAppSection: lazy(
|
alertDetailsAppSection: lazy(
|
||||||
() => import('../pages/threshold/components/alert_details_app_section')
|
() => import('../components/threshold/components/alert_details_app_section')
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
MetricsExplorerTimeOptions,
|
MetricsExplorerTimeOptions,
|
||||||
MetricsExplorerTimestampsRT,
|
MetricsExplorerTimestampsRT,
|
||||||
MetricsExplorerYAxisMode,
|
MetricsExplorerYAxisMode,
|
||||||
} from '../pages/threshold/hooks/use_metrics_explorer_options';
|
} from '../components/threshold/hooks/use_metrics_explorer_options';
|
||||||
|
|
||||||
export const options: MetricsExplorerOptions = {
|
export const options: MetricsExplorerOptions = {
|
||||||
limit: 3,
|
limit: 3,
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { schema } from '@kbn/config-schema';
|
import { schema } from '@kbn/config-schema';
|
||||||
|
import { extractReferences, injectReferences } from '@kbn/data-plugin/common';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { IRuleTypeAlerts } from '@kbn/alerting-plugin/server';
|
import { IRuleTypeAlerts } from '@kbn/alerting-plugin/server';
|
||||||
import { IBasePath, Logger } from '@kbn/core/server';
|
import { IBasePath, Logger } from '@kbn/core/server';
|
||||||
|
@ -16,10 +17,11 @@ import {
|
||||||
IRuleDataClient,
|
IRuleDataClient,
|
||||||
} from '@kbn/rule-registry-plugin/server';
|
} from '@kbn/rule-registry-plugin/server';
|
||||||
import { LicenseType } from '@kbn/licensing-plugin/server';
|
import { LicenseType } from '@kbn/licensing-plugin/server';
|
||||||
import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants';
|
import { EsQueryRuleParamsExtractedParams } from '@kbn/stack-alerts-plugin/server/rule_types/es_query/rule_type_params';
|
||||||
import { observabilityFeatureId } from '../../../../common';
|
import { observabilityFeatureId } from '../../../../common';
|
||||||
import { Comparator } from '../../../../common/threshold_rule/types';
|
import { Comparator } from '../../../../common/threshold_rule/types';
|
||||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants';
|
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants';
|
||||||
|
import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alertDetailUrlActionVariableDescription,
|
alertDetailUrlActionVariableDescription,
|
||||||
|
@ -148,7 +150,6 @@ export function thresholdRuleType(
|
||||||
validate: validateIsStringElasticsearchJSONFilter,
|
validate: validateIsStringElasticsearchJSONFilter,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
sourceId: schema.string(),
|
|
||||||
alertOnNoData: schema.maybe(schema.boolean()),
|
alertOnNoData: schema.maybe(schema.boolean()),
|
||||||
alertOnGroupDisappear: schema.maybe(schema.boolean()),
|
alertOnGroupDisappear: schema.maybe(schema.boolean()),
|
||||||
},
|
},
|
||||||
|
@ -203,6 +204,21 @@ export function thresholdRuleType(
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
useSavedObjectReferences: {
|
||||||
|
// TODO revisit types https://github.com/elastic/kibana/issues/159714
|
||||||
|
extractReferences: (params: any) => {
|
||||||
|
const [searchConfiguration, references] = extractReferences(params.searchConfiguration);
|
||||||
|
const newParams = { ...params, searchConfiguration } as EsQueryRuleParamsExtractedParams;
|
||||||
|
|
||||||
|
return { params: newParams, references };
|
||||||
|
},
|
||||||
|
injectReferences: (params: any, references: any) => {
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
searchConfiguration: injectReferences(params.searchConfiguration, references),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
producer: observabilityFeatureId,
|
producer: observabilityFeatureId,
|
||||||
getSummarizedAlerts: getSummarizedAlerts(),
|
getSummarizedAlerts: getSummarizedAlerts(),
|
||||||
alerts: MetricsRulesTypeAlertDefinition,
|
alerts: MetricsRulesTypeAlertDefinition,
|
||||||
|
|
|
@ -1,237 +0,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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const bucketsA = (from: number) => [
|
|
||||||
{
|
|
||||||
doc_count: null,
|
|
||||||
aggregatedValue: { value: null, values: [{ key: 95.0, value: null }] },
|
|
||||||
from_as_string: new Date(from).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 2,
|
|
||||||
aggregatedValue: { value: 0.5, values: [{ key: 95.0, value: 0.5 }] },
|
|
||||||
from_as_string: new Date(from + 60000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 2,
|
|
||||||
aggregatedValue: { value: 0.5, values: [{ key: 95.0, value: 0.5 }] },
|
|
||||||
from_as_string: new Date(from + 120000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 2,
|
|
||||||
aggregatedValue: { value: 0.5, values: [{ key: 95.0, value: 0.5 }] },
|
|
||||||
from_as_string: new Date(from + 180000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 3,
|
|
||||||
aggregatedValue: { value: 1.0, values: [{ key: 95.0, value: 1.0 }] },
|
|
||||||
from_as_string: new Date(from + 240000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 1,
|
|
||||||
aggregatedValue: { value: 1.0, values: [{ key: 95.0, value: 1.0 }] },
|
|
||||||
from_as_string: new Date(from + 300000).toISOString(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const bucketsB = (from: number) => [
|
|
||||||
{
|
|
||||||
doc_count: 0,
|
|
||||||
aggregatedValue: { value: null, values: [{ key: 99.0, value: null }] },
|
|
||||||
from_as_string: new Date(from).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 4,
|
|
||||||
aggregatedValue: { value: 2.5, values: [{ key: 99.0, value: 2.5 }] },
|
|
||||||
from_as_string: new Date(from + 60000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 4,
|
|
||||||
aggregatedValue: { value: 2.5, values: [{ key: 99.0, value: 2.5 }] },
|
|
||||||
from_as_string: new Date(from + 120000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 4,
|
|
||||||
aggregatedValue: { value: 2.5, values: [{ key: 99.0, value: 2.5 }] },
|
|
||||||
from_as_string: new Date(from + 180000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 5,
|
|
||||||
aggregatedValue: { value: 3.5, values: [{ key: 99.0, value: 3.5 }] },
|
|
||||||
from_as_string: new Date(from + 240000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 1,
|
|
||||||
aggregatedValue: { value: 3, values: [{ key: 99.0, value: 3 }] },
|
|
||||||
from_as_string: new Date(from + 300000).toISOString(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const bucketsC = (from: number) => [
|
|
||||||
{
|
|
||||||
doc_count: 0,
|
|
||||||
aggregatedValue: { value: null },
|
|
||||||
from_as_string: new Date(from).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 2,
|
|
||||||
aggregatedValue: { value: 0.5 },
|
|
||||||
from_as_string: new Date(from + 60000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 2,
|
|
||||||
aggregatedValue: { value: 0.5 },
|
|
||||||
from_as_string: new Date(from + 120000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 2,
|
|
||||||
aggregatedValue: { value: 0.5 },
|
|
||||||
from_as_string: new Date(from + 180000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 3,
|
|
||||||
aggregatedValue: { value: 16 },
|
|
||||||
from_as_string: new Date(from + 240000).toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
doc_count: 1,
|
|
||||||
aggregatedValue: { value: 3 },
|
|
||||||
from_as_string: new Date(from + 300000).toISOString(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const basicMetricResponse = () => ({
|
|
||||||
hits: {
|
|
||||||
total: {
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
aggregations: {
|
|
||||||
aggregatedValue: { value: 1.0, values: [{ key: 95.0, value: 1.0 }] },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const alternateMetricResponse = () => ({
|
|
||||||
hits: {
|
|
||||||
total: {
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
aggregations: {
|
|
||||||
aggregatedValue: { value: 3, values: [{ key: 99.0, value: 3 }] },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const emptyMetricResponse = {
|
|
||||||
aggregations: {
|
|
||||||
aggregatedIntervals: {
|
|
||||||
buckets: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const emptyRateResponse = (from: number) => ({
|
|
||||||
aggregations: {
|
|
||||||
aggregatedIntervals: {
|
|
||||||
buckets: [
|
|
||||||
{
|
|
||||||
doc_count: 2,
|
|
||||||
aggregatedValueMax: { value: null },
|
|
||||||
from_as_string: new Date(from).toISOString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const basicCompositeResponse = (from: number) => ({
|
|
||||||
aggregations: {
|
|
||||||
groupings: {
|
|
||||||
after_key: { groupBy0: 'foo' },
|
|
||||||
buckets: [
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
groupBy0: 'a',
|
|
||||||
},
|
|
||||||
aggregatedIntervals: {
|
|
||||||
buckets: bucketsA(from),
|
|
||||||
},
|
|
||||||
doc_count: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
groupBy0: 'b',
|
|
||||||
},
|
|
||||||
aggregatedIntervals: {
|
|
||||||
buckets: bucketsB(from),
|
|
||||||
},
|
|
||||||
doc_count: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hits: {
|
|
||||||
total: {
|
|
||||||
value: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const alternateCompositeResponse = (from: number) => ({
|
|
||||||
aggregations: {
|
|
||||||
groupings: {
|
|
||||||
after_key: { groupBy0: 'foo' },
|
|
||||||
buckets: [
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
groupBy0: 'a',
|
|
||||||
},
|
|
||||||
aggregatedIntervals: {
|
|
||||||
buckets: bucketsB(from),
|
|
||||||
},
|
|
||||||
doc_count: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
groupBy0: 'b',
|
|
||||||
},
|
|
||||||
aggregatedIntervals: {
|
|
||||||
buckets: bucketsA(from),
|
|
||||||
},
|
|
||||||
doc_count: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
groupBy0: 'c',
|
|
||||||
},
|
|
||||||
aggregatedIntervals: {
|
|
||||||
buckets: bucketsC(from),
|
|
||||||
},
|
|
||||||
doc_count: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hits: {
|
|
||||||
total: {
|
|
||||||
value: 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const compositeEndResponse = {
|
|
||||||
aggregations: {},
|
|
||||||
hits: { total: { value: 0 } },
|
|
||||||
};
|
|
||||||
|
|
||||||
export const changedSourceIdResponse = (from: number) => ({
|
|
||||||
aggregations: {
|
|
||||||
aggregatedIntervals: {
|
|
||||||
buckets: bucketsC(from),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -120,7 +120,7 @@ export const createMetricThresholdExecutor = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: check if we need to use "savedObjectsClient"=> https://github.com/elastic/kibana/issues/159340
|
// TODO: check if we need to use "savedObjectsClient"=> https://github.com/elastic/kibana/issues/159340
|
||||||
const { alertWithLifecycle, getAlertUuid, getAlertByAlertUuid, dataViews } = services;
|
const { alertWithLifecycle, getAlertUuid, getAlertByAlertUuid, searchSourceClient } = services;
|
||||||
|
|
||||||
const alertFactory: MetricThresholdAlertFactory = (
|
const alertFactory: MetricThresholdAlertFactory = (
|
||||||
id,
|
id,
|
||||||
|
@ -138,9 +138,8 @@ export const createMetricThresholdExecutor = ({
|
||||||
...flattenAdditionalContext(additionalContext),
|
...flattenAdditionalContext(additionalContext),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// TODO: check if we need to use "sourceId"
|
|
||||||
const { alertOnNoData, alertOnGroupDisappear: _alertOnGroupDisappear } = params as {
|
const { alertOnNoData, alertOnGroupDisappear: _alertOnGroupDisappear } = params as {
|
||||||
sourceId?: string;
|
|
||||||
alertOnNoData: boolean;
|
alertOnNoData: boolean;
|
||||||
alertOnGroupDisappear: boolean | undefined;
|
alertOnGroupDisappear: boolean | undefined;
|
||||||
};
|
};
|
||||||
|
@ -188,9 +187,9 @@ export const createMetricThresholdExecutor = ({
|
||||||
alertOnGroupDisappear && filterQueryIsSame && groupByIsSame && state.missingGroups
|
alertOnGroupDisappear && filterQueryIsSame && groupByIsSame && state.missingGroups
|
||||||
? state.missingGroups
|
? state.missingGroups
|
||||||
: [];
|
: [];
|
||||||
// TODO: check the DATA VIEW
|
|
||||||
const defaultDataView = await dataViews.getDefaultDataView();
|
const initialSearchSource = await searchSourceClient.create(params.searchConfiguration!);
|
||||||
const dataView = defaultDataView?.getIndexPattern();
|
const dataView = initialSearchSource.getField('index')!.getIndexPattern();
|
||||||
if (!dataView) {
|
if (!dataView) {
|
||||||
throw new Error('No matched data view');
|
throw new Error('No matched data view');
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ export interface MetricAnomalyParams {
|
||||||
nodeType: rt.TypeOf<typeof metricAnomalyNodeTypeRT>;
|
nodeType: rt.TypeOf<typeof metricAnomalyNodeTypeRT>;
|
||||||
metric: rt.TypeOf<typeof metricAnomalyMetricRT>;
|
metric: rt.TypeOf<typeof metricAnomalyMetricRT>;
|
||||||
alertInterval?: string;
|
alertInterval?: string;
|
||||||
sourceId?: string;
|
|
||||||
spaceId?: string;
|
spaceId?: string;
|
||||||
threshold: Exclude<ML_ANOMALY_THRESHOLD, ML_ANOMALY_THRESHOLD.LOW>;
|
threshold: Exclude<ML_ANOMALY_THRESHOLD, ML_ANOMALY_THRESHOLD.LOW>;
|
||||||
influencerFilter: rt.TypeOf<typeof metricAnomalyInfluencerFilterRT> | undefined;
|
influencerFilter: rt.TypeOf<typeof metricAnomalyInfluencerFilterRT> | undefined;
|
||||||
|
@ -48,7 +47,6 @@ export interface MetricAnomalyParams {
|
||||||
interface BaseMetricExpressionParams {
|
interface BaseMetricExpressionParams {
|
||||||
timeSize: number;
|
timeSize: number;
|
||||||
timeUnit: TimeUnitChar;
|
timeUnit: TimeUnitChar;
|
||||||
sourceId?: string;
|
|
||||||
threshold: number[];
|
threshold: number[];
|
||||||
comparator: Comparator;
|
comparator: Comparator;
|
||||||
warningComparator?: Comparator;
|
warningComparator?: Comparator;
|
||||||
|
|
|
@ -77,7 +77,9 @@
|
||||||
"@kbn/safer-lodash-set",
|
"@kbn/safer-lodash-set",
|
||||||
"@kbn/core-http-server",
|
"@kbn/core-http-server",
|
||||||
"@kbn/cloud-chat-plugin",
|
"@kbn/cloud-chat-plugin",
|
||||||
"@kbn/cloud-plugin"
|
"@kbn/cloud-plugin",
|
||||||
|
"@kbn/stack-alerts-plugin",
|
||||||
|
"@kbn/data-view-editor-plugin"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"target/**/*"
|
"target/**/*"
|
||||||
|
|
|
@ -7,4 +7,6 @@
|
||||||
|
|
||||||
import { StackAlertsPublicPlugin } from './plugin';
|
import { StackAlertsPublicPlugin } from './plugin';
|
||||||
|
|
||||||
|
export { DataViewSelectPopover } from './rule_types/components/data_view_select_popover';
|
||||||
|
|
||||||
export const plugin = () => new StackAlertsPublicPlugin();
|
export const plugin = () => new StackAlertsPublicPlugin();
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||||
import { DataViewSelectPopover, DataViewSelectPopoverProps } from './data_view_select_popover';
|
import { DataViewSelectPopover, DataViewSelectPopoverProps } from './data_view_select_popover';
|
||||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
|
||||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
|
import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
|
||||||
|
@ -24,12 +23,6 @@ const selectedDataView = {
|
||||||
getName: () => 'kibana_sample_data_logs',
|
getName: () => 'kibana_sample_data_logs',
|
||||||
} as unknown as DataView;
|
} as unknown as DataView;
|
||||||
|
|
||||||
const props: DataViewSelectPopoverProps = {
|
|
||||||
onSelectDataView: () => {},
|
|
||||||
onChangeMetaData: () => {},
|
|
||||||
dataView: selectedDataView,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataViewIds = ['mock-data-logs-id', 'mock-ecommerce-id', 'mock-test-id', 'mock-ad-hoc-id'];
|
const dataViewIds = ['mock-data-logs-id', 'mock-ecommerce-id', 'mock-test-id', 'mock-ad-hoc-id'];
|
||||||
|
|
||||||
const dataViewOptions = [
|
const dataViewOptions = [
|
||||||
|
@ -80,15 +73,15 @@ const mount = () => {
|
||||||
Promise.resolve(dataViewOptions.find((current) => current.id === id))
|
Promise.resolve(dataViewOptions.find((current) => current.id === id))
|
||||||
);
|
);
|
||||||
const dataViewEditorMock = dataViewEditorPluginMock.createStartContract();
|
const dataViewEditorMock = dataViewEditorPluginMock.createStartContract();
|
||||||
|
const props: DataViewSelectPopoverProps = {
|
||||||
|
dependencies: { dataViews: dataViewsMock, dataViewEditor: dataViewEditorMock },
|
||||||
|
onSelectDataView: () => {},
|
||||||
|
onChangeMetaData: () => {},
|
||||||
|
dataView: selectedDataView,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wrapper: mountWithIntl(
|
wrapper: mountWithIntl(<DataViewSelectPopover {...props} />),
|
||||||
<KibanaContextProvider
|
|
||||||
services={{ dataViews: dataViewsMock, dataViewEditor: dataViewEditorMock }}
|
|
||||||
>
|
|
||||||
<DataViewSelectPopover {...props} />
|
|
||||||
</KibanaContextProvider>
|
|
||||||
),
|
|
||||||
dataViewsMock,
|
dataViewsMock,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,13 +20,17 @@ import {
|
||||||
EuiText,
|
EuiText,
|
||||||
useEuiPaddingCSS,
|
useEuiPaddingCSS,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||||
|
import type { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||||
import { DataViewSelector } from '@kbn/unified-search-plugin/public';
|
import { DataViewSelector } from '@kbn/unified-search-plugin/public';
|
||||||
import type { DataViewListItemEnhanced } from '@kbn/unified-search-plugin/public/dataview_picker/dataview_list';
|
import type { DataViewListItemEnhanced } from '@kbn/unified-search-plugin/public/dataview_picker/dataview_list';
|
||||||
import { useTriggerUiActionServices } from '../es_query/util';
|
|
||||||
import { EsQueryRuleMetaData } from '../es_query/types';
|
import { EsQueryRuleMetaData } from '../es_query/types';
|
||||||
|
|
||||||
export interface DataViewSelectPopoverProps {
|
export interface DataViewSelectPopoverProps {
|
||||||
|
dependencies: {
|
||||||
|
dataViews: DataViewsPublicPluginStart;
|
||||||
|
dataViewEditor: DataViewEditorStart;
|
||||||
|
};
|
||||||
dataView?: DataView;
|
dataView?: DataView;
|
||||||
metadata?: EsQueryRuleMetaData;
|
metadata?: EsQueryRuleMetaData;
|
||||||
onSelectDataView: (selectedDataView: DataView) => void;
|
onSelectDataView: (selectedDataView: DataView) => void;
|
||||||
|
@ -43,12 +47,12 @@ const toDataViewListItem = (dataView: DataView): DataViewListItemEnhanced => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopoverProps> = ({
|
export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopoverProps> = ({
|
||||||
|
dependencies: { dataViews, dataViewEditor },
|
||||||
metadata = { adHocDataViewList: [], isManagementPage: true },
|
metadata = { adHocDataViewList: [], isManagementPage: true },
|
||||||
dataView,
|
dataView,
|
||||||
onSelectDataView,
|
onSelectDataView,
|
||||||
onChangeMetaData,
|
onChangeMetaData,
|
||||||
}) => {
|
}) => {
|
||||||
const { dataViews, dataViewEditor } = useTriggerUiActionServices();
|
|
||||||
const [dataViewItems, setDataViewsItems] = useState<DataViewListItemEnhanced[]>([]);
|
const [dataViewItems, setDataViewsItems] = useState<DataViewListItemEnhanced[]>([]);
|
||||||
const [dataViewPopoverOpen, setDataViewPopoverOpen] = useState(false);
|
const [dataViewPopoverOpen, setDataViewPopoverOpen] = useState(false);
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,7 @@ const isSearchSourceParam = (action: LocalStateAction): action is SearchSourcePa
|
||||||
export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProps) => {
|
export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProps) => {
|
||||||
const services = useTriggerUiActionServices();
|
const services = useTriggerUiActionServices();
|
||||||
const unifiedSearch = services.unifiedSearch;
|
const unifiedSearch = services.unifiedSearch;
|
||||||
|
const { dataViews, dataViewEditor } = useTriggerUiActionServices();
|
||||||
const { searchSource, errors, initialSavedQuery, setParam, ruleParams } = props;
|
const { searchSource, errors, initialSavedQuery, setParam, ruleParams } = props;
|
||||||
const [savedQuery, setSavedQuery] = useState<SavedQuery>();
|
const [savedQuery, setSavedQuery] = useState<SavedQuery>();
|
||||||
|
|
||||||
|
@ -117,7 +118,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
|
||||||
);
|
);
|
||||||
|
|
||||||
const { index: dataView, query, filter: filters } = ruleConfiguration;
|
const { index: dataView, query, filter: filters } = ruleConfiguration;
|
||||||
const dataViews = useMemo(() => (dataView ? [dataView] : []), [dataView]);
|
const indexPatterns = useMemo(() => (dataView ? [dataView] : []), [dataView]);
|
||||||
|
|
||||||
const [esFields, setEsFields] = useState<FieldOption[]>(
|
const [esFields, setEsFields] = useState<FieldOption[]>(
|
||||||
dataView ? convertFieldSpecToFieldOption(dataView.fields.map((field) => field.toSpec())) : []
|
dataView ? convertFieldSpecToFieldOption(dataView.fields.map((field) => field.toSpec())) : []
|
||||||
|
@ -296,6 +297,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
|
||||||
</EuiTitle>
|
</EuiTitle>
|
||||||
<EuiSpacer size="s" />
|
<EuiSpacer size="s" />
|
||||||
<DataViewSelectPopover
|
<DataViewSelectPopover
|
||||||
|
dependencies={{ dataViews, dataViewEditor }}
|
||||||
dataView={dataView}
|
dataView={dataView}
|
||||||
metadata={props.metadata}
|
metadata={props.metadata}
|
||||||
onSelectDataView={onSelectDataView}
|
onSelectDataView={onSelectDataView}
|
||||||
|
@ -320,7 +322,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
|
||||||
suggestionsSize="s"
|
suggestionsSize="s"
|
||||||
displayStyle="inPage"
|
displayStyle="inPage"
|
||||||
query={query}
|
query={query}
|
||||||
indexPatterns={dataViews}
|
indexPatterns={indexPatterns}
|
||||||
savedQuery={savedQuery}
|
savedQuery={savedQuery}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
onFiltersUpdated={onUpdateFilters}
|
onFiltersUpdated={onUpdateFilters}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue