Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dde5ddc
Manual rule run from rule details and rules table (#9327)
e40pud May 24, 2024
af84055
Fix types
e40pud May 30, 2024
4061665
Fix tests
e40pud May 30, 2024
b63a495
Integration tests
e40pud Jun 3, 2024
e53d3fb
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 3, 2024
488bd6e
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 3, 2024
36ec681
Update x-pack/test/security_solution_api_integration/test_suites/dete…
e40pud Jun 3, 2024
544a345
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 3, 2024
dd31cb2
Fixing serverless tests
e40pud Jun 3, 2024
4d460e1
Review feedback - error handling
e40pud Jun 3, 2024
08eda19
Review feedback: skip tests in MKI environments
e40pud Jun 5, 2024
50707d0
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 5, 2024
bbd4437
Review feedback
e40pud Jun 7, 2024
e8fa536
Add comments about skipping tests on MKI
e40pud Jun 7, 2024
d78f5db
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 7, 2024
7d2dfa3
Fix cypress tests
e40pud Jun 7, 2024
2823855
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 7, 2024
830fee5
Apply suggestions from code review
e40pud Jun 7, 2024
e636120
Review feedback
e40pud Jun 7, 2024
f586215
Fix cypress tests
e40pud Jun 9, 2024
2bd80c3
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 9, 2024
7bf15a6
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 10, 2024
35ef8e3
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 10, 2024
14b9166
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 10, 2024
e55435c
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 10, 2024
8b190fe
Merge branch 'main' into security/feature/9327-manual-rule-run
kibanamachine Jun 11, 2024
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
2 changes: 2 additions & 0 deletions .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,8 @@ enabled:
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_gaps/trial_license_complete_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_gaps/trial_license_complete_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/configs/ess.config.ts
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/alerting/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export const INTERNAL_ALERTING_BACKFILL_API_PATH =
`${INTERNAL_BASE_ALERTING_API_PATH}/rules/backfill` as const;
export const INTERNAL_ALERTING_BACKFILL_FIND_API_PATH =
`${INTERNAL_ALERTING_BACKFILL_API_PATH}/_find` as const;
export const INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH =
`${INTERNAL_ALERTING_BACKFILL_API_PATH}/_schedule` as const;

export const ALERTING_FEATURE_ID = 'alerts';
export const MONITORING_HISTORY_LIMIT = 200;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const SINGLE_RULE_ACTIONS = {
DUPLICATE: `${APP_UI_ID} singleRuleActions duplicate`,
EXPORT: `${APP_UI_ID} singleRuleActions export`,
DELETE: `${APP_UI_ID} singleRuleActions delete`,
MANUAL_RULE_RUN: `${APP_UI_ID} singleRuleActions manual run`,
PREVIEW: `${APP_UI_ID} singleRuleActions preview`,
SAVE: `${APP_UI_ID} singleRuleActions save`,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ import { RuleSnoozeBadge } from '../../../rule_management/components/rule_snooze
import { useBoolState } from '../../../../common/hooks/use_bool_state';
import { RuleDefinitionSection } from '../../../rule_management/components/rule_details/rule_definition_section';
import { RuleScheduleSection } from '../../../rule_management/components/rule_details/rule_schedule_section';
import { ManualRuleRunModal } from '../../../rule_gaps/components/manual_rule_run';
import { useManualRuleRunConfirmation } from '../../../rule_gaps/components/manual_rule_run/use_manual_rule_run_confirmation';
// eslint-disable-next-line no-restricted-imports
import { useLegacyUrlRedirect } from './use_redirect_legacy_url';
import { RuleDetailTabs, useRuleDetailsTabs } from './use_rule_details_tabs';
Expand Down Expand Up @@ -516,6 +518,13 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
confirmRuleDuplication,
} = useBulkDuplicateExceptionsConfirmation();

const {
isManualRuleRunConfirmationVisible,
showManualRuleRunConfirmation,
cancelManualRuleRun,
confirmManualRuleRun,
} = useManualRuleRunConfirmation();

if (
redirectToDetections(
isSignalIndexExists,
Expand Down Expand Up @@ -563,6 +572,9 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
{i18n.DELETE_CONFIRMATION_BODY}
</EuiConfirmModal>
)}
{isManualRuleRunConfirmationVisible && (
<ManualRuleRunModal onCancel={cancelManualRuleRun} onConfirm={confirmManualRuleRun} />
)}
<StyledFullHeightContainer onKeyDown={onKeyDown} ref={containerElement}>
<EuiWindowEvent event="resize" handler={noop} />
<FiltersGlobal show={showGlobalFilters({ globalFullScreen, graphEventId })}>
Expand Down Expand Up @@ -650,6 +662,7 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
hasActionsPrivileges
)}
showBulkDuplicateExceptionsConfirmation={showBulkDuplicateConfirmation}
showManualRuleRunConfirmation={showManualRuleRunConfirmation}
confirmDeletion={confirmDeletion}
/>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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 type { ScheduleBackfillResponseBody } from '@kbn/alerting-plugin/common/routes/backfill/apis/schedule';
import { scheduleRuleRunMock } from '../../logic/__mocks__/mock';

import type { ScheduleBackfillProps } from '../../types';

export const scheduleRuleRun = async ({
ruleIds,
timeRange,
}: ScheduleBackfillProps): Promise<ScheduleBackfillResponseBody> =>
Promise.resolve(scheduleRuleRunMock);
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 moment from 'moment';

import { INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH } from '@kbn/alerting-plugin/common';

import { KibanaServices } from '../../../common/lib/kibana';
import { scheduleRuleRunMock } from '../logic/__mocks__/mock';
import { scheduleRuleRun } from './api';

const mockKibanaServices = KibanaServices.get as jest.Mock;
jest.mock('../../../common/lib/kibana');

const fetchMock = jest.fn();
mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } });

describe('Detections Rule Gaps API', () => {
describe('scheduleRuleRun', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(scheduleRuleRunMock);
});

test('schedules rule run', async () => {
const timeRange = { startDate: moment().subtract(1, 'd'), endDate: moment() };
await scheduleRuleRun({
ruleIds: ['rule-1'],
timeRange,
});
expect(fetchMock).toHaveBeenCalledWith(
INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH,
expect.objectContaining({
body: `[{"rule_id":"rule-1","start":"${timeRange.startDate.toISOString()}","end":"${timeRange.endDate.toISOString()}"}]`,
method: 'POST',
})
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,43 @@
*/

import {
INTERNAL_ALERTING_BACKFILL_FIND_API_PATH,
INTERNAL_ALERTING_BACKFILL_API_PATH,
INTERNAL_ALERTING_BACKFILL_FIND_API_PATH,
INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH,
} from '@kbn/alerting-plugin/common';
import type { FindBackfillResponseBody } from '@kbn/alerting-plugin/common/routes/backfill/apis/find';
import type { ScheduleBackfillResponseBody } from '@kbn/alerting-plugin/common/routes/backfill/apis/schedule';
import { KibanaServices } from '../../../common/lib/kibana';
import type { ScheduleBackfillProps } from '../types';

/**
* Schedule rules run over a specified time range
*
* @param ruleIds `rule_id`s of each rule to be backfilled
* @param timeRange the time range over which the backfill should apply
*
* @throws An error if response is not OK
*/
export const scheduleRuleRun = async ({
ruleIds,
timeRange,
}: ScheduleBackfillProps): Promise<ScheduleBackfillResponseBody> => {
const params = ruleIds.map((ruleId) => {
return {
rule_id: ruleId,
start: timeRange.startDate.toISOString(),
end: timeRange.endDate.toISOString(),
};
});
return KibanaServices.get().http.fetch<ScheduleBackfillResponseBody>(
INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH,
{
method: 'POST',
version: '2023-10-31',
body: JSON.stringify(params),
}
);
};

/**
* Find backfills for the given rule IDs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 moment from 'moment';

import { act } from '@testing-library/react-hooks';
import { useScheduleRuleRunMutation } from './use_schedule_rule_run_mutation';
import { renderMutation } from '../../../../management/hooks/test_utils';
import { scheduleRuleRunMock } from '../../logic/__mocks__/mock';
import { INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH } from '@kbn/alerting-plugin/common';

import { KibanaServices } from '../../../../common/lib/kibana';

const mockKibanaServices = KibanaServices.get as jest.Mock;
jest.mock('../../../../common/lib/kibana');

const fetchMock = jest.fn();
mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } });

const apiVersion = '2023-10-31';

describe('Schedule rule run hook', () => {
let result: ReturnType<typeof useScheduleRuleRunMutation>;

beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(scheduleRuleRunMock);
});

it('schedules a rule run by calling the backfill API', async () => {
result = await renderMutation(() => useScheduleRuleRunMutation());

expect(fetchMock).toHaveBeenCalledTimes(0);

const timeRange = { startDate: moment().subtract(1, 'd'), endDate: moment() };

await act(async () => {
const res = await result.mutateAsync({ ruleIds: ['rule-1'], timeRange });
expect(res).toEqual(scheduleRuleRunMock);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock).toHaveBeenCalledWith(INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH, {
body: `[{"rule_id":"rule-1","start":"${timeRange.startDate.toISOString()}","end":"${timeRange.endDate.toISOString()}"}]`,
method: 'POST',
version: apiVersion,
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 { INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH } from '@kbn/alerting-plugin/common';
import type { UseMutationOptions } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import type { ScheduleBackfillProps } from '../../types';
import { scheduleRuleRun } from '../api';

export const SCHEDULE_RULE_RUN_MUTATION_KEY = [
'POST',
INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH,
];

export const useScheduleRuleRunMutation = (
options?: UseMutationOptions<unknown, Error, ScheduleBackfillProps>
) => {
return useMutation((scheduleOptions: ScheduleBackfillProps) => scheduleRuleRun(scheduleOptions), {
...options,
mutationKey: SCHEDULE_RULE_RUN_MUTATION_KEY,
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 React from 'react';
import { render, within } from '@testing-library/react';
import { ManualRuleRunModal } from '.';

describe('ManualRuleRunModal', () => {
const onCancelMock = jest.fn();
const onConfirmMock = jest.fn();

afterEach(() => {
onCancelMock.mockReset();
onConfirmMock.mockReset();
});

it('should render modal', () => {
const wrapper = render(
<ManualRuleRunModal onCancel={onCancelMock} onConfirm={onConfirmMock} />
);

expect(wrapper.getByTestId('manual-rule-run-modal-form')).toBeInTheDocument();
expect(wrapper.getByTestId('confirmModalCancelButton')).toBeEnabled();
expect(wrapper.getByTestId('confirmModalConfirmButton')).toBeEnabled();
});

it('should render confirmation button disabled if invalid time range has been selected', () => {
const wrapper = render(
<ManualRuleRunModal onCancel={onCancelMock} onConfirm={onConfirmMock} />
);

expect(wrapper.getByTestId('confirmModalConfirmButton')).toBeEnabled();

within(wrapper.getByTestId('end-date-picker')).getByText('Previous Month').click();

expect(wrapper.getByTestId('confirmModalConfirmButton')).toBeDisabled();
expect(wrapper.getByTestId('manual-rule-run-time-range-form')).toHaveTextContent(
'Selected time range is invalid'
);
});

it('should render confirmation button disabled if selected start date is more than 90 days in the past', () => {
const wrapper = render(
<ManualRuleRunModal onCancel={onCancelMock} onConfirm={onConfirmMock} />
);

expect(wrapper.getByTestId('confirmModalConfirmButton')).toBeEnabled();

within(wrapper.getByTestId('start-date-picker')).getByText('Previous Month').click();
within(wrapper.getByTestId('start-date-picker')).getByText('Previous Month').click();
within(wrapper.getByTestId('start-date-picker')).getByText('Previous Month').click();
within(wrapper.getByTestId('start-date-picker')).getByText('Previous Month').click();

expect(wrapper.getByTestId('confirmModalConfirmButton')).toBeDisabled();
expect(wrapper.getByTestId('manual-rule-run-time-range-form')).toHaveTextContent(
'Manual rule run cannot be scheduled earlier than 90 days ago'
);
});

it('should render confirmation button disabled if selected end date is in future', () => {
const wrapper = render(
<ManualRuleRunModal onCancel={onCancelMock} onConfirm={onConfirmMock} />
);

expect(wrapper.getByTestId('confirmModalConfirmButton')).toBeEnabled();

within(wrapper.getByTestId('end-date-picker')).getByText('Next month').click();

expect(wrapper.getByTestId('confirmModalConfirmButton')).toBeDisabled();
expect(wrapper.getByTestId('manual-rule-run-time-range-form')).toHaveTextContent(
'Manual rule run cannot be scheduled for the future'
);
});
});
Loading