[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|
|---|---|

|![image](6f1115e2-e1f1-4348-b380-18b7ce2cacba)|

## 🧪 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:
Maryam Saeidi 2023-06-21 13:25:51 +02:00 committed by GitHub
parent a705225f6f
commit 87b80cb21b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 231 additions and 659 deletions

View file

@ -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 = '<',

View file

@ -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"]
} }
} }

View file

@ -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",

View file

@ -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();

View file

@ -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');

View file

@ -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()}
/> />
); );

View file

@ -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');

View file

@ -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}

View file

@ -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

View file

@ -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(

View file

@ -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(

View file

@ -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) => {

View file

@ -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' },
}); });

View file

@ -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,
}); });

View file

@ -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;
} }

View file

@ -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>
);
};
};

View file

@ -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,
}; };

View file

@ -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;

View file

@ -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')
), ),
}); });
} }

View file

@ -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,

View file

@ -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,

View file

@ -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),
},
},
});

View file

@ -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');
} }

View file

@ -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;

View file

@ -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/**/*"

View file

@ -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();

View file

@ -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,
}; };
}; };

View file

@ -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);

View file

@ -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}