-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Solution] Address guided onboarding feedback for the rules …
…area (#145223) **Related to: #144016 ## Summary This follow-up PR addresses guided onboarding feedback mentioned [here](elastic/security-team#5386) and [here](#144458). To summarize: - We're keeping the first step (install prebuilt rules) intact, but most users wouldn't see it as the rules are installed automatically during previous stages. This step is needed to cover edge cases when rules were deleted for some reason. - We're splitting the second step into two: 1) search the first rule and 2) activate it. - We're adding "Next" buttons to these steps. - For the search step, the "Next" button will automatically filter the rules table, so the first rule becomes visible. - For the activate step, the "Next" button automatically activates the first rule. - The "Next" button stays optional; we still automatically progress the guide once user actions satisfy certain conditions, like the user filtered the rules table manually or activated the first rule by clicking its toggle.
- Loading branch information
Showing
10 changed files
with
264 additions
and
160 deletions.
There are no files selected for viewing
118 changes: 0 additions & 118 deletions
118
...etection_engine/rule_management_ui/components/guided_onboarding/rules_management_tour.tsx
This file was deleted.
Oops, something went wrong.
36 changes: 0 additions & 36 deletions
36
...n/public/detection_engine/rule_management_ui/components/guided_onboarding/translations.ts
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
180 changes: 180 additions & 0 deletions
180
...agement_ui/components/rules_table/rules_table/guided_onboarding/rules_management_tour.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
/* | ||
* 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 { EuiTourActions, EuiTourStepProps } from '@elastic/eui'; | ||
import { EuiButton, EuiTourStep } from '@elastic/eui'; | ||
import { noop } from 'lodash'; | ||
import React, { useCallback, useEffect, useMemo } from 'react'; | ||
import useObservable from 'react-use/lib/useObservable'; | ||
import { of } from 'rxjs'; | ||
import { BulkActionType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; | ||
import { useKibana } from '../../../../../../common/lib/kibana'; | ||
import { useFindRulesQuery } from '../../../../../rule_management/api/hooks/use_find_rules_query'; | ||
import { useExecuteBulkAction } from '../../../../../rule_management/logic/bulk_actions/use_execute_bulk_action'; | ||
import { useRulesTableContext } from '../rules_table_context'; | ||
import * as i18n from './translations'; | ||
import { useIsElementMounted } from './use_is_element_mounted'; | ||
|
||
export const INSTALL_PREBUILT_RULES_ANCHOR = 'install-prebuilt-rules-anchor'; | ||
export const SEARCH_FIRST_RULE_ANCHOR = 'search-first-rule-anchor'; | ||
|
||
export interface RulesFeatureTourContextType { | ||
steps: EuiTourStepProps[]; | ||
actions: EuiTourActions; | ||
} | ||
|
||
const GUIDED_ONBOARDING_RULES_FILTER = { | ||
filter: '', | ||
showCustomRules: false, | ||
showElasticRules: true, | ||
tags: ['Guided Onboarding'], | ||
}; | ||
|
||
export enum GuidedOnboardingRulesStatus { | ||
'inactive' = 'inactive', | ||
'installRules' = 'installRules', | ||
'searchRules' = 'searchRules', | ||
'enableRules' = 'enableRules', | ||
'completed' = 'completed', | ||
} | ||
|
||
export const RulesManagementTour = () => { | ||
const { guidedOnboardingApi } = useKibana().services.guidedOnboarding; | ||
const { executeBulkAction } = useExecuteBulkAction(); | ||
const { actions } = useRulesTableContext(); | ||
|
||
const isRulesStepActive = useObservable( | ||
guidedOnboardingApi?.isGuideStepActive$('security', 'rules') ?? of(false), | ||
false | ||
); | ||
|
||
const { data: onboardingRules } = useFindRulesQuery( | ||
{ filterOptions: GUIDED_ONBOARDING_RULES_FILTER }, | ||
{ enabled: isRulesStepActive } | ||
); | ||
|
||
const demoRule = useMemo(() => { | ||
// Rules are loading, cannot search for rule ID | ||
if (!onboardingRules?.rules.length) { | ||
return; | ||
} | ||
// Return any rule, first one is good enough | ||
return onboardingRules.rules[0]; | ||
}, [onboardingRules]); | ||
|
||
const ruleSwitchAnchor = demoRule ? `rule-switch-${demoRule.id}` : ''; | ||
|
||
/** | ||
* Wait until the tour target elements are visible on the page and mount | ||
* EuiTourStep components only after that. Otherwise, the tours would never | ||
* show up on the page. | ||
*/ | ||
const isInstallRulesAnchorMounted = useIsElementMounted(INSTALL_PREBUILT_RULES_ANCHOR); | ||
const isSearchFirstRuleAnchorMounted = useIsElementMounted(SEARCH_FIRST_RULE_ANCHOR); | ||
const isActivateFirstRuleAnchorMounted = useIsElementMounted(ruleSwitchAnchor); | ||
|
||
const tourStatus = useMemo(() => { | ||
if (!isRulesStepActive || !onboardingRules) { | ||
return GuidedOnboardingRulesStatus.inactive; | ||
} | ||
|
||
if (onboardingRules.total === 0) { | ||
// Onboarding rules are not installed - show the install/update rules step | ||
return GuidedOnboardingRulesStatus.installRules; | ||
} | ||
|
||
if (demoRule?.enabled) { | ||
// Rules are installed and enabled - the tour is completed | ||
return GuidedOnboardingRulesStatus.completed; | ||
} | ||
|
||
// Rule is installed but not enabled - show the find and activate steps | ||
if (isActivateFirstRuleAnchorMounted) { | ||
// If rule is visible on the table, show the activation step | ||
return GuidedOnboardingRulesStatus.enableRules; | ||
} else { | ||
// If rule is not visible on the table, show the search step | ||
return GuidedOnboardingRulesStatus.searchRules; | ||
} | ||
}, [demoRule?.enabled, isActivateFirstRuleAnchorMounted, isRulesStepActive, onboardingRules]); | ||
|
||
// Synchronize the current "internal" tour step with the global one | ||
useEffect(() => { | ||
if (isRulesStepActive && tourStatus === GuidedOnboardingRulesStatus.completed) { | ||
guidedOnboardingApi?.completeGuideStep('security', 'rules'); | ||
} | ||
}, [guidedOnboardingApi, isRulesStepActive, tourStatus]); | ||
|
||
const enableDemoRule = useCallback(async () => { | ||
if (demoRule) { | ||
await executeBulkAction({ | ||
type: BulkActionType.enable, | ||
ids: [demoRule.id], | ||
}); | ||
} | ||
}, [demoRule, executeBulkAction]); | ||
|
||
const findDemoRule = useCallback(() => { | ||
if (demoRule) { | ||
actions.setFilterOptions({ | ||
filter: demoRule.name, | ||
}); | ||
} | ||
}, [actions, demoRule]); | ||
|
||
return ( | ||
<> | ||
{isInstallRulesAnchorMounted && ( | ||
<EuiTourStep | ||
title={i18n.INSTALL_PREBUILT_RULES_TITLE} | ||
content={i18n.INSTALL_PREBUILT_RULES_CONTENT} | ||
onFinish={noop} | ||
step={1} | ||
stepsTotal={3} | ||
isOpen={tourStatus === GuidedOnboardingRulesStatus.installRules} | ||
anchor={`#${INSTALL_PREBUILT_RULES_ANCHOR}`} | ||
anchorPosition="downCenter" | ||
footerAction={<div />} // Replace "Skip tour" with an empty element | ||
/> | ||
)} | ||
{isSearchFirstRuleAnchorMounted && demoRule && ( | ||
<EuiTourStep | ||
title={i18n.SEARCH_FIRST_RULE_TITLE(demoRule.name)} | ||
content={i18n.SEARCH_FIRST_RULE_CONTENT(demoRule.name)} | ||
onFinish={noop} | ||
step={2} | ||
stepsTotal={3} | ||
isOpen={tourStatus === GuidedOnboardingRulesStatus.searchRules} | ||
anchor={`#${SEARCH_FIRST_RULE_ANCHOR}`} | ||
anchorPosition="upCenter" | ||
footerAction={ | ||
<EuiButton size="s" color="success" fill onClick={findDemoRule}> | ||
{i18n.NEXT_BUTTON} | ||
</EuiButton> | ||
} | ||
/> | ||
)} | ||
{isActivateFirstRuleAnchorMounted && demoRule && ( | ||
<EuiTourStep | ||
title={i18n.ENABLE_FIRST_RULE_TITLE(demoRule.name)} | ||
content={i18n.ENABLE_FIRST_RULE_CONTENT(demoRule.name)} | ||
onFinish={noop} | ||
step={3} | ||
stepsTotal={3} | ||
isOpen={tourStatus === GuidedOnboardingRulesStatus.enableRules} | ||
anchor={`#${ruleSwitchAnchor}`} | ||
anchorPosition="upCenter" | ||
footerAction={ | ||
<EuiButton size="s" color="success" fill onClick={enableDemoRule}> | ||
{i18n.NEXT_BUTTON} | ||
</EuiButton> | ||
} | ||
/> | ||
)} | ||
</> | ||
); | ||
}; |
Oops, something went wrong.