From 6b8e8a5b468746f14beb6e17264e5b9c5bebc67b Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Thu, 15 Oct 2020 18:46:11 +0100 Subject: [PATCH] [Security Solution] Update button text according to status (#80389) * update button text according to status * remove unused translations * fix functional test * fixup * fix unit test * update unit tests * update unit test --- .../load_empty_prompt.test.tsx | 96 ++++++++++- .../pre_packaged_rules/load_empty_prompt.tsx | 42 +++-- .../rules/pre_packaged_rules/translations.ts | 7 - .../detection_engine/rules/translations.ts | 54 ++++++ .../rules/use_pre_packaged_rules.test.tsx | 12 ++ .../rules/use_pre_packaged_rules.tsx | 128 +++++++++++++- .../detection_engine/rules/index.test.tsx | 163 +++++++++++++++++- .../pages/detection_engine/rules/index.tsx | 68 +++----- .../detection_engine/rules/translations.ts | 40 ----- .../translations/translations/ja-JP.json | 3 +- .../translations/translations/zh-CN.json | 3 +- 11 files changed, 492 insertions(+), 124 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx index a41da908085bc..75ab1524c5c06 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx @@ -5,10 +5,12 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { waitFor } from '@testing-library/react'; +import { shallow, mount, ReactWrapper } from 'enzyme'; import '../../../../common/mock/match_media'; import { PrePackagedRulesPrompt } from './load_empty_prompt'; +import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -23,16 +25,94 @@ jest.mock('react-router-dom', () => { jest.mock('../../../../common/components/link_to'); +jest.mock('../../../containers/detection_engine/rules/api', () => ({ + getPrePackagedRulesStatus: jest.fn().mockResolvedValue({ + rules_not_installed: 0, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 0, + timelines_installed: 0, + timelines_not_updated: 0, + }), + createPrepackagedRules: jest.fn(), +})); + +const props = { + createPrePackagedRules: jest.fn(), + loading: false, + userHasNoPermissions: false, + 'data-test-subj': 'load-prebuilt-rules', +}; + describe('PrePackagedRulesPrompt', () => { it('renders correctly', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.find('EmptyPrompt')).toHaveLength(1); }); }); + +describe('LoadPrebuiltRulesAndTemplatesButton', () => { + it('renders correct button with correct text - Load Elastic prebuilt rules and timeline templates', async () => { + (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ + rules_not_installed: 3, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 3, + timelines_installed: 0, + timelines_not_updated: 0, + }); + + const wrapper: ReactWrapper = mount(); + await waitFor(() => { + wrapper.update(); + + expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').last().text()).toEqual( + 'Load Elastic prebuilt rules and timeline templates' + ); + }); + }); + + it('renders correct button with correct text - Load Elastic prebuilt rules', async () => { + (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ + rules_not_installed: 3, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 0, + timelines_installed: 0, + timelines_not_updated: 0, + }); + + const wrapper: ReactWrapper = mount(); + await waitFor(() => { + wrapper.update(); + + expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').last().text()).toEqual( + 'Load Elastic prebuilt rules' + ); + }); + }); + + it('renders correct button with correct text - Load Elastic prebuilt timeline templates', async () => { + (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ + rules_not_installed: 0, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 3, + timelines_installed: 0, + timelines_not_updated: 0, + }); + + const wrapper: ReactWrapper = mount(); + await waitFor(() => { + wrapper.update(); + + expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').last().text()).toEqual( + 'Load Elastic prebuilt timeline templates' + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx index 99968cd4d9fe8..64b3cfa3aa991 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; -import React, { memo, useCallback } from 'react'; +import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { memo, useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { useHistory } from 'react-router-dom'; @@ -14,6 +14,8 @@ import * as i18n from './translations'; import { LinkButton } from '../../../../common/components/links'; import { SecurityPageName } from '../../../../app/types'; import { useFormatUrl } from '../../../../common/components/link_to'; +import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; +import { useUserData } from '../../user_info'; const EmptyPrompt = styled(EuiEmptyPrompt)` align-self: center; /* Corrects horizontal centering in IE11 */ @@ -46,24 +48,36 @@ const PrePackagedRulesPromptComponent: React.FC = ( [history] ); + const [ + { isSignalIndexExists, isAuthenticated, hasEncryptionKey, canUserCRUD, hasIndexWrite }, + ] = useUserData(); + + const { getLoadPrebuiltRulesAndTemplatesButton } = usePrePackagedRules({ + canUserCRUD, + hasIndexWrite, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + }); + + const loadPrebuiltRulesAndTemplatesButton = useMemo( + () => + getLoadPrebuiltRulesAndTemplatesButton({ + isDisabled: userHasNoPermissions, + onClick: handlePreBuiltCreation, + fill: true, + 'data-test-subj': 'load-prebuilt-rules', + }), + [getLoadPrebuiltRulesAndTemplatesButton, handlePreBuiltCreation, userHasNoPermissions] + ); + return ( {i18n.PRE_BUILT_TITLE}} body={

{i18n.PRE_BUILT_MSG}

} actions={ - - - {i18n.PRE_BUILT_ACTION} - - + {loadPrebuiltRulesAndTemplatesButton} + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton', + { + values: { missingRules }, + defaultMessage: + 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', + } + ); + +export const RELOAD_MISSING_PREPACKAGED_TIMELINES = (missingTimelines: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedTimelinesButton', + { + values: { missingTimelines }, + defaultMessage: + 'Install {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ', + } + ); + +export const RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES = ( + missingRules: number, + missingTimelines: number +) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesAndTimelinesButton', + { + values: { missingRules, missingTimelines }, + defaultMessage: + 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} and {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ', + } + ); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx index 92d46a785b034..7f74e92584494 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx @@ -32,6 +32,10 @@ describe('usePrePackagedRules', () => { await waitForNextUpdate(); expect(result.current).toEqual({ + getLoadPrebuiltRulesAndTemplatesButton: + result.current.getLoadPrebuiltRulesAndTemplatesButton, + getReloadPrebuiltRulesAndTemplatesButton: + result.current.getReloadPrebuiltRulesAndTemplatesButton, createPrePackagedRules: null, loading: true, loadingCreatePrePackagedRules: false, @@ -63,6 +67,10 @@ describe('usePrePackagedRules', () => { await waitForNextUpdate(); expect(result.current).toEqual({ + getLoadPrebuiltRulesAndTemplatesButton: + result.current.getLoadPrebuiltRulesAndTemplatesButton, + getReloadPrebuiltRulesAndTemplatesButton: + result.current.getReloadPrebuiltRulesAndTemplatesButton, createPrePackagedRules: result.current.createPrePackagedRules, loading: false, loadingCreatePrePackagedRules: false, @@ -100,6 +108,10 @@ describe('usePrePackagedRules', () => { expect(resp).toEqual(true); expect(spyOnCreatePrepackagedRules).toHaveBeenCalled(); expect(result.current).toEqual({ + getLoadPrebuiltRulesAndTemplatesButton: + result.current.getLoadPrebuiltRulesAndTemplatesButton, + getReloadPrebuiltRulesAndTemplatesButton: + result.current.getReloadPrebuiltRulesAndTemplatesButton, createPrePackagedRules: result.current.createPrePackagedRules, loading: false, loadingCreatePrePackagedRules: false, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx index d82d97883a3d0..4d19f44bcfc84 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; +import React, { useCallback, useMemo, useState, useEffect } from 'react'; +import { EuiButton } from '@elastic/eui'; import { errorToToaster, @@ -14,6 +15,11 @@ import { import { getPrePackagedRulesStatus, createPrepackagedRules } from './api'; import * as i18n from './translations'; +import { + getPrePackagedRuleStatus, + getPrePackagedTimelineStatus, +} from '../../../pages/detection_engine/rules/helpers'; + type Func = () => void; export type CreatePreBuiltRules = () => Promise; @@ -23,6 +29,23 @@ interface ReturnPrePackagedTimelines { timelinesNotUpdated: number | null; } +type GetLoadPrebuiltRulesAndTemplatesButton = (args: { + isDisabled: boolean; + onClick: () => void; + fill?: boolean; + 'data-test-subj'?: string; +}) => React.ReactNode | null; + +type GetReloadPrebuiltRulesAndTemplatesButton = ({ + isDisabled, + onClick, + fill, +}: { + isDisabled: boolean; + onClick: () => void; + fill?: boolean; +}) => React.ReactNode | null; + interface ReturnPrePackagedRules { createPrePackagedRules: null | CreatePreBuiltRules; loading: boolean; @@ -32,6 +55,8 @@ interface ReturnPrePackagedRules { rulesInstalled: number | null; rulesNotInstalled: number | null; rulesNotUpdated: number | null; + getLoadPrebuiltRulesAndTemplatesButton: GetLoadPrebuiltRulesAndTemplatesButton; + getReloadPrebuiltRulesAndTemplatesButton: GetReloadPrebuiltRulesAndTemplatesButton; } export type ReturnPrePackagedRulesAndTimelines = ReturnPrePackagedRules & @@ -89,7 +114,6 @@ export const usePrePackagedRules = ({ const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false); const [loading, setLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); - useEffect(() => { let isSubscribed = true; const abortCtrl = new AbortController(); @@ -100,7 +124,6 @@ export const usePrePackagedRules = ({ const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({ signal: abortCtrl.signal, }); - if (isSubscribed) { setPrepackagedDataStatus({ createPrePackagedRules: createElasticRules, @@ -225,9 +248,108 @@ export const usePrePackagedRules = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [canUserCRUD, hasIndexWrite, isAuthenticated, hasEncryptionKey, isSignalIndexExists]); + const prePackagedRuleStatus = useMemo( + () => + getPrePackagedRuleStatus( + prepackagedDataStatus.rulesInstalled, + prepackagedDataStatus.rulesNotInstalled, + prepackagedDataStatus.rulesNotUpdated + ), + [ + prepackagedDataStatus.rulesInstalled, + prepackagedDataStatus.rulesNotInstalled, + prepackagedDataStatus.rulesNotUpdated, + ] + ); + + const prePackagedTimelineStatus = useMemo( + () => + getPrePackagedTimelineStatus( + prepackagedDataStatus.timelinesInstalled, + prepackagedDataStatus.timelinesNotInstalled, + prepackagedDataStatus.timelinesNotUpdated + ), + [ + prepackagedDataStatus.timelinesInstalled, + prepackagedDataStatus.timelinesNotInstalled, + prepackagedDataStatus.timelinesNotUpdated, + ] + ); + const getLoadPrebuiltRulesAndTemplatesButton = useCallback( + ({ isDisabled, onClick, fill, 'data-test-subj': dataTestSubj = 'loadPrebuiltRulesBtn' }) => { + return prePackagedRuleStatus === 'ruleNotInstalled' || + prePackagedTimelineStatus === 'timelinesNotInstalled' ? ( + + {prePackagedRuleStatus === 'ruleNotInstalled' && + prePackagedTimelineStatus === 'timelinesNotInstalled' && + i18n.LOAD_PREPACKAGED_RULES_AND_TEMPLATES} + + {prePackagedRuleStatus === 'ruleNotInstalled' && + prePackagedTimelineStatus !== 'timelinesNotInstalled' && + i18n.LOAD_PREPACKAGED_RULES} + + {prePackagedRuleStatus !== 'ruleNotInstalled' && + prePackagedTimelineStatus === 'timelinesNotInstalled' && + i18n.LOAD_PREPACKAGED_TIMELINE_TEMPLATES} + + ) : null; + }, + [loadingCreatePrePackagedRules, prePackagedRuleStatus, prePackagedTimelineStatus] + ); + + const getMissingRulesOrTimelinesButtonTitle = useCallback( + (missingRules: number, missingTimelines: number) => { + if (missingRules > 0 && missingTimelines === 0) + return i18n.RELOAD_MISSING_PREPACKAGED_RULES(missingRules); + else if (missingRules === 0 && missingTimelines > 0) + return i18n.RELOAD_MISSING_PREPACKAGED_TIMELINES(missingTimelines); + else if (missingRules > 0 && missingTimelines > 0) + return i18n.RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES(missingRules, missingTimelines); + }, + [] + ); + + const getReloadPrebuiltRulesAndTemplatesButton = useCallback( + ({ isDisabled, onClick, fill = false }) => { + return prePackagedRuleStatus === 'someRuleUninstall' || + prePackagedTimelineStatus === 'someTimelineUninstall' ? ( + + {getMissingRulesOrTimelinesButtonTitle( + prepackagedDataStatus.rulesNotInstalled ?? 0, + prepackagedDataStatus.timelinesNotInstalled ?? 0 + )} + + ) : null; + }, + [ + getMissingRulesOrTimelinesButtonTitle, + loadingCreatePrePackagedRules, + prePackagedRuleStatus, + prePackagedTimelineStatus, + prepackagedDataStatus.rulesNotInstalled, + prepackagedDataStatus.timelinesNotInstalled, + ] + ); + return { loading, loadingCreatePrePackagedRules, ...prepackagedDataStatus, + getLoadPrebuiltRulesAndTemplatesButton, + getReloadPrebuiltRulesAndTemplatesButton, }; }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx index 886a24dd7cbe8..58e61c5b47486 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx @@ -5,13 +5,14 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, mount, ReactWrapper } from 'enzyme'; import '../../../../common/mock/match_media'; import { RulesPage } from './index'; import { useUserData } from '../../../components/user_info'; -import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; - +import { waitFor } from '@testing-library/react'; +import { TestProviders } from '../../../../common/mock'; +import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -26,16 +27,164 @@ jest.mock('react-router-dom', () => { jest.mock('../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../common/components/link_to'); jest.mock('../../../components/user_info'); -jest.mock('../../../containers/detection_engine/rules'); +jest.mock('../../../../common/components/toasters', () => { + const actual = jest.requireActual('../../../../common/components/toasters'); + return { + ...actual, + errorToToaster: jest.fn(), + useStateToaster: jest.fn().mockReturnValue([jest.fn(), jest.fn()]), + displaySuccessToast: jest.fn(), + }; +}); + +jest.mock('../../../containers/detection_engine/rules/api', () => ({ + getPrePackagedRulesStatus: jest.fn().mockResolvedValue({ + rules_not_installed: 0, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 0, + timelines_installed: 0, + timelines_not_updated: 0, + }), + createPrepackagedRules: jest.fn(), +})); + +jest.mock('../../../../common/lib/kibana', () => { + return { + useToast: jest.fn(), + useHttp: jest.fn(), + }; +}); + +jest.mock('../../../components/value_lists_management_modal', () => { + return { + ValueListsModal: jest.fn().mockReturnValue(
), + }; +}); + +jest.mock('./all', () => { + return { + AllRules: jest.fn().mockReturnValue(
), + }; +}); + +jest.mock('../../../../common/utils/route/spy_routes', () => { + return { + SpyRoute: jest.fn().mockReturnValue(
), + }; +}); + +jest.mock('../../../components/rules/pre_packaged_rules/update_callout', () => { + return { + UpdatePrePackagedRulesCallOut: jest.fn().mockReturnValue(
), + }; +}); describe('RulesPage', () => { beforeAll(() => { (useUserData as jest.Mock).mockReturnValue([{}]); - (usePrePackagedRules as jest.Mock).mockReturnValue({}); }); - it('renders correctly', () => { + + it('renders AllRules', () => { const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="all-rules"]').exists()).toEqual(true); + }); + + it('renders correct button with correct text - Load Elastic prebuilt rules and timeline templates', async () => { + (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ + rules_not_installed: 3, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 3, + timelines_installed: 0, + timelines_not_updated: 0, + }); + + const wrapper: ReactWrapper = mount( + + + + ); + await waitFor(() => { + wrapper.update(); + + expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').last().text()).toEqual( + 'Load Elastic prebuilt rules and timeline templates' + ); + }); + }); + + it('renders correct button with correct text - Load Elastic prebuilt rules', async () => { + (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ + rules_not_installed: 3, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 0, + timelines_installed: 0, + timelines_not_updated: 0, + }); + + const wrapper: ReactWrapper = mount( + + + + ); + + await waitFor(() => { + wrapper.update(); + + expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').last().text()).toEqual( + 'Load Elastic prebuilt rules' + ); + }); + }); + + it('renders correct button with correct text - Load Elastic prebuilt timeline templates', async () => { + (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ + rules_not_installed: 0, + rules_installed: 0, + rules_not_updated: 0, + timelines_not_installed: 3, + timelines_installed: 0, + timelines_not_updated: 0, + }); + + const wrapper: ReactWrapper = mount( + + + + ); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').last().text()).toEqual( + 'Load Elastic prebuilt timeline templates' + ); + }); + }); + + it('renders a callout - Update Elastic prebuilt rules', async () => { + (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ + rules_not_installed: 2, + rules_installed: 1, + rules_not_updated: 1, + timelines_not_installed: 0, + timelines_installed: 0, + timelines_not_updated: 0, + }); + + const wrapper: ReactWrapper = mount( + + + + ); - expect(wrapper.find('AllRules')).toHaveLength(1); + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('[data-test-subj="update-callout-button"]').exists()).toEqual(true); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx index 53c82569f94ae..8c7cb6a0d9284 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; -import React, { useCallback, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { usePrePackagedRules, importRules } from '../../../containers/detection_engine/rules'; @@ -70,6 +70,8 @@ const RulesPageComponent: React.FC = () => { timelinesInstalled, timelinesNotInstalled, timelinesNotUpdated, + getLoadPrebuiltRulesAndTemplatesButton, + getReloadPrebuiltRulesAndTemplatesButton, } = usePrePackagedRules({ canUserCRUD, hasIndexWrite, @@ -113,18 +115,6 @@ const RulesPageComponent: React.FC = () => { refreshRulesData.current = refreshRule; }, []); - const getMissingRulesOrTimelinesButtonTitle = useCallback( - (missingRules: number, missingTimelines: number) => { - if (missingRules > 0 && missingTimelines === 0) - return i18n.RELOAD_MISSING_PREPACKAGED_RULES(missingRules); - else if (missingRules === 0 && missingTimelines > 0) - return i18n.RELOAD_MISSING_PREPACKAGED_TIMELINES(missingTimelines); - else if (missingRules > 0 && missingTimelines > 0) - return i18n.RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES(missingRules, missingTimelines); - }, - [] - ); - const goToNewRule = useCallback( (ev) => { ev.preventDefault(); @@ -133,6 +123,24 @@ const RulesPageComponent: React.FC = () => { [history] ); + const loadPrebuiltRulesAndTemplatesButton = useMemo( + () => + getLoadPrebuiltRulesAndTemplatesButton({ + isDisabled: userHasNoPermissions(canUserCRUD) || loading, + onClick: handleCreatePrePackagedRules, + }), + [canUserCRUD, getLoadPrebuiltRulesAndTemplatesButton, handleCreatePrePackagedRules, loading] + ); + + const reloadPrebuiltRulesAndTemplatesButton = useMemo( + () => + getReloadPrebuiltRulesAndTemplatesButton({ + isDisabled: userHasNoPermissions(canUserCRUD) || loading, + onClick: handleCreatePrePackagedRules, + }), + [canUserCRUD, getReloadPrebuiltRulesAndTemplatesButton, handleCreatePrePackagedRules, loading] + ); + if ( redirectToDetections( isSignalIndexExists, @@ -177,35 +185,11 @@ const RulesPageComponent: React.FC = () => { title={i18n.PAGE_TITLE} > - {(prePackagedRuleStatus === 'ruleNotInstalled' || - prePackagedTimelineStatus === 'timelinesNotInstalled') && ( - - - {i18n.LOAD_PREPACKAGED_RULES} - - + {loadPrebuiltRulesAndTemplatesButton && ( + {loadPrebuiltRulesAndTemplatesButton} )} - {(prePackagedRuleStatus === 'someRuleUninstall' || - prePackagedTimelineStatus === 'someTimelineUninstall') && ( - - - {getMissingRulesOrTimelinesButtonTitle( - rulesNotInstalled ?? 0, - timelinesNotInstalled ?? 0 - )} - - + {reloadPrebuiltRulesAndTemplatesButton && ( + {reloadPrebuiltRulesAndTemplatesButton} )} @@ -247,6 +231,7 @@ const RulesPageComponent: React.FC = () => { {(prePackagedRuleStatus === 'ruleNeedUpdate' || prePackagedTimelineStatus === 'timelineNeedUpdate') && ( { )} - i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton', - { - values: { missingRules }, - defaultMessage: - 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', - } - ); - -export const RELOAD_MISSING_PREPACKAGED_TIMELINES = (missingTimelines: number) => - i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedTimelinesButton', - { - values: { missingTimelines }, - defaultMessage: - 'Install {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ', - } - ); - -export const RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES = ( - missingRules: number, - missingTimelines: number -) => - i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesAndTimelinesButton', - { - values: { missingRules, missingTimelines }, - defaultMessage: - 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} and {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ', - } - ); - export const IMPORT_RULE_BTN_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.components.importRuleModal.importRuleTitle', { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 763b26de00940..d0c24b0239b62 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15574,13 +15574,12 @@ "xpack.securitySolution.detectionEngine.rules.deleteDescription": "削除", "xpack.securitySolution.detectionEngine.rules.editPageTitle": "編集", "xpack.securitySolution.detectionEngine.rules.importRuleTitle": "ルールのインポート...", - "xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesButton": "Elastic事前構築済みルールおよびタイムラインテンプレートを読み込む", + "xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesAndTemplatesButton": "Elastic事前構築済みルールおよびタイムラインテンプレートを読み込む", "xpack.securitySolution.detectionEngine.rules.optionalFieldDescription": "オプション", "xpack.securitySolution.detectionEngine.rules.pageTitle": "検出ルール", "xpack.securitySolution.detectionEngine.rules.prePackagedRules.createOwnRuletButton": "独自のルールの作成", "xpack.securitySolution.detectionEngine.rules.prePackagedRules.emptyPromptMessage": "Elasticセキュリティには、バックグラウンドで実行され、条件が合うとアラートを作成する事前構築済み検出ルールがあります。デフォルトでは、Elastic Endpoint Securityルールを除くすべての事前構築済みルールが無効になっています。有効にする追加のルールを選択することができます。", "xpack.securitySolution.detectionEngine.rules.prePackagedRules.emptyPromptTitle": "Elastic事前構築済み検出ルールを読み込む", - "xpack.securitySolution.detectionEngine.rules.prePackagedRules.loadPreBuiltButton": "事前構築済み検出ルールおよびタイムラインテンプレートを読み込む", "xpack.securitySolution.detectionEngine.rules.releaseNotesHelp": "リリースノート", "xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesAndTimelinesButton": "{missingRules} Elastic事前構築済み{missingRules, plural, =1 {ルール} other {ルール}}と{missingTimelines} Elastic事前構築済み{missingTimelines, plural, =1 {タイムライン} other {タイムライン}}をインストール ", "xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton": "{missingRules} Elasticの事前構築済みの{missingRules, plural, =1 {個のルール} other {個のルール}}をインストール ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4245b446c5b93..db73cd8043e7e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15583,13 +15583,12 @@ "xpack.securitySolution.detectionEngine.rules.deleteDescription": "删除", "xpack.securitySolution.detectionEngine.rules.editPageTitle": "编辑", "xpack.securitySolution.detectionEngine.rules.importRuleTitle": "导入规则……", - "xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesButton": "加载 Elastic 预构建规则和时间线模板", + "xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesAndTemplatesButton": "加载 Elastic 预构建规则和时间线模板", "xpack.securitySolution.detectionEngine.rules.optionalFieldDescription": "可选", "xpack.securitySolution.detectionEngine.rules.pageTitle": "检测规则", "xpack.securitySolution.detectionEngine.rules.prePackagedRules.createOwnRuletButton": "创建自己的规则", "xpack.securitySolution.detectionEngine.rules.prePackagedRules.emptyPromptMessage": "Elastic Security 附带预置检测规则,这些规则在后台运行,并在条件满足时创建告警。默认情况下,除 Elastic Endpoint Security 规则外,所有预置规则都处于禁用状态。您可以选择其他要激活的规则。", "xpack.securitySolution.detectionEngine.rules.prePackagedRules.emptyPromptTitle": "加载 Elastic 预构建检测规则", - "xpack.securitySolution.detectionEngine.rules.prePackagedRules.loadPreBuiltButton": "加载预置检测规则和时间线模板", "xpack.securitySolution.detectionEngine.rules.releaseNotesHelp": "发行说明", "xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesAndTimelinesButton": "安装 {missingRules} 个 Elastic 预构建{missingRules, plural, =1 {规则} other {规则}}以及 {missingTimelines} 个 Elastic 预构建{missingTimelines, plural, =1 {时间线} other {时间线}} ", "xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton": "安装 {missingRules} 个 Elastic 预构建{missingRules, plural, =1 {规则} other {规则}} ",