Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ResponseOps][Alerts] Fix DSL filters in alerts KQL search bar #184002

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React from 'react';
import { render, screen } from '@testing-library/react';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { httpServiceMock, notificationServiceMock } from '@kbn/core/public/mocks';
import { Filter } from '@kbn/es-query';
import { useLoadRuleTypesQuery, useAlertsDataView, useRuleAADFields } from '../common/hooks';
import { AlertsSearchBar } from '.';

jest.mock('../common/hooks/use_load_rule_types_query');
jest.mock('../common/hooks/use_rule_aad_fields');
jest.mock('../common/hooks/use_alerts_data_view');

jest.mocked(useAlertsDataView).mockReturnValue({
isLoading: false,
dataView: {
title: '.alerts-*',
fields: [
{
name: 'event.action',
type: 'string',
aggregatable: true,
searchable: true,
},
],
},
});

jest.mocked(useLoadRuleTypesQuery).mockReturnValue({
ruleTypesState: {
isInitialLoad: false,
data: new Map(),
isLoading: false,
error: null,
},
authorizedToReadAnyRules: false,
hasAnyAuthorizedRuleType: false,
authorizedRuleTypes: [],
authorizedToCreateAnyRules: false,
isSuccess: false,
});

jest.mocked(useRuleAADFields).mockReturnValue({
aadFields: [],
loading: false,
});

const mockDataPlugin = dataPluginMock.createStartContract();
const unifiedSearchBarMock = jest.fn().mockImplementation((props) => (
<button
data-test-subj="querySubmitButton"
onClick={() => props.onQuerySubmit({ dateRange: { from: 'now', to: 'now' } })}
type="button"
>
{'Hello world'}
</button>
));
const httpMock = httpServiceMock.createStartContract();
const toastsMock = notificationServiceMock.createStartContract().toasts;

describe('AlertsSearchBar', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('renders correctly', () => {
render(
<AlertsSearchBar
rangeFrom="now/d"
rangeTo="now/d"
query=""
onQuerySubmit={jest.fn()}
initialFilters={[]}
onFiltersUpdated={jest.fn()}
appName={'test'}
dataService={mockDataPlugin}
featureIds={['observability', 'stackAlerts']}
unifiedSearchBar={unifiedSearchBarMock}
http={httpMock}
toasts={toastsMock}
/>
);
expect(screen.getByTestId('querySubmitButton')).toBeInTheDocument();
});

it('renders initial filters correctly', () => {
const filters = [
{
meta: {
negate: false,
alias: null,
disabled: false,
type: 'custom',
key: 'query',
},
query: { match_phrase: { 'host.name': 'testValue' } },
$state: { store: 'appState' },
},
] as Filter[];

render(
<AlertsSearchBar
rangeFrom="now/d"
rangeTo="now/d"
query=""
onQuerySubmit={jest.fn()}
initialFilters={filters}
onFiltersUpdated={jest.fn()}
appName={'test'}
dataService={mockDataPlugin}
featureIds={['observability', 'stackAlerts']}
unifiedSearchBar={unifiedSearchBarMock}
http={httpMock}
toasts={toastsMock}
/>
);

expect(mockDataPlugin.query.filterManager.addFilters).toHaveBeenCalledWith(filters);
});
});
34 changes: 27 additions & 7 deletions packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { useCallback, useMemo, useState } from 'react';
import { useCallback, useMemo, useEffect, useState, useRef } from 'react';
import { cloneDeep } from 'lodash';
import type { Query, TimeRange } from '@kbn/es-query';
import type { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component';
import { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils';
Expand All @@ -26,10 +27,8 @@ export const AlertsSearchBar = ({
featureIds = EMPTY_FEATURE_IDS,
ruleTypeId,
query,
filters,
onQueryChange,
onQuerySubmit,
onFiltersUpdated,
rangeFrom,
rangeTo,
showFilterBar = false,
Expand All @@ -40,14 +39,17 @@ export const AlertsSearchBar = ({
http,
toasts,
unifiedSearchBar,
dataViewsService,
dataService,
initialFilters,
onFiltersUpdated,
}: AlertsSearchBarProps) => {
const [queryLanguage, setQueryLanguage] = useState<QueryLanguageType>('kuery');
const isFirstRender = useRef(false);
const { dataView } = useAlertsDataView({
featureIds,
http,
toasts,
dataViewsService,
dataViewsService: dataService.dataViews,
});
const { aadFields, loading: fieldsLoading } = useRuleAADFields({
ruleTypeId,
Expand Down Expand Up @@ -105,20 +107,38 @@ export const AlertsSearchBar = ({
});
};

useEffect(() => {
if (initialFilters?.length && !isFirstRender.current) {
dataService.query.filterManager.addFilters(cloneDeep(initialFilters));
isFirstRender.current = true;
}

const subscription = dataService.query.state$.subscribe((state) => {
if (state.changes.filters) {
onFiltersUpdated?.(state.state.filters ?? []);
}
});

return () => {
isFirstRender.current = false;
subscription.unsubscribe();
dataService.query.filterManager.removeAll();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialFilters]);

return unifiedSearchBar({
appName,
disableQueryLanguageSwitcher,
// @ts-expect-error - DataView fields prop and SearchBar indexPatterns props are overly broad
indexPatterns: !indexPatterns || fieldsLoading ? NO_INDEX_PATTERNS : indexPatterns,
placeholder,
query: { query: query ?? '', language: queryLanguage },
filters,
dateRangeFrom: rangeFrom,
dateRangeTo: rangeTo,
displayStyle: 'inPage',
showFilterBar,
onQuerySubmit: onSearchQuerySubmit,
onFiltersUpdated,
onRefresh,
showDatePicker,
showQueryInput: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { Filter } from '@kbn/es-query';
import type { ValidFeatureId } from '@kbn/rule-data-utils';
import type { ToastsStart, HttpStart } from '@kbn/core/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import type { DataViewsContract } from '@kbn/data-views-plugin/common';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';

export type QueryLanguageType = 'lucene' | 'kuery';

Expand All @@ -22,7 +22,7 @@ export interface AlertsSearchBarProps {
rangeFrom?: string;
rangeTo?: string;
query?: string;
filters?: Filter[];
initialFilters?: Filter[];
showFilterBar?: boolean;
showDatePicker?: boolean;
showSubmitButton?: boolean;
Expand All @@ -41,5 +41,5 @@ export interface AlertsSearchBarProps {
http: HttpStart;
toasts: ToastsStart;
unifiedSearchBar: UnifiedSearchPublicPluginStart['ui']['SearchBar'];
dataViewsService: DataViewsContract;
dataService: DataPublicPluginStart;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { createMaintenanceWindow, CreateParams } from '../services/maintenance_w

const onErrorWithMessage = (message: string) =>
i18n.translate('xpack.alerting.maintenanceWindowsCreateFailureWithMessage', {
defaultMessage: 'Failed to create maintenance window: {message}',
defaultMessage: ['Failed to create maintenance window', message].filter(Boolean).join(': '),
values: { message },
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,7 @@ export const CreateMaintenanceWindowForm = React.memo<CreateMaintenanceWindowFor

const [isScopedQueryEnabled, setIsScopedQueryEnabled] = useState(!!initialValue?.scopedQuery);
const [query, setQuery] = useState<string>(initialValue?.scopedQuery?.kql || '');
const [filters, setFilters] = useState<Filter[]>(
(initialValue?.scopedQuery?.filters as Filter[]) || []
);
const [filters, setFilters] = useState<Filter[]>(initialValue?.scopedQuery?.filters || []);
const [scopedQueryErrors, setScopedQueryErrors] = useState<string[]>([]);
const hasSetInitialCategories = useRef<boolean>(false);
const categoryIdsHistory = useRef<string[]>([]);
Expand Down Expand Up @@ -463,7 +461,7 @@ export const CreateMaintenanceWindowForm = React.memo<CreateMaintenanceWindowFor
<MaintenanceWindowScopedQuery
featureIds={featureIds}
query={query}
filters={filters}
initialFilters={initialValue?.scopedQuery?.filters}
isLoading={isLoadingRuleTypes}
isEnabled={isScopedQueryEnabled}
errors={scopedQueryErrors}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('MaintenanceWindowScopedQuery', () => {
<MaintenanceWindowScopedQuery
featureIds={['observability', 'management', 'securitySolution'] as AlertConsumers[]}
query={''}
filters={[]}
initialFilters={[]}
onQueryChange={jest.fn()}
onFiltersChange={jest.fn()}
/>
Expand All @@ -63,7 +63,7 @@ describe('MaintenanceWindowScopedQuery', () => {
featureIds={['observability', 'management', 'securitySolution'] as AlertConsumers[]}
isEnabled={false}
query={''}
filters={[]}
initialFilters={[]}
onQueryChange={jest.fn()}
onFiltersChange={jest.fn()}
/>
Expand All @@ -77,7 +77,7 @@ describe('MaintenanceWindowScopedQuery', () => {
featureIds={['observability', 'management', 'securitySolution'] as AlertConsumers[]}
isLoading={true}
query={''}
filters={[]}
initialFilters={[]}
onQueryChange={jest.fn()}
onFiltersChange={jest.fn()}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { useKibana } from '../../../utils/kibana_react';
export interface MaintenanceWindowScopedQueryProps {
featureIds: AlertConsumers[];
query: string;
filters: Filter[];
initialFilters?: Filter[];
errors?: string[];
isLoading?: boolean;
isEnabled?: boolean;
Expand All @@ -29,7 +29,7 @@ export const MaintenanceWindowScopedQuery = React.memo(
const {
featureIds,
query,
filters,
initialFilters,
errors = [],
isLoading,
isEnabled = true,
Expand Down Expand Up @@ -79,7 +79,7 @@ export const MaintenanceWindowScopedQuery = React.memo(
featureIds={featureIds}
disableQueryLanguageSwitcher={true}
query={query}
filters={filters}
initialFilters={initialFilters}
onQueryChange={onQueryChangeInternal}
onQuerySubmit={onQueryChangeInternal}
onFiltersUpdated={onFiltersChange}
Expand All @@ -90,7 +90,7 @@ export const MaintenanceWindowScopedQuery = React.memo(
http={http}
toasts={toasts}
unifiedSearchBar={SearchBar}
dataViewsService={data.dataViews}
dataService={data}
/>
</EuiFormRow>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export function ObservabilityAlertSearchBar({
rangeFrom={rangeFrom}
rangeTo={rangeTo}
showFilterBar={showFilterBar}
filters={filters}
initialFilters={filters}
onFiltersUpdated={onFilterUpdated}
savedQuery={savedQuery}
onSavedQueryUpdated={setSavedQuery}
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -9793,7 +9793,6 @@
"xpack.alerting.maintenanceWindows.upcoming": "À venir",
"xpack.alerting.maintenanceWindowsArchiveFailure": "Impossible d'archiver la fenêtre de maintenance.",
"xpack.alerting.maintenanceWindowsArchiveSuccess": "Fenêtre de maintenance archivée \"{title}\"",
"xpack.alerting.maintenanceWindowsCreateFailureWithMessage": "Impossible de créer la fenêtre de maintenance : {message}",
"xpack.alerting.maintenanceWindowsCreateFailureWithoutMessage": "Impossible de créer la fenêtre de maintenance",
"xpack.alerting.maintenanceWindowsCreateSuccess": "Fenêtre de maintenance créée \"{title}\"",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveFailure": "Impossible d'annuler et d'archiver la fenêtre de maintenance.",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -9787,7 +9787,6 @@
"xpack.alerting.maintenanceWindows.upcoming": "予定",
"xpack.alerting.maintenanceWindowsArchiveFailure": "保守時間枠をアーカイブできませんでした。",
"xpack.alerting.maintenanceWindowsArchiveSuccess": "アーカイブされた保守時間枠''{title}''",
"xpack.alerting.maintenanceWindowsCreateFailureWithMessage": "保守時間枠を作成できませんでした:{message}",
"xpack.alerting.maintenanceWindowsCreateFailureWithoutMessage": "保守時間枠を作成できませんでした",
"xpack.alerting.maintenanceWindowsCreateSuccess": "作成された保守時間枠''{title}''",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveFailure": "保守時間枠をキャンセルしてアーカイブできませんでした。",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -9801,7 +9801,6 @@
"xpack.alerting.maintenanceWindows.upcoming": "即将发生",
"xpack.alerting.maintenanceWindowsArchiveFailure": "无法归档维护窗口。",
"xpack.alerting.maintenanceWindowsArchiveSuccess": "已归档维护窗口“{title}”",
"xpack.alerting.maintenanceWindowsCreateFailureWithMessage": "无法创建维护窗口:{message}",
"xpack.alerting.maintenanceWindowsCreateFailureWithoutMessage": "无法创建维护窗口",
"xpack.alerting.maintenanceWindowsCreateSuccess": "已创建维护窗口“{title}”",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveFailure": "无法取消并归档维护窗口。",
Expand Down
Loading