Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const ExploreMatchingButton = ({
<EuiButton
fullWidth
size="s"
data-test-subj="explore-matching-indices-button"
onClick={() => {
setPopoverIsOpen(false);
onCreateDefaultAdHocDataView(dataViewSearchString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

import { COMPARATORS } from '@kbn/triggers-actions-ui-plugin/public';
import { ErrorKey } from './types';

export const DEFAULT_VALUES = {
THRESHOLD_COMPARATOR: COMPARATORS.GREATER_THAN,
Expand All @@ -22,17 +21,31 @@ export const DEFAULT_VALUES = {
EXCLUDE_PREVIOUS_HITS: true,
};

export const EXPRESSION_ERRORS = {
index: new Array<string>(),
size: new Array<string>(),
timeField: new Array<string>(),
export const COMMON_EXPRESSION_ERRORS = {
searchType: new Array<string>(),
threshold0: new Array<string>(),
threshold1: new Array<string>(),
esQuery: new Array<string>(),
thresholdComparator: new Array<string>(),
timeWindowSize: new Array<string>(),
size: new Array<string>(),
};

export const SEARCH_SOURCE_ONLY_EXPRESSION_ERRORS = {
searchConfiguration: new Array<string>(),
searchType: new Array<string>(),
timeField: new Array<string>(),
};

export const ONLY_ES_QUERY_EXPRESSION_ERRORS = {
index: new Array<string>(),
esQuery: new Array<string>(),
timeField: new Array<string>(),
};

const ALL_EXPRESSION_ERROR_ENTRIES = {
...COMMON_EXPRESSION_ERRORS,
...SEARCH_SOURCE_ONLY_EXPRESSION_ERRORS,
...ONLY_ES_QUERY_EXPRESSION_ERRORS,
};

export const EXPRESSION_ERROR_KEYS = Object.keys(EXPRESSION_ERRORS) as ErrorKey[];
export const ALL_EXPRESSION_ERROR_KEYS = Object.keys(ALL_EXPRESSION_ERROR_ENTRIES) as Array<
keyof typeof ALL_EXPRESSION_ERROR_ENTRIES
>;
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';

import { EuiFormRow, EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui';
import { DocLinksStart, HttpSetup } from '@kbn/core/public';

import { XJson } from '@kbn/es-ui-shared-plugin/public';
import { CodeEditor, useKibana } from '@kbn/kibana-react-plugin/public';
import { CodeEditor } from '@kbn/kibana-react-plugin/public';
import { getFields, RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public';
import { parseDuration } from '@kbn/alerting-plugin/common';
import { hasExpressionValidationErrors } from '../validation';
Expand All @@ -24,14 +23,10 @@ import { IndexSelectPopover } from '../../components/index_select_popover';
import { DEFAULT_VALUES } from '../constants';
import { RuleCommonExpressions } from '../rule_common_expressions';
import { totalHitsToNumber } from '../test_query_row';
import { useTriggerUiActionServices } from '../util';

const { useXJsonMode } = XJson;

interface KibanaDeps {
http: HttpSetup;
docLinks: DocLinksStart;
}

export const EsQueryExpression: React.FC<
RuleTypeParamsExpressionProps<EsQueryRuleParams<SearchType.esQuery>, EsQueryRuleMetaData>
> = ({ ruleParams, setRuleParams, setRuleProperty, errors, data }) => {
Expand Down Expand Up @@ -73,7 +68,8 @@ export const EsQueryExpression: React.FC<
[setRuleParams]
);

const { http, docLinks } = useKibana<KibanaDeps>().services;
const services = useTriggerUiActionServices();
const { http, docLinks } = services;

const [esFields, setEsFields] = useState<
Array<{
Expand Down Expand Up @@ -107,13 +103,9 @@ export const EsQueryExpression: React.FC<
}
};

const hasValidationErrors = useCallback(() => {
return hasExpressionValidationErrors(currentRuleParams);
}, [currentRuleParams]);

const onTestQuery = useCallback(async () => {
const window = `${timeWindowSize}${timeWindowUnit}`;
if (hasValidationErrors()) {
if (hasExpressionValidationErrors(currentRuleParams)) {
return { nrOfDocs: 0, timeWindow: window };
}
const timeWindow = parseDuration(window);
Expand All @@ -136,7 +128,7 @@ export const EsQueryExpression: React.FC<

const hits = rawResponse.hits;
return { nrOfDocs: totalHitsToNumber(hits.total), timeWindow: window };
}, [data.search, esQuery, index, timeField, timeWindowSize, timeWindowUnit, hasValidationErrors]);
}, [timeWindowSize, timeWindowUnit, currentRuleParams, esQuery, data.search, index, timeField]);

return (
<Fragment>
Expand Down Expand Up @@ -251,7 +243,7 @@ export const EsQueryExpression: React.FC<
setParam('size', updatedValue);
}}
errors={errors}
hasValidationErrors={hasValidationErrors()}
hasValidationErrors={hasExpressionValidationErrors(currentRuleParams)}
onTestFetch={onTestQuery}
excludeHitsFromPreviousRun={excludeHitsFromPreviousRun}
onChangeExcludeHitsFromPreviousRun={(exclude) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { SearchSourceExpression, SearchSourceExpressionProps } from './search_so
import { EsQueryExpression } from './es_query_expression';
import { QueryFormTypeChooser } from './query_form_type_chooser';
import { isSearchSourceRule } from '../util';
import { EXPRESSION_ERROR_KEYS } from '../constants';
import { ALL_EXPRESSION_ERROR_KEYS } from '../constants';

function areSearchSourceExpressionPropsEqual(
prevProps: Readonly<PropsWithChildren<SearchSourceExpressionProps>>,
Expand Down Expand Up @@ -59,7 +59,7 @@ export const EsQueryRuleTypeExpression: React.FunctionComponent<
}
);

const errorParam = EXPRESSION_ERROR_KEYS.find((errorKey) => {
const errorParam = ALL_EXPRESSION_ERROR_KEYS.find((errorKey) => {
return errors[errorKey]?.length >= 1 && ruleParams[errorKey] !== undefined;
});

Expand All @@ -68,8 +68,9 @@ export const EsQueryRuleTypeExpression: React.FunctionComponent<
<EuiCallOut
color="danger"
size="s"
data-test-subj="esQueryAlertExpressionError"
title={
['index', 'searchType'].includes(errorParam)
['index', 'searchType', 'timeField'].includes(errorParam)
? errors[errorParam]
: expressionGenericErrorMessage
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ export const SearchSourceExpression = ({
const [paramsError, setParamsError] = useState<Error>();

const setParam = useCallback(
(paramField: string, paramValue: unknown) => setRuleParams(paramField, paramValue),
(paramField: string, paramValue: unknown) => {
setRuleParams(paramField, paramValue);
},
[setRuleParams]
);

Expand All @@ -65,22 +67,26 @@ export const SearchSourceExpression = ({
initialSearchConfiguration = newSearchSource.getSerializedFields();
}

setRuleProperty('params', {
searchConfiguration: initialSearchConfiguration,
searchType: SearchType.searchSource,
timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE,
timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT,
threshold: threshold ?? DEFAULT_VALUES.THRESHOLD,
thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR,
size: size ?? DEFAULT_VALUES.SIZE,
excludeHitsFromPreviousRun:
excludeHitsFromPreviousRun ?? DEFAULT_VALUES.EXCLUDE_PREVIOUS_HITS,
});

data.search.searchSource
.create(initialSearchConfiguration)
.then(setSearchSource)
.catch(setParamsError);
try {
const createdSearchSource = await data.search.searchSource.create(
initialSearchConfiguration
);
setRuleProperty('params', {
searchConfiguration: initialSearchConfiguration,
timeField: createdSearchSource.getField('index')?.timeFieldName,
searchType: SearchType.searchSource,
timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE,
timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT,
threshold: threshold ?? DEFAULT_VALUES.THRESHOLD,
thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR,
size: size ?? DEFAULT_VALUES.SIZE,
excludeHitsFromPreviousRun:
excludeHitsFromPreviousRun ?? DEFAULT_VALUES.EXCLUDE_PREVIOUS_HITS,
});
setSearchSource(createdSearchSource);
} catch (error) {
setParamsError(error);
}
};

initSearchSource();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import { DEFAULT_VALUES } from '../constants';
import { DataViewSelectPopover } from '../../components/data_view_select_popover';
import { RuleCommonExpressions } from '../rule_common_expressions';
import { totalHitsToNumber } from '../test_query_row';
import { hasExpressionValidationErrors } from '../validation';
import { useTriggerUiActionServices } from '../util';
import { hasExpressionValidationErrors } from '../validation';

const HIDDEN_FILTER_PANEL_OPTIONS: SearchBarProps['hiddenFilterPanelOptions'] = [
'pinFilter',
Expand Down Expand Up @@ -94,6 +94,10 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
if (isSearchSourceParam(action)) {
searchSource.setParent(undefined).setField(action.type, action.payload);
setParam('searchConfiguration', searchSource.getSerializedFields());

if (action.type === 'index') {
setParam('timeField', searchSource.getField('index')?.timeFieldName);
}
} else {
setParam(action.type, action.payload);
}
Expand All @@ -112,6 +116,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
ruleParams.excludeHitsFromPreviousRun ?? DEFAULT_VALUES.EXCLUDE_PREVIOUS_HITS,
}
);

const { index: dataView, query, filter: filters } = ruleConfiguration;
const dataViews = useMemo(() => (dataView ? [dataView] : []), [dataView]);

Expand Down Expand Up @@ -290,7 +295,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp
onChangeWindowUnit={onChangeWindowUnit}
onChangeSizeValue={onChangeSizeValue}
errors={errors}
hasValidationErrors={hasExpressionValidationErrors(ruleParams) || !dataView}
hasValidationErrors={hasExpressionValidationErrors(props.ruleParams)}
onTestFetch={onTestFetch}
onCopyQuery={onCopyQuery}
excludeHitsFromPreviousRun={ruleConfiguration.excludeHitsFromPreviousRun}
Expand Down
18 changes: 2 additions & 16 deletions x-pack/plugins/stack_alerts/public/rule_types/es_query/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@
import { RuleTypeParams } from '@kbn/alerting-plugin/common';
import { SerializedSearchSourceFields } from '@kbn/data-plugin/common';
import { EuiComboBoxOptionOption } from '@elastic/eui';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import type { DataViewsPublicPluginStart, DataView } from '@kbn/data-views-plugin/public';
import { EXPRESSION_ERRORS } from './constants';
import type { DataView } from '@kbn/data-views-plugin/public';

export interface Comparator {
text: string;
Expand Down Expand Up @@ -50,20 +46,10 @@ export interface OnlyEsQueryRuleParams {
}

export interface OnlySearchSourceRuleParams {
timeField?: string;
searchType?: 'searchSource';
searchConfiguration?: SerializedSearchSourceFields;
savedQueryId?: string;
}

export type DataViewOption = EuiComboBoxOptionOption<string>;

export type ExpressionErrors = typeof EXPRESSION_ERRORS;

export type ErrorKey = keyof ExpressionErrors & unknown;

export interface TriggersAndActionsUiDeps {
dataViews: DataViewsPublicPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
data: DataPublicPluginStart;
dataViewEditor: DataViewEditorStart;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
* 2.0.
*/

import { useKibana } from '@kbn/kibana-react-plugin/public';
import { EsQueryRuleParams, SearchType, TriggersAndActionsUiDeps } from './types';
import { useKibana } from '@kbn/triggers-actions-ui-plugin/public';
import { EsQueryRuleParams, SearchType } from './types';

export const isSearchSourceRule = (
ruleParams: EsQueryRuleParams
): ruleParams is EsQueryRuleParams<SearchType.searchSource> => {
return ruleParams.searchType === 'searchSource';
};

export const useTriggerUiActionServices = () => useKibana<TriggersAndActionsUiDeps>().services;
export const useTriggerUiActionServices = () => useKibana().services;
Loading