-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Security Solution] Address guided onboarding feedback for the rules area #145223
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea that only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I like that idea 👍 We could improve that in a follow-up if you don't mind. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good |
||
{ 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> | ||
} | ||
/> | ||
)} | ||
</> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just curious why enum keys are string literals while it can be started with capital case keys as in the TypeScript docs? So it could be
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, no intention here, to be honest. FWIW, in security solution, enum formats are not very consistent, but the most widespread one is CamelCase for names and snake_case for members:
I'd stick to it for consistency.