[Security Solution]: Add banner to promote prebuilt rule customization in ESS (#213750)

**Resolves: https://github.com/elastic/kibana/issues/205594**

## Summary

**Changes:**
- Adds a banner to promote prebuilt rule customization in ESS. Link
currently leads to a 404 page since the blog post is not yet published.
(Serverless banner to be added later, after April 1, when the blog post
is published). Banner is dismissible. It's state is stored in
localStorage.
<img width="1006" alt="Scherm­afbeelding 2025-03-11 om 12 25 45"
src="https://github.com/user-attachments/assets/41d83db9-4bc4-433e-a7e2-c5ef1049a20c"
/>


 - A couple unrelated small changes:
- Fixes spelling of singular/plural for "require" in the upgrade flyout
- Fixes horizontal line misalignment in upgrade flyout. It was caused by
an incorrect `css` function import: `import { css } from
'@emotion/css';` instead of `import { css } from '@emotion/react';`

<img width="653" alt="Scherm­afbeelding 2025-03-10 om 12 12 33"
src="https://github.com/user-attachments/assets/ab5f3b9e-73b2-4938-bda2-401eece5407d"
/>
<img width="676" alt="Scherm­afbeelding 2025-03-10 om 12 13 17"
src="https://github.com/user-attachments/assets/37bbff65-326f-415c-aab8-c9c661ef14ce"
/>

<img width="1966" alt="Scherm­afbeelding 2025-03-10 om 12 26 05"
src="https://github.com/user-attachments/assets/16ac2b9e-13ba-45d8-adcd-c9fb74f8db6e"
/>
<img width="1966" alt="Scherm­afbeelding 2025-03-10 om 12 24 54"
src="https://github.com/user-attachments/assets/c53e7642-26f5-490f-b1bc-6f3961aef71a"
/>
This commit is contained in:
Nikita Indik 2025-03-11 14:45:30 +01:00 committed by GitHub
parent aa850d4b9f
commit 122c7e12e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 139 additions and 2 deletions

View file

@ -449,6 +449,9 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
updatePrebuiltDetectionRules: isServerless
? `${SERVERLESS_DOCS}security-prebuilt-rules-management.html#update-prebuilt-rules`
: `${SECURITY_SOLUTION_DOCS}prebuilt-rules-management.html#update-prebuilt-rules`,
prebuiltRuleCustomizationPromoBlog: isServerless
? '' // URL for Serverless to be added later, once the blog post is published. Issue: https://github.com/elastic/kibana/issues/209000
: `${ELASTIC_WEBSITE_URL}blog/security-prebuilt-rules-editing`,
createEsqlRuleType: `${SECURITY_SOLUTION_DOCS}rules-ui-create.html#create-esql-rule`,
ruleUiAdvancedParams: `${SECURITY_SOLUTION_DOCS}rules-ui-create.html#rule-ui-advanced-params`,
entityAnalytics: {

View file

@ -313,6 +313,7 @@ export interface DocLinks {
readonly manageDetectionRules: string;
readonly createDetectionRules: string;
readonly updatePrebuiltDetectionRules: string;
readonly prebuiltRuleCustomizationPromoBlog: string;
readonly createEsqlRuleType: string;
readonly ruleUiAdvancedParams: string;
readonly entityAnalytics: {

View file

@ -8,7 +8,7 @@
import type { PropsWithChildren } from 'react';
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/css';
import { css } from '@emotion/react';
export function FieldUpgradeSideHeader({ children }: PropsWithChildren<{}>) {
const { euiTheme } = useEuiTheme();

View file

@ -14,7 +14,7 @@ import { useKibana } from '../../../../../../common/lib/kibana/kibana_react';
export const TOTAL_NUM_OF_FIELDS = (count: number) => (
<FormattedMessage
id="xpack.securitySolution.detectionEngine.rules.upgradeRules.diffTab.totalNumOfFieldsWithUpdates"
defaultMessage="{countValue} {count, plural, one {field} other {fields}} require review"
defaultMessage="{countValue} {count, plural, one {field} other {fields}} {count, plural, one {requires} other {require}} review"
values={{ countValue: <strong>{count}</strong>, count }}
/>
);

View file

@ -36,6 +36,7 @@ import { RulesTableContextProvider } from '../../components/rules_table/rules_ta
import { useInvalidateFetchCoverageOverviewQuery } from '../../../rule_management/api/hooks/use_fetch_coverage_overview_query';
import { HeaderPage } from '../../../../common/components/header_page';
import { RuleUpdateCallouts } from '../../components/rule_update_callouts/rule_update_callouts';
import { BlogPostPrebuiltRuleCustomizationCallout } from '../../../../detections/components/callouts/blog_post_prebuilt_rule_customization_callout';
const RulesPageComponent: React.FC = () => {
const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState();
@ -175,6 +176,7 @@ const RulesPageComponent: React.FC = () => {
kibanaServices={kibanaServices}
categories={[DEFAULT_APP_CATEGORIES.security.id]}
/>
<BlogPostPrebuiltRuleCustomizationCallout />
<AllRules data-test-subj="all-rules" />
</SecuritySolutionPageWrapper>
</RulesTableContextProvider>

View file

@ -0,0 +1,38 @@
/*
* 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 React from 'react';
import { EuiCallOut, useEuiTheme, type EuiCallOutProps } from '@elastic/eui';
import { css } from '@emotion/react';
interface BackgroundImageCalloutProps extends EuiCallOutProps {
backgroundImage: string;
description: JSX.Element;
}
export function BackgroundImageCallout({
description,
backgroundImage,
...euiCalloutProps
}: BackgroundImageCalloutProps) {
const { euiTheme } = useEuiTheme();
return (
<EuiCallOut
css={css`
padding-left: ${euiTheme.size.xl};
background-image: url(${backgroundImage});
background-repeat: no-repeat;
background-position-x: right;
background-position-y: bottom;
`}
{...euiCalloutProps}
>
{description}
</EuiCallOut>
);
}

View file

@ -0,0 +1,63 @@
/*
* 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 React, { useCallback, useMemo } from 'react';
import avcBannerBackground from '@kbn/avc-banner/src/avc_banner_background.svg';
import { EuiSpacer, EuiButton } from '@elastic/eui';
import { type CallOutMessage } from '../../../../common/components/callouts';
import { useCallOutStorage } from '../../../../common/components/callouts/use_callout_storage';
import * as i18n from './translations';
import { useKibana } from '../../../../common/lib/kibana';
import { BackgroundImageCallout } from '../background_image_callout';
export function BlogPostPrebuiltRuleCustomizationCallout() {
// URL is currently only available in ESS. So we are only showing this callout in ESS for now.
const blogPostUrl =
useKibana().services.docLinks.links.securitySolution.prebuiltRuleCustomizationPromoBlog;
const calloutMessage: CallOutMessage = useMemo(
() => ({
type: 'success',
id: 'blog-post-elastic-security-prebuilt-rule-customization',
title: i18n.CALLOUT_TITLE,
description: (
<>
{i18n.CALLOUT_DESCRIPTION}
<EuiSpacer size="s" />
<EuiButton size="s" color="success" href={blogPostUrl} target="_blank">
{i18n.CALLOUT_ACTION_BUTTON_LABEL}
</EuiButton>
</>
),
}),
[blogPostUrl]
);
const { isVisible, dismiss } = useCallOutStorage([calloutMessage], 'detections');
const handleDismiss = useCallback(() => {
dismiss(calloutMessage);
}, [dismiss, calloutMessage]);
if (blogPostUrl && isVisible(calloutMessage)) {
return (
<>
<BackgroundImageCallout
backgroundImage={avcBannerBackground}
title={calloutMessage.title}
description={calloutMessage.description}
color={calloutMessage.type}
iconType="cheer"
onDismiss={handleDismiss}
/>
<EuiSpacer size="l" />
</>
);
}
return null;
}

View file

@ -0,0 +1,30 @@
/*
* 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 CALLOUT_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.blogPostPrebuiltRuleCustomizationCallout.calloutTitle',
{
defaultMessage: 'Get more value out of Elastic prebuilt rules!',
}
);
export const CALLOUT_DESCRIPTION = i18n.translate(
'xpack.securitySolution.detectionEngine.blogPostPrebuiltRuleCustomizationCallout.calloutDescription',
{
defaultMessage:
'Learn how to customize prebuilt rules and update them with the latest improvements.',
}
);
export const CALLOUT_ACTION_BUTTON_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.blogPostPrebuiltRuleCustomizationCallout.calloutButtonLabel',
{
defaultMessage: 'Read the blog',
}
);