Skip to content

Commit

Permalink
Nits, fixes, and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Oct 19, 2024
1 parent 0ad199b commit b0892c6
Show file tree
Hide file tree
Showing 39 changed files with 484 additions and 117 deletions.
163 changes: 148 additions & 15 deletions packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { Filter, FilterStateStore } from '@kbn/es-query';
import { ToastsStart } from '@kbn/core-notifications-browser';
import { useLoadRuleTypesQuery, useAlertsDataView } from '../common/hooks';
import { useAlertsDataView } from '../common/hooks';
import { AlertsSearchBar } from '.';
import { HttpStart } from '@kbn/core-http-browser';

Expand All @@ -35,20 +35,6 @@ jest.mocked(useAlertsDataView).mockReturnValue({
},
});

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

const unifiedSearchBarMock = jest.fn().mockImplementation((props) => (
<button
data-test-subj="querySubmitButton"
Expand Down Expand Up @@ -160,4 +146,151 @@ describe('AlertsSearchBar', () => {
expect(mockDataPlugin.query.filterManager.setFilters).toHaveBeenCalledWith(filters);
});
});

it('calls the unifiedSearchBar correctly for security rule types', async () => {
render(
<AlertsSearchBar
rangeFrom="now/d"
rangeTo="now/d"
query=""
onQuerySubmit={jest.fn()}
toasts={toastsMock}
http={httpMock}
dataService={mockDataPlugin}
appName={'test'}
onFiltersUpdated={jest.fn()}
unifiedSearchBar={unifiedSearchBarMock}
ruleTypeIds={['siem.esqlRuleType', '.esQuery']}
/>
);

await waitFor(() => {
expect(unifiedSearchBarMock).toHaveBeenCalledWith(
expect.objectContaining({
suggestionsAbstraction: undefined,
})
);
});
});

it('calls the unifiedSearchBar correctly for NON security rule types', async () => {
render(
<AlertsSearchBar
rangeFrom="now/d"
rangeTo="now/d"
query=""
onQuerySubmit={jest.fn()}
toasts={toastsMock}
http={httpMock}
dataService={mockDataPlugin}
appName={'test'}
onFiltersUpdated={jest.fn()}
unifiedSearchBar={unifiedSearchBarMock}
ruleTypeIds={['.esQuery']}
/>
);

await waitFor(() => {
expect(unifiedSearchBarMock).toHaveBeenCalledWith(
expect.objectContaining({
suggestionsAbstraction: { type: 'alerts', fields: {} },
})
);
});
});

it('calls the unifiedSearchBar with correct index patters', async () => {
render(
<AlertsSearchBar
rangeFrom="now/d"
rangeTo="now/d"
query=""
onQuerySubmit={jest.fn()}
toasts={toastsMock}
http={httpMock}
dataService={mockDataPlugin}
appName={'test'}
onFiltersUpdated={jest.fn()}
unifiedSearchBar={unifiedSearchBarMock}
ruleTypeIds={['.esQuery', 'apm.anomaly']}
/>
);

await waitFor(() => {
expect(unifiedSearchBarMock).toHaveBeenCalledWith(
expect.objectContaining({
indexPatterns: [
{
fields: [
{ aggregatable: true, name: 'event.action', searchable: true, type: 'string' },
],
title: '.esQuery,apm.anomaly',
},
],
})
);
});
});

it('calls the unifiedSearchBar with correct index patters without rule types', async () => {
render(
<AlertsSearchBar
rangeFrom="now/d"
rangeTo="now/d"
query=""
onQuerySubmit={jest.fn()}
toasts={toastsMock}
http={httpMock}
dataService={mockDataPlugin}
appName={'test'}
onFiltersUpdated={jest.fn()}
unifiedSearchBar={unifiedSearchBarMock}
/>
);

await waitFor(() => {
expect(unifiedSearchBarMock).toHaveBeenCalledWith(
expect.objectContaining({
indexPatterns: [
{
fields: [
{ aggregatable: true, name: 'event.action', searchable: true, type: 'string' },
],
title: '.alerts-*',
},
],
})
);
});
});

it('calls the unifiedSearchBar with correct index patters without data views', async () => {
jest.mocked(useAlertsDataView).mockReturnValue({
isLoading: false,
dataView: undefined,
});

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

await waitFor(() => {
expect(unifiedSearchBarMock).toHaveBeenCalledWith(
expect.objectContaining({
indexPatterns: [],
})
);
});
});
});
27 changes: 6 additions & 21 deletions packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
import { useCallback, useMemo, useState } from 'react';
import type { Query, TimeRange } from '@kbn/es-query';
import type { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component';
import { AlertConsumers, isSiemRuleType } from '@kbn/rule-data-utils';
import { isSiemRuleType } from '@kbn/rule-data-utils';
import { NO_INDEX_PATTERNS } from './constants';
import { SEARCH_BAR_PLACEHOLDER } from './translations';
import type { AlertsSearchBarProps, QueryLanguageType } from './types';
import { useLoadRuleTypesQuery, useAlertsDataView } from '../common/hooks';
import { useAlertsDataView } from '../common/hooks';

export type { AlertsSearchBarProps } from './types';

Expand All @@ -23,7 +23,7 @@ const SA_ALERTS = { type: 'alerts', fields: {} } as SuggestionsAbstraction;
export const AlertsSearchBar = ({
appName,
disableQueryLanguageSwitcher = false,
ruleTypeIds,
ruleTypeIds = [],
query,
filters,
onQueryChange,
Expand All @@ -43,14 +43,14 @@ export const AlertsSearchBar = ({
}: AlertsSearchBarProps) => {
const [queryLanguage, setQueryLanguage] = useState<QueryLanguageType>('kuery');
const { dataView } = useAlertsDataView({
ruleTypeIds: ruleTypeIds ?? [],
ruleTypeIds,
http,
toasts,
dataViewsService: dataService.dataViews,
});

const indexPatterns = useMemo(() => {
if (ruleTypeIds && dataView?.fields?.length) {
if (ruleTypeIds.length > 0 && dataView?.fields?.length) {
return [{ title: ruleTypeIds.join(','), fields: dataView.fields }];
}

Expand All @@ -60,22 +60,7 @@ export const AlertsSearchBar = ({
return null;
}, [dataView, ruleTypeIds]);

const ruleType = useLoadRuleTypesQuery({
filteredRuleTypes: ruleTypeIds ?? [],
enabled: Boolean(ruleTypeIds?.length),
http,
toasts,
});

const allProducers = new Set(
ruleTypeIds
?.map((ruleTypeId) => ruleType.ruleTypesState.data.get(ruleTypeId)?.producer)
.filter((ruleTypeId): ruleTypeId is string => Boolean(ruleTypeId)) ?? []
);

const hasSiemRuleTypes = ruleTypeIds?.some(isSiemRuleType) ?? false;
const isSiemProducer = allProducers.has(AlertConsumers.SIEM);
const isSecurity = hasSiemRuleTypes || isSiemProducer;
const isSecurity = ruleTypeIds?.some(isSiemRuleType) ?? false;

const onSearchQuerySubmit = useCallback(
({ dateRange, query: nextQuery }: { dateRange: TimeRange; query?: Query }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('useAlertsDataView', () => {
await waitFor(() => expect(result.current).toEqual(mockedAsyncDataView));
});

it('fetches indexes and fields for non-siem feature ids, returning a DataViewBase object', async () => {
it('fetches indexes and fields for non-siem rule type ids, returning a DataViewBase object', async () => {
const { result, waitForValueToChange } = renderHook(
() =>
useAlertsDataView({
Expand All @@ -95,7 +95,7 @@ describe('useAlertsDataView', () => {
expect(result.current.dataView).not.toBe(mockDataView);
});

it('only fetches index names for the siem feature id, returning a DataView', async () => {
it('only fetches index names for the siem rule type ids, returning a DataView', async () => {
const { result, waitFor } = renderHook(
() => useAlertsDataView({ ...mockServices, ruleTypeIds: ['siem.esqlRule', 'siem.eqlRule'] }),
{
Expand All @@ -109,7 +109,7 @@ describe('useAlertsDataView', () => {
await waitFor(() => expect(result.current.dataView).toBe(mockDataView));
});

it('does not fetch anything if siem and other feature ids are mixed together', async () => {
it('does not fetch anything if siem and other rule type ids are mixed together', async () => {
const { result, waitFor } = renderHook(
() =>
useAlertsDataView({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('useFetchAlertsIndexNamesQuery', () => {
queryClient.clear();
});

it('does not fetch if featureIds is empty', () => {
it('does not fetch if ruleTypeIds is empty', () => {
renderHook(() => useFetchAlertsIndexNamesQuery({ http: mockHttpClient, ruleTypeIds: [] }), {
wrapper,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,4 +420,41 @@ describe('ruleActionsSettings', () => {

expect(screen.queryByText('filter query error')).toBeInTheDocument();
});

test('should show the rule actions filter for siem rule types', () => {
useRuleFormState.mockReturnValue({
plugins: {
settings: {},
},
formData: {
consumer: 'siem',
schedule: { interval: '5h' },
},
selectedRuleType: {
/**
* With the current configuration
* hasFieldsForAad will return false
* and we are testing the isSiemRuleType(ruleTypeId)
* branch of the code
*/
...ruleType,
id: 'siem.esqlRuleType',
hasFieldsForAAD: false,
},
selectedRuleTypeModel: ruleModel,
});

render(
<RuleActionsSettings
action={getAction('1')}
onUseDefaultMessageChange={mockOnUseDefaultMessageChange}
onNotifyWhenChange={mockOnNotifyWhenChange}
onActionGroupChange={mockOnActionGroupChange}
onAlertsFilterChange={mockOnAlertsFilterChange}
onTimeframeChange={mockOnTimeframeChange}
/>
);

expect(screen.queryByText('RuleActionsAlertsFilter')).toBeInTheDocument();
});
});
14 changes: 14 additions & 0 deletions packages/kbn-rule-data-utils/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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".
*/

module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-rule-data-utils'],
};
22 changes: 22 additions & 0 deletions packages/kbn-rule-data-utils/src/alerts_as_data_rbac.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 { isSiemRuleType } from './alerts_as_data_rbac';

describe('alertsAsDataRbac', () => {
describe('isSiemRuleType', () => {
test('returns true for siem rule types', () => {
expect(isSiemRuleType('siem.esqlRuleType')).toBe(true);
});

test('returns false for NON siem rule types', () => {
expect(isSiemRuleType('apm.anomaly')).toBe(false);
});
});
});
3 changes: 2 additions & 1 deletion packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const getEsQueryConfig = (params?: GetEsQueryConfigParamType): EsQueryCon
};

/**
* TODO: Abstract it and remove it
* TODO: Remove when checks for specific rule type ids is not needed
*in the codebase.
*/
export const isSiemRuleType = (ruleTypeId: string) => ruleTypeId.startsWith('siem.');
5 changes: 5 additions & 0 deletions packages/kbn-rule-data-utils/src/rule_types/stack_rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ export const STACK_ALERTS_FEATURE_ID = 'stackAlerts';
export const ES_QUERY_ID = '.es-query';
export const ML_ANOMALY_DETECTION_RULE_TYPE_ID = 'xpack.ml.anomaly_detection_alert';

/**
* These rule types are not the only stack rules. There are more.
* The variable holds all stack rule types that support multiple
* consumers aka the "Role visibility" UX dropdown.
*/
export const STACK_RULE_TYPE_IDS = [ES_QUERY_ID, ML_ANOMALY_DETECTION_RULE_TYPE_ID];
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ export const findRulesInternalRequestQuerySchema = schema.object({
page: schema.number({
defaultValue: 1,
min: 1,
meta: {
description: 'The page number to return.',
},
}),
search: schema.maybe(schema.string()),
default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], {
Expand Down
Loading

0 comments on commit b0892c6

Please sign in to comment.