Skip to content

Commit

Permalink
[8.x] [Security Solution] Adds enable on install UI workflow to prebu…
Browse files Browse the repository at this point in the history
…ilt rules page (#191529) (#193368)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution] Adds enable on install UI workflow to prebuilt
rules page (#191529)](#191529)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Davis
Plumlee","email":"56367316+dplumlee@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-09-18T21:56:10Z","message":"[Security
Solution] Adds enable on install UI workflow to prebuilt rules page
(#191529)\n\n## Summary\r\n\r\nAdds overflow button UI to all prebuilt
rules install buttons in order\r\nto enable the rule when it is
successfully installed. Previously, a user\r\nwould have to navigate
back to the rules page and find the rule(s) they\r\njust installed to
enable, this combines those two workflows into a\r\nsingle button action
- speeding up the out of the box rule\r\nimplementation.\r\n\r\n###
Screenshots\r\n**Prebuilt rules table columns**\r\n<img width=\"530\"
alt=\"Screenshot 2024-09-04 at 10 38
05 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/4a009afa-a8f0-4eaa-a76b-8f4e509f35a3\">\r\n\r\n\r\n**Prebuilt
rules table bulk install**\r\n<img width=\"1478\" alt=\"Screenshot
2024-09-04 at 10 38
16 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/eb6deb9b-9b4e-4be3-a4ac-0da06d6f1e8e\">\r\n\r\n\r\n**Prebuilt
rule details flyout**\r\n<img width=\"1489\" alt=\"Screenshot 2024-09-04
at 10 38
44 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/a4bce22d-7e90-42e4-8522-cf411a297659\">\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"3bea483f34e03ea1bbeb836350c650fd06673f10","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","v9.0.0","Team:Detections
and Resp","Team: SecuritySolution","Team:Detection Rule
Management","Feature:Prebuilt Detection
Rules","v8.16.0"],"title":"[Security Solution] Adds enable on install UI
workflow to prebuilt rules
page","number":191529,"url":"https://github.com/elastic/kibana/pull/191529","mergeCommit":{"message":"[Security
Solution] Adds enable on install UI workflow to prebuilt rules page
(#191529)\n\n## Summary\r\n\r\nAdds overflow button UI to all prebuilt
rules install buttons in order\r\nto enable the rule when it is
successfully installed. Previously, a user\r\nwould have to navigate
back to the rules page and find the rule(s) they\r\njust installed to
enable, this combines those two workflows into a\r\nsingle button action
- speeding up the out of the box rule\r\nimplementation.\r\n\r\n###
Screenshots\r\n**Prebuilt rules table columns**\r\n<img width=\"530\"
alt=\"Screenshot 2024-09-04 at 10 38
05 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/4a009afa-a8f0-4eaa-a76b-8f4e509f35a3\">\r\n\r\n\r\n**Prebuilt
rules table bulk install**\r\n<img width=\"1478\" alt=\"Screenshot
2024-09-04 at 10 38
16 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/eb6deb9b-9b4e-4be3-a4ac-0da06d6f1e8e\">\r\n\r\n\r\n**Prebuilt
rule details flyout**\r\n<img width=\"1489\" alt=\"Screenshot 2024-09-04
at 10 38
44 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/a4bce22d-7e90-42e4-8522-cf411a297659\">\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"3bea483f34e03ea1bbeb836350c650fd06673f10"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/191529","number":191529,"mergeCommit":{"message":"[Security
Solution] Adds enable on install UI workflow to prebuilt rules page
(#191529)\n\n## Summary\r\n\r\nAdds overflow button UI to all prebuilt
rules install buttons in order\r\nto enable the rule when it is
successfully installed. Previously, a user\r\nwould have to navigate
back to the rules page and find the rule(s) they\r\njust installed to
enable, this combines those two workflows into a\r\nsingle button action
- speeding up the out of the box rule\r\nimplementation.\r\n\r\n###
Screenshots\r\n**Prebuilt rules table columns**\r\n<img width=\"530\"
alt=\"Screenshot 2024-09-04 at 10 38
05 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/4a009afa-a8f0-4eaa-a76b-8f4e509f35a3\">\r\n\r\n\r\n**Prebuilt
rules table bulk install**\r\n<img width=\"1478\" alt=\"Screenshot
2024-09-04 at 10 38
16 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/eb6deb9b-9b4e-4be3-a4ac-0da06d6f1e8e\">\r\n\r\n\r\n**Prebuilt
rule details flyout**\r\n<img width=\"1489\" alt=\"Screenshot 2024-09-04
at 10 38
44 AM\"\r\nsrc=\"https://github.com/user-attachments/assets/a4bce22d-7e90-42e4-8522-cf411a297659\">\r\n\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [ ] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n\r\n\r\n### For maintainers\r\n\r\n-
[ ] This was checked for breaking API changes and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"3bea483f34e03ea1bbeb836350c650fd06673f10"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 19, 2024
1 parent 676d73f commit ef37576
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,27 @@ import { useInvalidateFindRulesQuery } from '../use_find_rules_query';
import { useInvalidateFetchRuleManagementFiltersQuery } from '../use_fetch_rule_management_filters_query';
import { useInvalidateFetchRulesSnoozeSettingsQuery } from '../use_fetch_rules_snooze_settings_query';
import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './use_fetch_prebuilt_rules_install_review_query';
import type { BulkAction } from '../../api';
import { performInstallSpecificRules } from '../../api';
import { useInvalidateFetchCoverageOverviewQuery } from '../use_fetch_coverage_overview_query';
import { useBulkActionMutation } from '../use_bulk_action_mutation';

export const PERFORM_SPECIFIC_RULES_INSTALLATION_KEY = [
'POST',
'SPECIFIC_RULES',
PERFORM_RULE_INSTALLATION_URL,
];

export interface UsePerformSpecificRulesInstallParams {
rules: InstallSpecificRulesRequest['rules'];
enable?: boolean;
}

export const usePerformSpecificRulesInstallMutation = (
options?: UseMutationOptions<
PerformRuleInstallationResponseBody,
Error,
InstallSpecificRulesRequest['rules']
UsePerformSpecificRulesInstallParams
>
) => {
const invalidateFindRulesQuery = useInvalidateFindRulesQuery();
Expand All @@ -40,15 +47,15 @@ export const usePerformSpecificRulesInstallMutation = (
useInvalidateFetchPrebuiltRulesInstallReviewQuery();
const invalidateRuleStatus = useInvalidateFetchPrebuiltRulesStatusQuery();
const invalidateFetchCoverageOverviewQuery = useInvalidateFetchCoverageOverviewQuery();
const { mutateAsync } = useBulkActionMutation();

return useMutation<
PerformRuleInstallationResponseBody,
Error,
InstallSpecificRulesRequest['rules']
UsePerformSpecificRulesInstallParams
>(
(rulesToInstall: InstallSpecificRulesRequest['rules']) => {
return performInstallSpecificRules(rulesToInstall);
},
(rulesToInstall: UsePerformSpecificRulesInstallParams) =>
performInstallSpecificRules(rulesToInstall.rules),
{
...options,
mutationKey: PERFORM_SPECIFIC_RULES_INSTALLATION_KEY,
Expand All @@ -62,6 +69,14 @@ export const usePerformSpecificRulesInstallMutation = (
invalidateRuleStatus();
invalidateFetchCoverageOverviewQuery();

const [response, , { enable }] = args;

if (response && enable) {
const ruleIdsToEnable = response.results.created.map((rule) => rule.id);
const bulkAction: BulkAction = { type: 'enable', ids: ruleIdsToEnable };
mutateAsync({ bulkAction });
}

if (options?.onSettled) {
options.onSettled(...args);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@
* 2.0.
*/

import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import React from 'react';
import {
EuiButton,
EuiButtonIcon,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiPopover,
} from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { useBoolean } from 'react-use';
import { useUserData } from '../../../../../detections/components/user_info';
import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context';
import * as i18n from './translations';
Expand All @@ -31,19 +41,69 @@ export const AddPrebuiltRulesHeaderButtons = () => {
const isRuleInstalling = loadingRules.length > 0;
const isRequestInProgress = isRuleInstalling || isRefetching || isUpgradingSecurityPackages;

const [isOverflowPopoverOpen, setOverflowPopover] = useBoolean(false);

const onOverflowButtonClick = () => {
setOverflowPopover(!isOverflowPopoverOpen);
};

const closeOverflowPopover = useCallback(() => {
setOverflowPopover(false);
}, [setOverflowPopover]);

const enableOnClick = useCallback(() => {
installSelectedRules(true);
closeOverflowPopover();
}, [closeOverflowPopover, installSelectedRules]);

const installOnClick = useCallback(() => {
installSelectedRules();
}, [installSelectedRules]);

const overflowItems = useMemo(
() => [
<EuiContextMenuItem key="copy" icon={'play'} onClick={enableOnClick}>
{i18n.INSTALL_AND_ENABLE_BUTTON_LABEL}
</EuiContextMenuItem>,
],
[enableOnClick]
);

return (
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
{shouldDisplayInstallSelectedRulesButton ? (
<EuiFlexItem grow={false}>
<EuiButton
onClick={installSelectedRules}
disabled={!canUserEditRules || isRequestInProgress}
data-test-subj="installSelectedRulesButton"
>
{i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)}
{isRuleInstalling ? <EuiLoadingSpinner size="s" /> : undefined}
</EuiButton>
</EuiFlexItem>
<>
<EuiFlexItem grow={false}>
<EuiButton
onClick={installOnClick}
disabled={!canUserEditRules || isRequestInProgress}
data-test-subj="installSelectedRulesButton"
>
{i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)}
{isRuleInstalling && <EuiLoadingSpinner size="s" />}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
button={
<EuiButtonIcon
display="base"
size="m"
iconType="boxesVertical"
aria-label={i18n.INSTALL_RULES_OVERFLOW_BUTTON_ARIA_LABEL}
onClick={onOverflowButtonClick}
disabled={!canUserEditRules || isRequestInProgress}
/>
}
isOpen={isOverflowPopoverOpen}
closePopover={closeOverflowPopover}
panelPaddingSize="s"
anchorPosition="downRight"
>
<EuiContextMenuPanel size="s" items={overflowItems} />
</EuiPopover>
</EuiFlexItem>
</>
) : null}
<EuiFlexItem grow={false}>
<EuiButton
Expand All @@ -55,7 +115,7 @@ export const AddPrebuiltRulesHeaderButtons = () => {
aria-label={i18n.INSTALL_ALL_ARIA_LABEL}
>
{i18n.INSTALL_ALL}
{isRuleInstalling ? <EuiLoadingSpinner size="s" /> : undefined}
{isRuleInstalling && <EuiLoadingSpinner size="s" />}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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 {
EuiButtonEmpty,
EuiButtonIcon,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiPopover,
} from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import { useBoolean } from 'react-use';
import type { Rule } from '../../../../rule_management/logic';
import type { RuleSignatureId } from '../../../../../../common/api/detection_engine';
import type { AddPrebuiltRulesTableActions } from './add_prebuilt_rules_table_context';
import * as i18n from './translations';

export interface PrebuiltRulesInstallButtonProps {
ruleId: RuleSignatureId;
record: Rule;
installOneRule: AddPrebuiltRulesTableActions['installOneRule'];
loadingRules: RuleSignatureId[];
isDisabled: boolean;
}

export const PrebuiltRulesInstallButton = ({
ruleId,
record,
installOneRule,
loadingRules,
isDisabled,
}: PrebuiltRulesInstallButtonProps) => {
const isRuleInstalling = loadingRules.includes(ruleId);
const isInstallButtonDisabled = isRuleInstalling || isDisabled;
const [isPopoverOpen, setPopover] = useBoolean(false);

const onOverflowButtonClick = useCallback(() => {
setPopover(!isPopoverOpen);
}, [isPopoverOpen, setPopover]);

const closeOverflowPopover = useCallback(() => {
setPopover(false);
}, [setPopover]);

const enableOnClick = useCallback(() => {
installOneRule(ruleId, true);
closeOverflowPopover();
}, [closeOverflowPopover, installOneRule, ruleId]);

const installOnClick = useCallback(() => {
installOneRule(ruleId);
}, [installOneRule, ruleId]);

const overflowItems = useMemo(
() => [
<EuiContextMenuItem key="copy" icon={'play'} onClick={enableOnClick}>
{i18n.INSTALL_AND_ENABLE_BUTTON_LABEL}
</EuiContextMenuItem>,
],
[enableOnClick]
);

const popoverButton = useMemo(
() => (
<EuiButtonIcon
display="empty"
size="s"
iconType="boxesVertical"
aria-label={i18n.INSTALL_RULES_OVERFLOW_BUTTON_ARIA_LABEL}
onClick={onOverflowButtonClick}
disabled={isInstallButtonDisabled}
/>
),
[isInstallButtonDisabled, onOverflowButtonClick]
);

if (isRuleInstalling) {
return (
<EuiLoadingSpinner
size="s"
data-test-subj={`installSinglePrebuiltRuleButton-loadingSpinner-${ruleId}`}
/>
);
}
return (
<EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="s"
disabled={isInstallButtonDisabled}
onClick={installOnClick}
data-test-subj={`installSinglePrebuiltRuleButton-${ruleId}`}
aria-label={i18n.INSTALL_RULE_BUTTON_ARIA_LABEL(record.name)}
>
{i18n.INSTALL_BUTTON_LABEL}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
button={popoverButton}
isOpen={isPopoverOpen}
closePopover={closeOverflowPopover}
panelPaddingSize="s"
anchorPosition="downRight"
>
<EuiContextMenuPanel size="s" items={overflowItems} />
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
);
};
Loading

0 comments on commit ef37576

Please sign in to comment.