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 { indexPatternRt } from '@kbn/io-ts-utils';
|
||||
import { ML_ANOMALY_THRESHOLD } from '@kbn/ml-anomaly-utils/anomaly_threshold';
|
||||
import { values } from 'lodash';
|
||||
import { Color } from './color_palette';
|
||||
|
@ -94,68 +93,14 @@ export const logDataViewReferenceRT = rt.type({
|
|||
dataViewId: rt.string,
|
||||
});
|
||||
|
||||
export type LogDataViewReference = rt.TypeOf<typeof logDataViewReferenceRT>;
|
||||
|
||||
// Index name
|
||||
export const logIndexNameReferenceRT = rt.type({
|
||||
type: rt.literal('index_name'),
|
||||
indexName: rt.string,
|
||||
});
|
||||
export type LogIndexNameReference = rt.TypeOf<typeof 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
|
||||
*/
|
||||
|
@ -180,32 +125,6 @@ export const metricsSourceStatusRT = rt.strict({
|
|||
|
||||
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 {
|
||||
GT = '>',
|
||||
LT = '<',
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"charts",
|
||||
"data",
|
||||
"dataViews",
|
||||
"dataViewEditor",
|
||||
"embeddable",
|
||||
"exploratoryView",
|
||||
"features",
|
||||
|
@ -29,7 +30,7 @@
|
|||
"visualizations"
|
||||
],
|
||||
"optionalPlugins": ["discover", "home", "licensing", "usageCollection", "cloud", "spaces"],
|
||||
"requiredBundles": ["data", "kibanaReact", "kibanaUtils", "unifiedSearch", "cloudChat", "spaces"],
|
||||
"requiredBundles": ["data", "kibanaReact", "kibanaUtils", "unifiedSearch", "cloudChat", "stackAlerts", "spaces"],
|
||||
"extraPublicDirs": ["common"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ Array [
|
|||
"chartType": "line",
|
||||
"derivedIndexPattern": Object {
|
||||
"fields": Array [],
|
||||
"title": "metricbeat-*",
|
||||
"title": "unknown-index",
|
||||
},
|
||||
"expression": Object {
|
||||
"aggType": "count",
|
||||
|
@ -35,9 +35,6 @@ Array [
|
|||
"host.hostname",
|
||||
],
|
||||
"hideTitle": true,
|
||||
"source": Object {
|
||||
"id": "default",
|
||||
},
|
||||
"timeRange": Object {
|
||||
"from": "2023-03-28T10:43:13.802Z",
|
||||
"to": "2023-03-29T13:14:09.581Z",
|
|
@ -16,7 +16,7 @@ import {
|
|||
buildMetricThresholdAlert,
|
||||
buildMetricThresholdRule,
|
||||
} from '../mocks/metric_threshold_rule';
|
||||
import { AlertDetailsAppSection } from './alert_details_app_section';
|
||||
import AlertDetailsAppSection from './alert_details_app_section';
|
||||
import { ExpressionChart } from './expression_chart';
|
||||
|
||||
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', () => {
|
||||
const queryClient = new QueryClient();
|
||||
const mockedSetAlertSummaryFields = jest.fn();
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-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 { Threshold } from './threshold';
|
||||
import { MetricsExplorerChartType } from '../hooks/use_metrics_explorer_options';
|
||||
import { useSourceContext, withSourceProvider } from '../helpers/source';
|
||||
import { MetricThresholdRuleTypeParams } from '../types';
|
||||
|
||||
// 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>>;
|
||||
}
|
||||
|
||||
export function AlertDetailsAppSection({
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function AlertDetailsAppSection({
|
||||
alert,
|
||||
rule,
|
||||
ruleLink,
|
||||
setAlertSummaryFields,
|
||||
}: AppSectionProps) {
|
||||
const { uiSettings, charts } = useKibana().services;
|
||||
const { source, createDerivedIndexPattern } = useSourceContext();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const derivedIndexPattern = useMemo(
|
||||
() => createDerivedIndexPattern(),
|
||||
[createDerivedIndexPattern]
|
||||
// TODO Use rule data view
|
||||
const derivedIndexPattern = useMemo<DataViewBase>(
|
||||
() => ({
|
||||
fields: [],
|
||||
title: 'unknown-index',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const chartProps = {
|
||||
theme: charts.theme.useChartsTheme(),
|
||||
|
@ -162,7 +166,6 @@ export function AlertDetailsAppSection({
|
|||
filterQuery={rule.params.filterQueryText}
|
||||
groupBy={rule.params.groupBy}
|
||||
hideTitle
|
||||
source={source}
|
||||
timeRange={timeRange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -173,5 +176,3 @@ export function AlertDetailsAppSection({
|
|||
</EuiFlexGroup>
|
||||
) : null;
|
||||
}
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default withSourceProvider<AppSectionProps>(AlertDetailsAppSection)('default');
|
|
@ -5,34 +5,35 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { kibanaStartMock } from '../../../utils/kibana_react.mock';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
// 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 { MetricsExplorerMetric } from '../../../../common/threshold_rule/metrics_explorer';
|
||||
import { Comparator } from '../../../../common/threshold_rule/types';
|
||||
|
||||
jest.mock('../helpers/source', () => ({
|
||||
withSourceProvider: () => jest.fn,
|
||||
useSourceContext: () => ({
|
||||
source: { id: 'default' },
|
||||
createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }),
|
||||
}),
|
||||
}));
|
||||
jest.mock('../../../utils/kibana_react');
|
||||
|
||||
jest.mock('../../../utils/kibana_react', () => ({
|
||||
useKibana: () => ({
|
||||
services: mockCoreMock.createStart(),
|
||||
}),
|
||||
}));
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
|
||||
const mockKibana = () => {
|
||||
useKibanaMock.mockReturnValue({
|
||||
...kibanaStartMock.startContract(),
|
||||
});
|
||||
};
|
||||
|
||||
const dataViewMock = dataViewPluginMocks.createStartContract();
|
||||
|
||||
describe('Expression', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockKibana();
|
||||
});
|
||||
|
||||
async function setup(currentOptions: {
|
||||
metrics?: MetricsExplorerMetric[];
|
||||
filterQuery?: string;
|
||||
|
@ -43,6 +44,7 @@ describe('Expression', () => {
|
|||
groupBy: undefined,
|
||||
filterQueryText: '',
|
||||
sourceId: 'default',
|
||||
searchConfiguration: {},
|
||||
};
|
||||
const wrapper = mountWithIntl(
|
||||
<Expressions
|
||||
|
@ -55,8 +57,10 @@ describe('Expression', () => {
|
|||
setRuleProperty={() => {}}
|
||||
metadata={{
|
||||
currentOptions,
|
||||
adHocDataViewList: [],
|
||||
}}
|
||||
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.
|
||||
*/
|
||||
|
||||
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiAccordion,
|
||||
|
@ -18,6 +19,10 @@ import {
|
|||
EuiText,
|
||||
EuiToolTip,
|
||||
} 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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { debounce } from 'lodash';
|
||||
|
@ -36,13 +41,12 @@ import { ExpressionRow } from './expression_row';
|
|||
import { MetricsExplorerKueryBar } from './kuery_bar';
|
||||
import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options';
|
||||
import { convertKueryToElasticSearchQuery } from '../helpers/kuery';
|
||||
import { useSourceContext, withSourceProvider } from '../helpers/source';
|
||||
import { MetricsExplorerGroupBy } from './group_by';
|
||||
const FILTER_TYPING_DEBOUNCE_MS = 500;
|
||||
|
||||
type Props = Omit<
|
||||
RuleTypeParamsExpressionProps<RuleTypeParams & AlertParams, AlertContextMeta>,
|
||||
'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch' | 'onChangeMetaData'
|
||||
'defaultActionGroupId' | 'actionGroups' | 'charts' | 'data' | 'unifiedSearch'
|
||||
>;
|
||||
|
||||
export const defaultExpression = {
|
||||
|
@ -53,18 +57,55 @@ export const defaultExpression = {
|
|||
timeUnit: 'm',
|
||||
} as MetricExpression;
|
||||
|
||||
export function Expressions(props: Props) {
|
||||
const { setRuleParams, ruleParams, errors, metadata } = props;
|
||||
const { docLinks } = useKibana().services;
|
||||
const { source, createDerivedIndexPattern } = useSourceContext();
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function Expressions(props: Props) {
|
||||
const { setRuleParams, ruleParams, errors, metadata, onChangeMetaData } = props;
|
||||
const { data, dataViews, dataViewEditor, docLinks } = useKibana().services;
|
||||
|
||||
const [timeSize, setTimeSize] = useState<number | undefined>(1);
|
||||
const [timeUnit, setTimeUnit] = useState<TimeUnitChar | undefined>('m');
|
||||
const derivedIndexPattern = useMemo(
|
||||
() => createDerivedIndexPattern(),
|
||||
[createDerivedIndexPattern]
|
||||
const [dataView, setDataView] = useState<DataView>();
|
||||
const [searchSource, setSearchSource] = useState<ISearchSource>();
|
||||
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>(() => {
|
||||
if (metadata?.currentOptions?.metrics) {
|
||||
return metadata.currentOptions as MetricsExplorerOptions;
|
||||
|
@ -76,31 +117,49 @@ export function Expressions(props: Props) {
|
|||
}
|
||||
}, [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(
|
||||
(id, e: MetricExpression) => {
|
||||
const exp = ruleParams.criteria ? ruleParams.criteria.slice() : [];
|
||||
exp[id] = e;
|
||||
setRuleParams('criteria', exp);
|
||||
const ruleCriteria = ruleParams.criteria ? ruleParams.criteria.slice() : [];
|
||||
ruleCriteria[id] = e;
|
||||
setRuleParams('criteria', ruleCriteria);
|
||||
},
|
||||
[setRuleParams, ruleParams.criteria]
|
||||
);
|
||||
|
||||
const addExpression = useCallback(() => {
|
||||
const exp = ruleParams.criteria?.slice() || [];
|
||||
exp.push({
|
||||
const ruleCriteria = ruleParams.criteria?.slice() || [];
|
||||
ruleCriteria.push({
|
||||
...defaultExpression,
|
||||
timeSize: timeSize ?? defaultExpression.timeSize,
|
||||
timeUnit: timeUnit ?? defaultExpression.timeUnit,
|
||||
});
|
||||
setRuleParams('criteria', exp);
|
||||
setRuleParams('criteria', ruleCriteria);
|
||||
}, [setRuleParams, ruleParams.criteria, timeSize, timeUnit]);
|
||||
|
||||
const removeExpression = useCallback(
|
||||
(id: number) => {
|
||||
const exp = ruleParams.criteria?.slice() || [];
|
||||
if (exp.length > 1) {
|
||||
exp.splice(id, 1);
|
||||
setRuleParams('criteria', exp);
|
||||
const ruleCriteria = ruleParams.criteria?.slice() || [];
|
||||
if (ruleCriteria.length > 1) {
|
||||
ruleCriteria.splice(id, 1);
|
||||
setRuleParams('criteria', ruleCriteria);
|
||||
}
|
||||
},
|
||||
[setRuleParams, ruleParams.criteria]
|
||||
|
@ -143,26 +202,25 @@ export function Expressions(props: Props) {
|
|||
|
||||
const updateTimeSize = useCallback(
|
||||
(ts: number | undefined) => {
|
||||
const criteria =
|
||||
const ruleCriteria =
|
||||
ruleParams.criteria?.map((c) => ({
|
||||
...c,
|
||||
timeSize: ts,
|
||||
})) || [];
|
||||
setTimeSize(ts || undefined);
|
||||
setRuleParams('criteria', criteria);
|
||||
setRuleParams('criteria', ruleCriteria);
|
||||
},
|
||||
[ruleParams.criteria, setRuleParams]
|
||||
);
|
||||
|
||||
const updateTimeUnit = useCallback(
|
||||
(tu: string) => {
|
||||
const criteria =
|
||||
ruleParams.criteria?.map((c) => ({
|
||||
const ruleCriteria = (ruleParams.criteria?.map((c) => ({
|
||||
...c,
|
||||
timeUnit: tu,
|
||||
})) || [];
|
||||
})) || []) as AlertParams['criteria'];
|
||||
setTimeUnit(tu as TimeUnitChar);
|
||||
setRuleParams('criteria', criteria as AlertParams['criteria']);
|
||||
setRuleParams('criteria', ruleCriteria);
|
||||
},
|
||||
[ruleParams.criteria, setRuleParams]
|
||||
);
|
||||
|
@ -230,17 +288,13 @@ export function Expressions(props: Props) {
|
|||
preFillAlertGroupBy();
|
||||
}
|
||||
|
||||
if (!ruleParams.sourceId) {
|
||||
setRuleParams('sourceId', source?.id || 'default');
|
||||
}
|
||||
|
||||
if (typeof ruleParams.alertOnNoData === 'undefined') {
|
||||
setRuleParams('alertOnNoData', true);
|
||||
}
|
||||
if (typeof ruleParams.alertOnGroupDisappear === 'undefined') {
|
||||
setRuleParams('alertOnGroupDisappear', true);
|
||||
}
|
||||
}, [metadata, source]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, [metadata]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const handleFieldSearchChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => onFilterChange(e.target.value),
|
||||
|
@ -283,7 +337,15 @@ export function Expressions(props: Props) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size={'m'} />
|
||||
<DataViewSelectPopover
|
||||
dependencies={{ dataViews, dataViewEditor }}
|
||||
dataView={dataView}
|
||||
onSelectDataView={onSelectDataView}
|
||||
onChangeMetaData={({ adHocDataViewList }) => {
|
||||
onChangeMetaData({ ...metadata, adHocDataViewList });
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiText size="xs">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
|
@ -298,7 +360,7 @@ export function Expressions(props: Props) {
|
|||
return (
|
||||
<ExpressionRow
|
||||
canDelete={(ruleParams.criteria && ruleParams.criteria.length > 1) || false}
|
||||
fields={derivedIndexPattern.fields}
|
||||
fields={derivedIndexPattern.fields as any}
|
||||
remove={removeExpression}
|
||||
addExpression={addExpression}
|
||||
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 || {}}
|
||||
dataView={derivedIndexPattern}
|
||||
>
|
||||
{/* Preview */}
|
||||
<ExpressionChart
|
||||
expression={e}
|
||||
derivedIndexPattern={derivedIndexPattern}
|
||||
source={source}
|
||||
filterQuery={ruleParams.filterQueryText}
|
||||
groupBy={ruleParams.groupBy}
|
||||
/>
|
||||
</ExpressionRow>
|
||||
);
|
||||
})}
|
||||
|
||||
<div style={{ marginLeft: 28 }}>
|
||||
<ForLastExpression
|
||||
timeWindowSize={timeSize}
|
||||
|
@ -328,7 +389,6 @@ export function Expressions(props: Props) {
|
|||
onChangeWindowUnit={updateTimeUnit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<EuiSpacer size={'m'} />
|
||||
<div>
|
||||
<EuiButtonEmpty
|
||||
|
@ -345,7 +405,6 @@ export function Expressions(props: Props) {
|
|||
/>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiAccordion
|
||||
id="advanced-options-accordion"
|
||||
|
@ -387,7 +446,6 @@ export function Expressions(props: Props) {
|
|||
</EuiPanel>
|
||||
</EuiAccordion>
|
||||
<EuiSpacer size={'m'} />
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.observability.threshold.rule.alertFlyout.filterLabel', {
|
||||
defaultMessage: 'Filter (optional)',
|
||||
|
@ -398,7 +456,7 @@ export function Expressions(props: Props) {
|
|||
fullWidth
|
||||
display="rowCompressed"
|
||||
>
|
||||
{(metadata && (
|
||||
{(metadata && derivedIndexPattern && (
|
||||
<MetricsExplorerKueryBar
|
||||
derivedIndexPattern={derivedIndexPattern}
|
||||
onChange={debouncedOnFilterChange}
|
||||
|
@ -414,7 +472,6 @@ export function Expressions(props: Props) {
|
|||
/>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.observability.threshold.rule.alertFlyout.createAlertPerText', {
|
||||
|
@ -432,7 +489,7 @@ export function Expressions(props: Props) {
|
|||
>
|
||||
<MetricsExplorerGroupBy
|
||||
onChange={onGroupByChange}
|
||||
fields={derivedIndexPattern.fields}
|
||||
fields={derivedIndexPattern.fields as any}
|
||||
options={{
|
||||
...options,
|
||||
groupBy: ruleParams.groupBy || undefined,
|
||||
|
@ -508,7 +565,3 @@ const docCountNoDataDisabledHelpText = i18n.translate(
|
|||
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 { MetricExpression } from '../types';
|
||||
import { ExpressionChart } from './expression_chart';
|
||||
import {
|
||||
Aggregators,
|
||||
Comparator,
|
||||
MetricsSourceConfiguration,
|
||||
} from '../../../../common/threshold_rule/types';
|
||||
import { Aggregators, Comparator } from '../../../../common/threshold_rule/types';
|
||||
|
||||
const mockStartServices = mockCoreMock.createStart();
|
||||
|
||||
|
@ -57,24 +53,10 @@ describe('ExpressionChart', () => {
|
|||
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(
|
||||
<ExpressionChart
|
||||
expression={expression}
|
||||
derivedIndexPattern={derivedIndexPattern}
|
||||
source={source}
|
||||
filterQuery={filterQuery}
|
||||
groupBy={groupBy}
|
||||
annotations={annotations}
|
|
@ -31,7 +31,6 @@ import { Color } from '../../../../common/threshold_rule/color_palette';
|
|||
import {
|
||||
MetricsExplorerChartType,
|
||||
MetricsExplorerOptionsMetric,
|
||||
MetricsSourceConfiguration,
|
||||
} from '../../../../common/threshold_rule/types';
|
||||
import { MetricExpression, TimeRange } from '../types';
|
||||
import { createFormatterForMetric } from '../helpers/create_formatter_for_metric';
|
||||
|
@ -57,7 +56,6 @@ interface Props {
|
|||
filterQuery?: string;
|
||||
groupBy?: string | string[];
|
||||
hideTitle?: boolean;
|
||||
source?: MetricsSourceConfiguration;
|
||||
timeRange?: TimeRange;
|
||||
}
|
||||
|
||||
|
@ -69,14 +67,12 @@ export function ExpressionChart({
|
|||
filterQuery,
|
||||
groupBy,
|
||||
hideTitle = false,
|
||||
source,
|
||||
timeRange,
|
||||
}: Props) {
|
||||
const { charts, uiSettings } = useKibana().services;
|
||||
const { isLoading, data } = useMetricsExplorerChartData(
|
||||
expression,
|
||||
derivedIndexPattern,
|
||||
source,
|
||||
filterQuery,
|
||||
groupBy,
|
||||
timeRange
|
|
@ -13,14 +13,6 @@ import { act } from 'react-dom/test-utils';
|
|||
import { MetricExpression } from '../types';
|
||||
import { ExpressionRow } from './expression_row';
|
||||
|
||||
jest.mock('../helpers/source', () => ({
|
||||
withSourceProvider: () => jest.fn,
|
||||
useSourceContext: () => ({
|
||||
source: { id: 'default' },
|
||||
createDerivedIndexPattern: () => ({ fields: [], title: 'metricbeat-*' }),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ExpressionRow', () => {
|
||||
async function setup(expression: MetricExpression) {
|
||||
const wrapper = mountWithIntl(
|
|
@ -144,20 +144,6 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
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 (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
|
@ -177,12 +163,6 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
|
|||
<EuiFlexItem grow>
|
||||
<StyledExpressionRow style={{ gap: aggType !== 'custom' ? 24 : 12 }}>
|
||||
<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
|
||||
data-test-subj="customEquationWhen"
|
||||
description={i18n.translate(
|
|
@ -9,10 +9,7 @@ import DateMath from '@kbn/datemath';
|
|||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { useMemo } from 'react';
|
||||
import { MetricExplorerCustomMetricAggregations } from '../../../../common/threshold_rule/metrics_explorer';
|
||||
import {
|
||||
MetricExpressionCustomMetric,
|
||||
MetricsSourceConfiguration,
|
||||
} from '../../../../common/threshold_rule/types';
|
||||
import { MetricExpressionCustomMetric } from '../../../../common/threshold_rule/types';
|
||||
import { MetricExpression, TimeRange } from '../types';
|
||||
import { useMetricsExplorerData } from './use_metrics_explorer_data';
|
||||
|
||||
|
@ -26,7 +23,6 @@ const DEFAULT_TIME_RANGE = {};
|
|||
export const useMetricsExplorerChartData = (
|
||||
expression: MetricExpression,
|
||||
derivedIndexPattern: DataViewBase,
|
||||
source?: MetricsSourceConfiguration,
|
||||
filterQuery?: string,
|
||||
groupBy?: string | string[],
|
||||
timeRange: TimeRange = DEFAULT_TIME_RANGE
|
||||
|
@ -53,11 +49,13 @@ export const useMetricsExplorerChartData = (
|
|||
],
|
||||
aggregation: expression.aggType || 'avg',
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
expression.aggType,
|
||||
expression.equation,
|
||||
expression.metric,
|
||||
expression.customMetrics,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
JSON.stringify(expression.customMetrics),
|
||||
filterQuery,
|
||||
groupBy,
|
||||
]
|
||||
|
@ -74,7 +72,7 @@ export const useMetricsExplorerChartData = (
|
|||
};
|
||||
}, [timeRange, timeSize, timeUnit]);
|
||||
|
||||
return useMetricsExplorerData(options, source?.configuration, derivedIndexPattern, timestamps);
|
||||
return useMetricsExplorerData(options, derivedIndexPattern, timestamps);
|
||||
};
|
||||
|
||||
const mapMetricThresholdMetricToMetricsExplorerMetric = (metric: MetricExpressionCustomMetric) => {
|
|
@ -17,13 +17,11 @@ import {
|
|||
MetricsExplorerTimestampsRT,
|
||||
} from './use_metrics_explorer_options';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { MetricsSourceConfigurationProperties } from '../../../../common/threshold_rule/types';
|
||||
import {
|
||||
createSeries,
|
||||
derivedIndexPattern,
|
||||
options,
|
||||
resp,
|
||||
source,
|
||||
timestamps,
|
||||
} from '../../../utils/metrics_explorer';
|
||||
|
||||
|
@ -54,20 +52,12 @@ const renderUseMetricsExplorerDataHook = () => {
|
|||
return renderHook(
|
||||
(props: {
|
||||
options: MetricsExplorerOptions;
|
||||
source: MetricsSourceConfigurationProperties | undefined;
|
||||
derivedIndexPattern: DataViewBase;
|
||||
timestamps: MetricsExplorerTimestampsRT;
|
||||
}) =>
|
||||
useMetricsExplorerData(
|
||||
props.options,
|
||||
props.source,
|
||||
props.derivedIndexPattern,
|
||||
props.timestamps
|
||||
),
|
||||
}) => useMetricsExplorerData(props.options, props.derivedIndexPattern, props.timestamps),
|
||||
{
|
||||
initialProps: {
|
||||
options,
|
||||
source,
|
||||
derivedIndexPattern,
|
||||
timestamps,
|
||||
},
|
||||
|
@ -163,7 +153,6 @@ describe('useMetricsExplorerData Hook', () => {
|
|||
aggregation: 'count',
|
||||
metrics: [{ aggregation: 'count' }],
|
||||
},
|
||||
source,
|
||||
derivedIndexPattern,
|
||||
timestamps,
|
||||
});
|
||||
|
@ -187,7 +176,6 @@ describe('useMetricsExplorerData Hook', () => {
|
|||
mockedFetch.mockResolvedValue(resp as any);
|
||||
rerender({
|
||||
options,
|
||||
source,
|
||||
derivedIndexPattern,
|
||||
timestamps: { fromTimestamp: 1678378092225, toTimestamp: 1678381693477, interval: '>=10s' },
|
||||
});
|
|
@ -9,7 +9,6 @@ import { DataViewBase } from '@kbn/es-query';
|
|||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { MetricsSourceConfigurationProperties } from '../../../../common/threshold_rule/types';
|
||||
import {
|
||||
MetricsExplorerResponse,
|
||||
metricsExplorerResponseRT,
|
||||
|
@ -24,7 +23,6 @@ import { decodeOrThrow } from '../helpers/runtime_types';
|
|||
|
||||
export function useMetricsExplorerData(
|
||||
options: MetricsExplorerOptions,
|
||||
source: MetricsSourceConfigurationProperties | undefined,
|
||||
derivedIndexPattern: DataViewBase,
|
||||
{ fromTimestamp, toTimestamp, interval }: MetricsExplorerTimestampsRT,
|
||||
enabled = true
|
||||
|
@ -35,7 +33,7 @@ export function useMetricsExplorerData(
|
|||
MetricsExplorerResponse,
|
||||
Error
|
||||
>({
|
||||
queryKey: ['metricExplorer', options, fromTimestamp, toTimestamp],
|
||||
queryKey: ['metricExplorer', options, fromTimestamp, toTimestamp, derivedIndexPattern.title],
|
||||
queryFn: async ({ signal, pageParam = { afterKey: null } }) => {
|
||||
if (!fromTimestamp || !toTimestamp) {
|
||||
throw new Error('Unable to parse timerange');
|
||||
|
@ -43,8 +41,8 @@ export function useMetricsExplorerData(
|
|||
if (!http) {
|
||||
throw new Error('HTTP service is unavailable');
|
||||
}
|
||||
if (!source) {
|
||||
throw new Error('Source is unavailable');
|
||||
if (!derivedIndexPattern.title) {
|
||||
throw new Error('Data view is unavailable');
|
||||
}
|
||||
|
||||
const { afterKey } = pageParam;
|
||||
|
@ -57,7 +55,7 @@ export function useMetricsExplorerData(
|
|||
groupBy: options.groupBy,
|
||||
afterKey,
|
||||
limit: options.limit,
|
||||
indexPattern: source.metricAlias,
|
||||
indexPattern: derivedIndexPattern.title,
|
||||
filterQuery:
|
||||
(options.filterQuery &&
|
||||
convertKueryToElasticSearchQuery(options.filterQuery, derivedIndexPattern)) ||
|
||||
|
@ -74,7 +72,7 @@ export function useMetricsExplorerData(
|
|||
return decodeOrThrow(metricsExplorerResponseRT)(response);
|
||||
},
|
||||
getNextPageParam: (lastPage) => lastPage.pageInfo,
|
||||
enabled: enabled && !!fromTimestamp && !!toTimestamp && !!http && !!source,
|
||||
enabled: enabled && !!fromTimestamp && !!toTimestamp && !!http && !!derivedIndexPattern.title,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
|
@ -7,8 +7,8 @@
|
|||
import * as rt from 'io-ts';
|
||||
import { CasesUiStart } from '@kbn/cases-plugin/public';
|
||||
import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { DataPublicPluginStart, SerializedSearchSourceFields } from '@kbn/data-plugin/public';
|
||||
import { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import { DiscoverStart } from '@kbn/discover-plugin/public';
|
||||
import { EmbeddableStart } from '@kbn/embeddable-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';
|
||||
|
||||
export interface AlertContextMeta {
|
||||
adHocDataViewList: DataView[];
|
||||
currentOptions?: Partial<MetricsExplorerOptions>;
|
||||
series?: MetricsExplorerSeries;
|
||||
}
|
||||
|
@ -94,6 +95,7 @@ export interface AlertParams {
|
|||
filterQueryText?: string;
|
||||
alertOnNoData?: boolean;
|
||||
alertOnGroupDisappear?: boolean;
|
||||
searchConfiguration: SerializedSearchSourceFields;
|
||||
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(),
|
||||
},
|
||||
},
|
||||
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(),
|
||||
triggersActionsUi: triggersActionsUiStartMock.createStart(),
|
||||
data: data.createStart(),
|
||||
dataViews: dataViews.createStart(),
|
||||
dataViewEditor: dataViewEditor.createStart(),
|
||||
lens: null,
|
||||
discover: null,
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { BehaviorSubject, from } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
|
||||
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import {
|
||||
AppDeepLink,
|
||||
|
@ -103,6 +104,7 @@ export interface ObservabilityPublicPluginsStart {
|
|||
charts: ChartsPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
dataViewEditor: DataViewEditorStart;
|
||||
discover: DiscoverStart;
|
||||
embeddable: EmbeddableStart;
|
||||
exploratoryView: ExploratoryViewPublicStart;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALERT_REASON } from '@kbn/rule-data-utils';
|
||||
|
@ -16,8 +17,8 @@ import {
|
|||
SLO_BURN_RATE_RULE_TYPE_ID,
|
||||
} from '../../common/constants';
|
||||
import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation';
|
||||
import { validateMetricThreshold } from '../pages/threshold/components/validation';
|
||||
import { formatReason } from '../pages/threshold/rule_data_formatters';
|
||||
import { validateMetricThreshold } from '../components/threshold/components/validation';
|
||||
import { formatReason } from '../components/threshold/rule_data_formatters';
|
||||
|
||||
const sloBurnRateDefaultActionMessage = i18n.translate(
|
||||
'xpack.observability.slo.rules.burnRate.defaultActionMessage',
|
||||
|
@ -91,7 +92,7 @@ export const registerObservabilityRuleTypes = (
|
|||
documentationUrl(docLinks) {
|
||||
return `${docLinks.links.observability.threshold}`;
|
||||
},
|
||||
ruleParamsExpression: lazy(() => import('../pages/threshold/components/expression')),
|
||||
ruleParamsExpression: lazy(() => import('../components/threshold/components/expression')),
|
||||
validate: validateMetricThreshold,
|
||||
defaultActionMessage: i18n.translate(
|
||||
'xpack.observability.threshold.rule.alerting.threshold.defaultActionMessage',
|
||||
|
@ -106,7 +107,7 @@ export const registerObservabilityRuleTypes = (
|
|||
requiresAppContext: false,
|
||||
format: formatReason,
|
||||
alertDetailsAppSection: lazy(
|
||||
() => import('../pages/threshold/components/alert_details_app_section')
|
||||
() => import('../components/threshold/components/alert_details_app_section')
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
MetricsExplorerTimeOptions,
|
||||
MetricsExplorerTimestampsRT,
|
||||
MetricsExplorerYAxisMode,
|
||||
} from '../pages/threshold/hooks/use_metrics_explorer_options';
|
||||
} from '../components/threshold/hooks/use_metrics_explorer_options';
|
||||
|
||||
export const options: MetricsExplorerOptions = {
|
||||
limit: 3,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { extractReferences, injectReferences } from '@kbn/data-plugin/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IRuleTypeAlerts } from '@kbn/alerting-plugin/server';
|
||||
import { IBasePath, Logger } from '@kbn/core/server';
|
||||
|
@ -16,10 +17,11 @@ import {
|
|||
IRuleDataClient,
|
||||
} from '@kbn/rule-registry-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 { Comparator } from '../../../../common/threshold_rule/types';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants';
|
||||
import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants';
|
||||
|
||||
import {
|
||||
alertDetailUrlActionVariableDescription,
|
||||
|
@ -148,7 +150,6 @@ export function thresholdRuleType(
|
|||
validate: validateIsStringElasticsearchJSONFilter,
|
||||
})
|
||||
),
|
||||
sourceId: schema.string(),
|
||||
alertOnNoData: 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,
|
||||
getSummarizedAlerts: getSummarizedAlerts(),
|
||||
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
|
||||
const { alertWithLifecycle, getAlertUuid, getAlertByAlertUuid, dataViews } = services;
|
||||
const { alertWithLifecycle, getAlertUuid, getAlertByAlertUuid, searchSourceClient } = services;
|
||||
|
||||
const alertFactory: MetricThresholdAlertFactory = (
|
||||
id,
|
||||
|
@ -138,9 +138,8 @@ export const createMetricThresholdExecutor = ({
|
|||
...flattenAdditionalContext(additionalContext),
|
||||
},
|
||||
});
|
||||
// TODO: check if we need to use "sourceId"
|
||||
|
||||
const { alertOnNoData, alertOnGroupDisappear: _alertOnGroupDisappear } = params as {
|
||||
sourceId?: string;
|
||||
alertOnNoData: boolean;
|
||||
alertOnGroupDisappear: boolean | undefined;
|
||||
};
|
||||
|
@ -188,9 +187,9 @@ export const createMetricThresholdExecutor = ({
|
|||
alertOnGroupDisappear && filterQueryIsSame && groupByIsSame && state.missingGroups
|
||||
? state.missingGroups
|
||||
: [];
|
||||
// TODO: check the DATA VIEW
|
||||
const defaultDataView = await dataViews.getDefaultDataView();
|
||||
const dataView = defaultDataView?.getIndexPattern();
|
||||
|
||||
const initialSearchSource = await searchSourceClient.create(params.searchConfiguration!);
|
||||
const dataView = initialSearchSource.getField('index')!.getIndexPattern();
|
||||
if (!dataView) {
|
||||
throw new Error('No matched data view');
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ export interface MetricAnomalyParams {
|
|||
nodeType: rt.TypeOf<typeof metricAnomalyNodeTypeRT>;
|
||||
metric: rt.TypeOf<typeof metricAnomalyMetricRT>;
|
||||
alertInterval?: string;
|
||||
sourceId?: string;
|
||||
spaceId?: string;
|
||||
threshold: Exclude<ML_ANOMALY_THRESHOLD, ML_ANOMALY_THRESHOLD.LOW>;
|
||||
influencerFilter: rt.TypeOf<typeof metricAnomalyInfluencerFilterRT> | undefined;
|
||||
|
@ -48,7 +47,6 @@ export interface MetricAnomalyParams {
|
|||
interface BaseMetricExpressionParams {
|
||||
timeSize: number;
|
||||
timeUnit: TimeUnitChar;
|
||||
sourceId?: string;
|
||||
threshold: number[];
|
||||
comparator: Comparator;
|
||||
warningComparator?: Comparator;
|
||||
|
|
|
@ -77,7 +77,9 @@
|
|||
"@kbn/safer-lodash-set",
|
||||
"@kbn/core-http-server",
|
||||
"@kbn/cloud-chat-plugin",
|
||||
"@kbn/cloud-plugin"
|
||||
"@kbn/cloud-plugin",
|
||||
"@kbn/stack-alerts-plugin",
|
||||
"@kbn/data-view-editor-plugin"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
|
|
|
@ -7,4 +7,6 @@
|
|||
|
||||
import { StackAlertsPublicPlugin } from './plugin';
|
||||
|
||||
export { DataViewSelectPopover } from './rule_types/components/data_view_select_popover';
|
||||
|
||||
export const plugin = () => new StackAlertsPublicPlugin();
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import React from 'react';
|
||||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
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 type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks';
|
||||
|
@ -24,12 +23,6 @@ const selectedDataView = {
|
|||
getName: () => 'kibana_sample_data_logs',
|
||||
} 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 dataViewOptions = [
|
||||
|
@ -80,15 +73,15 @@ const mount = () => {
|
|||
Promise.resolve(dataViewOptions.find((current) => current.id === id))
|
||||
);
|
||||
const dataViewEditorMock = dataViewEditorPluginMock.createStartContract();
|
||||
const props: DataViewSelectPopoverProps = {
|
||||
dependencies: { dataViews: dataViewsMock, dataViewEditor: dataViewEditorMock },
|
||||
onSelectDataView: () => {},
|
||||
onChangeMetaData: () => {},
|
||||
dataView: selectedDataView,
|
||||
};
|
||||
|
||||
return {
|
||||
wrapper: mountWithIntl(
|
||||
<KibanaContextProvider
|
||||
services={{ dataViews: dataViewsMock, dataViewEditor: dataViewEditorMock }}
|
||||
>
|
||||
<DataViewSelectPopover {...props} />
|
||||
</KibanaContextProvider>
|
||||
),
|
||||
wrapper: mountWithIntl(<DataViewSelectPopover {...props} />),
|
||||
dataViewsMock,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -20,13 +20,17 @@ import {
|
|||
EuiText,
|
||||
useEuiPaddingCSS,
|
||||
} 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 type { DataViewListItemEnhanced } from '@kbn/unified-search-plugin/public/dataview_picker/dataview_list';
|
||||
import { useTriggerUiActionServices } from '../es_query/util';
|
||||
import { EsQueryRuleMetaData } from '../es_query/types';
|
||||
|
||||
export interface DataViewSelectPopoverProps {
|
||||
dependencies: {
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
dataViewEditor: DataViewEditorStart;
|
||||
};
|
||||
dataView?: DataView;
|
||||
metadata?: EsQueryRuleMetaData;
|
||||
onSelectDataView: (selectedDataView: DataView) => void;
|
||||
|
@ -43,12 +47,12 @@ const toDataViewListItem = (dataView: DataView): DataViewListItemEnhanced => {
|
|||
};
|
||||
|
||||
export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopoverProps> = ({
|
||||
dependencies: { dataViews, dataViewEditor },
|
||||
metadata = { adHocDataViewList: [], isManagementPage: true },
|
||||
dataView,
|
||||
onSelectDataView,
|
||||
onChangeMetaData,
|
||||
}) => {
|
||||
const { dataViews, dataViewEditor } = useTriggerUiActionServices();
|
||||
const [dataViewItems, setDataViewsItems] = useState<DataViewListItemEnhanced[]>([]);
|
||||
const [dataViewPopoverOpen, setDataViewPopoverOpen] = useState(false);
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ const isSearchSourceParam = (action: LocalStateAction): action is SearchSourcePa
|
|||
export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProps) => {
|
||||
const services = useTriggerUiActionServices();
|
||||
const unifiedSearch = services.unifiedSearch;
|
||||
const { dataViews, dataViewEditor } = useTriggerUiActionServices();
|
||||
const { searchSource, errors, initialSavedQuery, setParam, ruleParams } = props;
|
||||
const [savedQuery, setSavedQuery] = useState<SavedQuery>();
|
||||
|
||||
|
@ -117,7 +118,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
|
|||
);
|
||||
|
||||
const { index: dataView, query, filter: filters } = ruleConfiguration;
|
||||
const dataViews = useMemo(() => (dataView ? [dataView] : []), [dataView]);
|
||||
const indexPatterns = useMemo(() => (dataView ? [dataView] : []), [dataView]);
|
||||
|
||||
const [esFields, setEsFields] = useState<FieldOption[]>(
|
||||
dataView ? convertFieldSpecToFieldOption(dataView.fields.map((field) => field.toSpec())) : []
|
||||
|
@ -296,6 +297,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
|
|||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<DataViewSelectPopover
|
||||
dependencies={{ dataViews, dataViewEditor }}
|
||||
dataView={dataView}
|
||||
metadata={props.metadata}
|
||||
onSelectDataView={onSelectDataView}
|
||||
|
@ -320,7 +322,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
|
|||
suggestionsSize="s"
|
||||
displayStyle="inPage"
|
||||
query={query}
|
||||
indexPatterns={dataViews}
|
||||
indexPatterns={indexPatterns}
|
||||
savedQuery={savedQuery}
|
||||
filters={filters}
|
||||
onFiltersUpdated={onUpdateFilters}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue