diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts index e52a5cd8e0618..647230982c834 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts @@ -13,6 +13,9 @@ import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt import { useInvalidateFindRulesQuery, useUpdateRulesCache } from './use_find_rules_query'; import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query'; import { useInvalidateFetchRuleByIdQuery } from './use_fetch_rule_by_id_query'; +import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/constants'; + +export const BULK_ACTION_MUTATION_KEY = ['POST', DETECTION_ENGINE_RULES_BULK_ACTION]; export const useBulkActionMutation = ( options?: UseMutationOptions @@ -27,6 +30,7 @@ export const useBulkActionMutation = ( (action: BulkActionProps) => performBulkAction(action), { ...options, + mutationKey: BULK_ACTION_MUTATION_KEY, onSuccess: (...args) => { const [res, { action }] = args; switch (action) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts index bcc5fbcdbb18e..623db44af6098 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts @@ -6,14 +6,20 @@ */ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; +import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/constants'; import type { BulkExportProps, BulkExportResponse } from '../api'; import { bulkExportRules } from '../api'; +export const BULK_ACTION_MUTATION_KEY = ['POST', DETECTION_ENGINE_RULES_BULK_ACTION]; + export const useBulkExportMutation = ( options?: UseMutationOptions ) => { return useMutation( (action: BulkExportProps) => bulkExportRules(action), - options + { + ...options, + mutationKey: BULK_ACTION_MUTATION_KEY, + } ); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_prebuilt_rules_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_prebuilt_rules_mutation.ts index 2559be0609d08..86c6efbd50f85 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_prebuilt_rules_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_prebuilt_rules_mutation.ts @@ -11,6 +11,9 @@ import { createPrepackagedRules } from '../api'; import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query'; import { useInvalidateFindRulesQuery } from './use_find_rules_query'; import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query'; +import { PREBUILT_RULES_URL } from '../../../../../common/detection_engine/prebuilt_rules/api/urls'; + +export const CREATE_PREBUILT_RULES_MUTATION_KEY = ['PUT', PREBUILT_RULES_URL]; export const useCreatePrebuiltRulesMutation = ( options?: UseMutationOptions @@ -21,6 +24,7 @@ export const useCreatePrebuiltRulesMutation = ( return useMutation(() => createPrepackagedRules(), { ...options, + mutationKey: CREATE_PREBUILT_RULES_MUTATION_KEY, onSuccess: (...args) => { // Always invalidate all rules and the prepackaged rules status cache as // the number of rules might change after the installation diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_rule_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_rule_mutation.ts index 8d62927a6261f..56a3d67492713 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_rule_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_rule_mutation.ts @@ -6,6 +6,7 @@ */ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import type { RuleCreateProps, RuleResponse, @@ -16,6 +17,8 @@ import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query'; import { useInvalidateFindRulesQuery } from './use_find_rules_query'; +export const CREATE_RULE_MUTATION_KEY = ['POST', DETECTION_ENGINE_RULES_URL]; + export const useCreateRuleMutation = ( options?: UseMutationOptions ) => { @@ -27,6 +30,7 @@ export const useCreateRuleMutation = ( (rule: RuleCreateProps) => createRule({ rule: transformOutput(rule) }), { ...options, + mutationKey: CREATE_RULE_MUTATION_KEY, onSuccess: (...args) => { invalidateFetchPrePackagedRulesStatusQuery(); invalidateFindRulesQuery(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query.ts index a0344386ffe04..5fd22fae143cb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query.ts @@ -10,14 +10,15 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { getPrePackagedRulesStatus } from '../api'; import { DEFAULT_QUERY_OPTIONS } from './constants'; import type { PrePackagedRulesStatusResponse } from '../../logic'; +import { PREBUILT_RULES_STATUS_URL } from '../../../../../common/detection_engine/prebuilt_rules/api/urls'; -export const PREBUILT_RULES_STATUS_QUERY_KEY = 'prePackagedRulesStatus'; +export const PREBUILT_RULES_STATUS_QUERY_KEY = ['GET', PREBUILT_RULES_STATUS_URL]; export const useFetchPrebuiltRulesStatusQuery = ( - options: UseQueryOptions + options?: UseQueryOptions ) => { return useQuery( - [PREBUILT_RULES_STATUS_QUERY_KEY], + PREBUILT_RULES_STATUS_QUERY_KEY, async ({ signal }) => { const response = await getPrePackagedRulesStatus({ signal }); return response; @@ -40,7 +41,7 @@ export const useInvalidateFetchPrebuiltRulesStatusQuery = () => { const queryClient = useQueryClient(); return useCallback(() => { - queryClient.invalidateQueries([PREBUILT_RULES_STATUS_QUERY_KEY], { + queryClient.invalidateQueries(PREBUILT_RULES_STATUS_QUERY_KEY, { refetchType: 'active', }); }, [queryClient]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts index 03fe7c6e2df17..66539807787ff 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts @@ -8,12 +8,13 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { transformInput } from '../../../../detections/containers/detection_engine/rules/transforms'; import type { Rule } from '../../logic'; import { fetchRuleById } from '../api'; import { DEFAULT_QUERY_OPTIONS } from './constants'; -const FIND_ONE_RULE_QUERY_KEY = 'findOneRule'; +const FIND_ONE_RULE_QUERY_KEY = ['GET', DETECTION_ENGINE_RULES_URL]; /** * A wrapper around useQuery provides default values to the underlying query, @@ -23,9 +24,9 @@ const FIND_ONE_RULE_QUERY_KEY = 'findOneRule'; * @param options - react-query options * @returns useQuery result */ -export const useFetchRuleByIdQuery = (id: string, options: UseQueryOptions) => { +export const useFetchRuleByIdQuery = (id: string, options?: UseQueryOptions) => { return useQuery( - [FIND_ONE_RULE_QUERY_KEY, id], + [...FIND_ONE_RULE_QUERY_KEY, id], async ({ signal }) => { const response = await fetchRuleById({ signal, id }); @@ -49,7 +50,7 @@ export const useInvalidateFetchRuleByIdQuery = () => { const queryClient = useQueryClient(); return useCallback(() => { - queryClient.invalidateQueries([FIND_ONE_RULE_QUERY_KEY], { + queryClient.invalidateQueries(FIND_ONE_RULE_QUERY_KEY, { refetchType: 'active', }); }, [queryClient]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_tags_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_tags_query.ts index 1be43f992f07f..c09ae5d6cb56d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_tags_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_tags_query.ts @@ -8,12 +8,12 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; +import { DETECTION_ENGINE_TAGS_URL } from '../../../../../common/constants'; import type { FetchTagsResponse } from '../api'; import { fetchTags } from '../api'; import { DEFAULT_QUERY_OPTIONS } from './constants'; -// TODO: https://github.com/elastic/kibana/pull/142950 Let's use more detailed cache keys, e.g. ['GET', DETECTION_ENGINE_TAGS_URL] -const TAGS_QUERY_KEY = 'tags'; +const TAGS_QUERY_KEY = ['GET', DETECTION_ENGINE_TAGS_URL]; /** * Hook for using the list of Tags from the Detection Engine API @@ -21,7 +21,7 @@ const TAGS_QUERY_KEY = 'tags'; */ export const useFetchTagsQuery = (options?: UseQueryOptions) => { return useQuery( - [TAGS_QUERY_KEY], + TAGS_QUERY_KEY, async ({ signal }) => { return fetchTags({ signal }); }, @@ -36,7 +36,7 @@ export const useInvalidateFetchTagsQuery = () => { const queryClient = useQueryClient(); return useCallback(() => { - queryClient.invalidateQueries([TAGS_QUERY_KEY], { + queryClient.invalidateQueries(TAGS_QUERY_KEY, { refetchType: 'active', }); }, [queryClient]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_find_rules_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_find_rules_query.ts index ad50ab471a7fd..35b6430a51172 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_find_rules_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_find_rules_query.ts @@ -8,6 +8,7 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; +import { DETECTION_ENGINE_RULES_URL_FIND } from '../../../../../common/constants'; import type { FilterOptions, PaginationOptions, Rule, SortingOptions } from '../../logic'; import { fetchRules } from '../api'; import { DEFAULT_QUERY_OPTIONS } from './constants'; @@ -18,7 +19,7 @@ export interface FindRulesQueryArgs { pagination?: Pick; } -const FIND_RULES_QUERY_KEY = 'findRules'; +const FIND_RULES_QUERY_KEY = ['GET', DETECTION_ENGINE_RULES_URL_FIND]; export interface RulesQueryResponse { rules: Rule[]; @@ -37,7 +38,7 @@ export interface RulesQueryResponse { */ export const useFindRulesQuery = ( queryArgs: FindRulesQueryArgs, - queryOptions: UseQueryOptions< + queryOptions?: UseQueryOptions< RulesQueryResponse, Error, RulesQueryResponse, @@ -45,7 +46,7 @@ export const useFindRulesQuery = ( > ) => { return useQuery( - [FIND_RULES_QUERY_KEY, queryArgs], + [...FIND_RULES_QUERY_KEY, queryArgs], async ({ signal }) => { const response = await fetchRules({ signal, ...queryArgs }); @@ -73,7 +74,7 @@ export const useInvalidateFindRulesQuery = () => { * Invalidate all queries that start with FIND_RULES_QUERY_KEY. This * includes the in-memory query cache and paged query cache. */ - queryClient.invalidateQueries([FIND_RULES_QUERY_KEY], { + queryClient.invalidateQueries(FIND_RULES_QUERY_KEY, { refetchType: 'active', }); }, [queryClient]); @@ -98,7 +99,7 @@ export const useUpdateRulesCache = () => { return useCallback( (newRules: Rule[]) => { queryClient.setQueriesData['data']>( - [FIND_RULES_QUERY_KEY], + FIND_RULES_QUERY_KEY, (currentData) => currentData ? { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts index 6f15fb4fdd8ce..d0b60ccb9b89a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts @@ -15,6 +15,9 @@ import { updateRule } from '../api'; import { useInvalidateFindRulesQuery } from './use_find_rules_query'; import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query'; import { useInvalidateFetchRuleByIdQuery } from './use_fetch_rule_by_id_query'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; + +export const UPDATE_RULE_MUTATION_KEY = ['PUT', DETECTION_ENGINE_RULES_URL]; export const useUpdateRuleMutation = ( options?: UseMutationOptions @@ -27,6 +30,7 @@ export const useUpdateRuleMutation = ( (rule: RuleUpdateProps) => updateRule({ rule: transformOutput(rule) }), { ...options, + mutationKey: UPDATE_RULE_MUTATION_KEY, onSuccess: (...args) => { invalidateFindRulesQuery(); invalidateFetchRuleByIdQuery(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/rules_management_tour.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/rules_management_tour.tsx new file mode 100644 index 0000000000000..1fcb56a009edb --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/rules_management_tour.tsx @@ -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 type { EuiTourActions, EuiTourStepProps } from '@elastic/eui'; +import { EuiTourStep } from '@elastic/eui'; +import { noop } from 'lodash'; +import React, { useEffect, useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { of } from 'rxjs'; +import { useKibana } from '../../../../common/lib/kibana'; +import { useFindRulesQuery } from '../../../rule_management/api/hooks/use_find_rules_query'; +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', + 'activateRules' = 'activateRules', + 'completed' = 'completed', +} + +export const RulesManagementTour = () => { + const { guidedOnboardingApi } = useKibana().services.guidedOnboarding; + + const isRulesStepActive = useObservable( + guidedOnboardingApi?.isGuideStepActive$('security', 'rules') ?? of(false), + false + ); + + const { data: onboardingRules } = useFindRulesQuery( + { filterOptions: GUIDED_ONBOARDING_RULES_FILTER }, + { enabled: isRulesStepActive } + ); + + 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 (!onboardingRules.rules.some((rule) => rule.enabled)) { + // None of the onboarding rules is active - show the activate step + return GuidedOnboardingRulesStatus.activateRules; + } + + // Rules are installed and enabled - the tour is completed + return GuidedOnboardingRulesStatus.completed; + }, [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]); + + /** + * 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); + + return ( + <> + {isInstallRulesAnchorMounted && ( + } // Replace "Skip tour" with an empty element + /> + )} + {isSearchFirstRuleAnchorMounted && ( + } // Replace "Skip tour" with an empty element + /> + )} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/translations.ts new file mode 100644 index 0000000000000..6c8a2880801a6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/translations.ts @@ -0,0 +1,36 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const INSTALL_PREBUILT_RULES_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.title', + { + defaultMessage: 'Load the Elastic prebuilt rules', + } +); + +export const INSTALL_PREBUILT_RULES_CONTENT = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.guidedOnboarding.installPrebuiltRules.content', + { + defaultMessage: 'To get started you need to load the Elastic prebuilt rules.', + } +); + +export const SEARCH_FIRST_RULE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.guidedOnboarding.searchFirstRule.title', + { + defaultMessage: 'Search for Elastic Defend rules', + } +); + +export const SEARCH_FIRST_RULE_CONTENT = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.guidedOnboarding.searchFirstRule.content', + { + defaultMessage: 'Find the My First Alert rule and enable it.', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/use_is_element_mounted.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/use_is_element_mounted.ts new file mode 100644 index 0000000000000..b3be0184e1a3e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/guided_onboarding/use_is_element_mounted.ts @@ -0,0 +1,35 @@ +/* + * 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 { useEffect, useState } from 'react'; + +export const useIsElementMounted = (elementId: string) => { + const [isElementMounted, setIsElementMounted] = useState(false); + + useEffect(() => { + const observer = new MutationObserver(() => { + const isElementFound = !!document.getElementById(elementId); + + if (isElementFound && !isElementMounted) { + setIsElementMounted(true); + } + + if (!isElementFound && isElementMounted) { + setIsElementMounted(false); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + + return () => observer.disconnect(); + }, [isElementMounted, elementId]); + + return isElementMounted; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rules_table_filters.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rules_table_filters.tsx index 784d3dfc62427..143ae37a694d1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rules_table_filters.tsx @@ -22,6 +22,7 @@ import * as i18n from '../../../../../detections/pages/detection_engine/rules/tr import { useRulesTableContext } from '../rules_table/rules_table_context'; import { TagsFilterPopover } from './tags_filter_popover'; import { useTags } from '../../../../rule_management/logic/use_tags'; +import { SEARCH_FIRST_RULE_ANCHOR } from '../../guided_onboarding/rules_management_tour'; const FilterWrapper = styled(EuiFlexGroup)` margin-bottom: ${({ theme }) => theme.eui.euiSizeXS}; @@ -85,6 +86,7 @@ const RulesTableFiltersComponent = () => { { const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState(); @@ -85,6 +86,7 @@ const RulesPageComponent: React.FC = () => { + - {getLoadRulesOrTimelinesButtonTitle(prePackagedAssetsStatus, prePackagedTimelineStatus)} - +
+ + {getLoadRulesOrTimelinesButtonTitle(prePackagedAssetsStatus, prePackagedTimelineStatus)} + +
); } @@ -81,20 +88,26 @@ export const LoadPrePackagedRulesButton = ({ prePackagedTimelineStatus === 'someTimelineUninstall'; if (showUpdateButton) { + // Without the outer div EuiStepTour crashes with Uncaught DOMException: + // Failed to execute 'removeChild' on 'Node': The node to be removed is not + // a child of this node. return ( - - {getMissingRulesOrTimelinesButtonTitle( - prePackagedRulesStatus?.rules_not_installed ?? 0, - prePackagedRulesStatus?.timelines_not_installed ?? 0 - )} - +
+ + {getMissingRulesOrTimelinesButtonTitle( + prePackagedRulesStatus?.rules_not_installed ?? 0, + prePackagedRulesStatus?.timelines_not_installed ?? 0 + )} + +
); }