diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding.tsx
index a240e937d4de6..17f4840e68dc4 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding.tsx
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding.tsx
@@ -40,7 +40,7 @@ export const OnboardingPage = React.memo(() => {
restrictWidth={PAGE_CONTENT_WIDTH}
paddingSize="xl"
bottomBorder="extended"
- style={{ backgroundColor: euiTheme.colors.lightestShade }}
+ style={{ backgroundColor: euiTheme.colors.body }}
>
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/body_config.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/body_config.ts
index d57b38a2e02f1..8191f392e97e6 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/body_config.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/body_config.ts
@@ -9,6 +9,9 @@ import { i18n } from '@kbn/i18n';
import type { OnboardingGroupConfig } from '../../types';
import { integrationsCardConfig } from './cards/integrations';
import { dashboardsCardConfig } from './cards/dashboards';
+import { rulesCardConfig } from './cards/rules';
+import { alertsCardConfig } from './cards/alerts';
+import { assistantCardConfig } from './cards/assistant';
export const bodyConfig: OnboardingGroupConfig[] = [
{
@@ -21,6 +24,12 @@ export const bodyConfig: OnboardingGroupConfig[] = [
title: i18n.translate('xpack.securitySolution.onboarding.alertsGroup.title', {
defaultMessage: 'Configure rules and alerts',
}),
- cards: [],
+ cards: [rulesCardConfig, alertsCardConfig],
+ },
+ {
+ title: i18n.translate('xpack.securitySolution.onboarding.discoverGroup.title', {
+ defaultMessage: 'Discover Elastic AI',
+ }),
+ cards: [assistantCardConfig],
},
];
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/alerts_card.test.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/alerts_card.test.tsx
new file mode 100644
index 0000000000000..3e83bcb851f82
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/alerts_card.test.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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 { render } from '@testing-library/react';
+import { AlertsCard } from './alerts_card';
+import { TestProviders } from '../../../../../common/mock';
+
+const props = {
+ setComplete: jest.fn(),
+ checkComplete: jest.fn(),
+ isCardComplete: jest.fn(),
+ setExpandedCardId: jest.fn(),
+};
+
+describe('AlertsCard', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('description should be in the document', () => {
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId('alertsCardDescription')).toBeInTheDocument();
+ });
+
+ it('card callout should be rendered if integrations cards is not complete', () => {
+ props.isCardComplete.mockReturnValueOnce(false);
+
+ const { getByText } = render(
+
+
+
+ );
+
+ expect(getByText('To view alerts add integrations first.')).toBeInTheDocument();
+ });
+
+ it('card button should be disabled if integrations cards is not complete', () => {
+ props.isCardComplete.mockReturnValueOnce(false);
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId('alertsCardButton').querySelector('button')).toBeDisabled();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/alerts_card.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/alerts_card.tsx
new file mode 100644
index 0000000000000..c0369ed23d61c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/alerts_card.tsx
@@ -0,0 +1,82 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
+import { SecurityPageName } from '@kbn/security-solution-navigation';
+import { SecuritySolutionLinkButton } from '../../../../../common/components/links';
+import { OnboardingCardId } from '../../../../constants';
+import type { OnboardingCardComponent } from '../../../../types';
+import { OnboardingCardContentImagePanel } from '../common/card_content_image_panel';
+import { CardCallOut } from '../common/card_callout';
+import alertsImageSrc from './images/alerts.png';
+import * as i18n from './translations';
+
+export const AlertsCard: OnboardingCardComponent = ({
+ isCardComplete,
+ setExpandedCardId,
+ setComplete,
+}) => {
+ const isIntegrationsCardComplete = useMemo(
+ () => isCardComplete(OnboardingCardId.integrations),
+ [isCardComplete]
+ );
+
+ const expandIntegrationsCard = useCallback(() => {
+ setExpandedCardId(OnboardingCardId.integrations, { scroll: true });
+ }, [setExpandedCardId]);
+
+ return (
+
+
+
+
+ {i18n.ALERTS_CARD_DESCRIPTION}
+
+ {!isIntegrationsCardComplete && (
+ <>
+
+
+
+ {i18n.ALERTS_CARD_CALLOUT_INTEGRATIONS_BUTTON}
+
+
+
+
+
+ }
+ />
+ >
+ )}
+
+
+ setComplete(true)}
+ deepLinkId={SecurityPageName.alerts}
+ fill
+ isDisabled={!isIntegrationsCardComplete}
+ >
+ {i18n.ALERTS_CARD_VIEW_ALERTS_BUTTON}
+
+
+
+
+ );
+};
+
+// eslint-disable-next-line import/no-default-export
+export default AlertsCard;
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/images/alerts.png b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/images/alerts.png
new file mode 100644
index 0000000000000..6eaf13bfc7b53
Binary files /dev/null and b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/images/alerts.png differ
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/images/alerts_icon.png b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/images/alerts_icon.png
new file mode 100644
index 0000000000000..e1013a6eae7fc
Binary files /dev/null and b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/images/alerts_icon.png differ
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/index.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/index.ts
new file mode 100644
index 0000000000000..5ed5f4d34ce39
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/index.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 type { OnboardingCardConfig } from '../../../../types';
+import { OnboardingCardId } from '../../../../constants';
+import { ALERTS_CARD_TITLE } from './translations';
+import alertsIcon from './images/alerts_icon.png';
+
+export const alertsCardConfig: OnboardingCardConfig = {
+ id: OnboardingCardId.alerts,
+ title: ALERTS_CARD_TITLE,
+ icon: alertsIcon,
+ Component: React.lazy(() => import('./alerts_card')),
+};
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/translations.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/translations.ts
new file mode 100644
index 0000000000000..3138f01d20b66
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/translations.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 ALERTS_CARD_TITLE = i18n.translate(
+ 'xpack.securitySolution.onboarding.alertsCard.title',
+ {
+ defaultMessage: 'View alerts',
+ }
+);
+
+export const ALERTS_CARD_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.onboarding.alertsCard.description',
+ {
+ defaultMessage:
+ 'Visualize, sort, filter, and investigate alerts from across your infrastructure. Examine individual alerts of interest, and discover general patterns in alert volume and severity.',
+ }
+);
+
+export const ALERTS_CARD_CALLOUT_INTEGRATIONS_TEXT = i18n.translate(
+ 'xpack.securitySolution.onboarding.alertsCard.calloutIntegrationsText',
+ {
+ defaultMessage: 'To view alerts add integrations first.',
+ }
+);
+
+export const ALERTS_CARD_CALLOUT_INTEGRATIONS_BUTTON = i18n.translate(
+ 'xpack.securitySolution.onboarding.alertsCard.calloutIntegrationsButton',
+ {
+ defaultMessage: 'Add integrations step',
+ }
+);
+
+export const ALERTS_CARD_VIEW_ALERTS_BUTTON = i18n.translate(
+ 'xpack.securitySolution.onboarding.alertsCard.viewAlertsButton',
+ {
+ defaultMessage: 'View alerts',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx
new file mode 100644
index 0000000000000..d2592ae01dea0
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_card.tsx
@@ -0,0 +1,77 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiText } from '@elastic/eui';
+import { css } from '@emotion/css';
+import type { AIConnector } from '@kbn/elastic-assistant/impl/connectorland/connector_selector';
+import { OnboardingCardId } from '../../../../constants';
+import type { OnboardingCardComponent } from '../../../../types';
+import * as i18n from './translations';
+import { OnboardingCardContentPanel } from '../common/card_content_panel';
+import { ConnectorCards } from './components/connectors/connector_cards';
+import { CardCallOut } from '../common/card_callout';
+
+export const AssistantCard: OnboardingCardComponent = ({
+ isCardComplete,
+ setExpandedCardId,
+ checkCompleteMetadata,
+ checkComplete,
+}) => {
+ const isIntegrationsCardComplete = useMemo(
+ () => isCardComplete(OnboardingCardId.integrations),
+ [isCardComplete]
+ );
+
+ const expandIntegrationsCard = useCallback(() => {
+ setExpandedCardId(OnboardingCardId.integrations, { scroll: true });
+ }, [setExpandedCardId]);
+
+ const aiConnectors = checkCompleteMetadata?.connectors as AIConnector[];
+
+ return (
+
+
+
+
+ {i18n.ASSISTANT_CARD_DESCRIPTION}
+
+
+
+ {isIntegrationsCardComplete ? (
+
+ ) : (
+
+
+
+ {i18n.ASSISTANT_CARD_CALLOUT_INTEGRATIONS_BUTTON}
+
+
+
+
+
+ }
+ />
+
+ )}
+
+
+
+ );
+};
+
+// eslint-disable-next-line import/no-default-export
+export default AssistantCard;
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_check_complete.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_check_complete.ts
new file mode 100644
index 0000000000000..eae9f13bec8a0
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/assistant_check_complete.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { loadAllActions as loadConnectors } from '@kbn/triggers-actions-ui-plugin/public/common/constants';
+import type { AIConnector } from '@kbn/elastic-assistant/impl/connectorland/connector_selector';
+import { i18n } from '@kbn/i18n';
+import type { OnboardingCardCheckComplete } from '../../../../types';
+import { AllowedActionTypeIds } from './constants';
+
+export const checkAssistantCardComplete: OnboardingCardCheckComplete = async ({ http }) => {
+ const allConnectors = await loadConnectors({ http });
+
+ const aiConnectors = allConnectors.reduce((acc: AIConnector[], connector) => {
+ if (!connector.isMissingSecrets && AllowedActionTypeIds.includes(connector.actionTypeId)) {
+ acc.push(connector);
+ }
+ return acc;
+ }, []);
+
+ const completeBadgeText = i18n.translate(
+ 'xpack.securitySolution.onboarding.assistantCard.badge.completeText',
+ {
+ defaultMessage: '{count} AI {count, plural, one {connector} other {connectors}} added',
+ values: { count: aiConnectors.length },
+ }
+ );
+
+ return {
+ isComplete: aiConnectors.length > 0,
+ completeBadgeText,
+ metadata: {
+ connectors: aiConnectors,
+ },
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/connector_cards.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/connector_cards.tsx
new file mode 100644
index 0000000000000..42b3a5b14f039
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/connector_cards.tsx
@@ -0,0 +1,83 @@
+/*
+ * 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 { type AIConnector } from '@kbn/elastic-assistant/impl/connectorland/connector_selector';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPanel,
+ EuiLoadingSpinner,
+ EuiText,
+ EuiBadge,
+ EuiSpacer,
+} from '@elastic/eui';
+import { css } from '@emotion/css';
+import { useKibana } from '../../../../../../../common/lib/kibana';
+import { CreateConnectorPopover } from './create_connector_popover';
+import { ConnectorSetup } from './connector_setup';
+
+interface ConnectorCardsProps {
+ connectors?: AIConnector[];
+ onConnectorSaved: () => void;
+ onClose?: () => void;
+}
+
+export const ConnectorCards = React.memo(
+ ({ connectors, onConnectorSaved, onClose }) => {
+ const {
+ triggersActionsUi: { actionTypeRegistry },
+ } = useKibana().services;
+
+ if (!connectors) return ;
+
+ if (connectors.length > 0) {
+ return (
+ <>
+
+ {connectors?.map((connector) => (
+
+
+
+
+ {connector.name}
+
+
+
+ {actionTypeRegistry.get(connector.actionTypeId).actionTypeTitle}
+
+
+
+
+
+ ))}
+
+
+
+ >
+ );
+ }
+
+ return ;
+ }
+);
+ConnectorCards.displayName = 'ConnectorCards';
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/connector_setup.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/connector_setup.tsx
new file mode 100644
index 0000000000000..c0a82049d98c0
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/connector_setup.tsx
@@ -0,0 +1,146 @@
+/*
+ * 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, useState } from 'react';
+import {
+ useEuiTheme,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiListGroup,
+ EuiIcon,
+ EuiPanel,
+ EuiLoadingSpinner,
+ EuiText,
+ EuiLink,
+ EuiTextColor,
+} from '@elastic/eui';
+import { css } from '@emotion/css';
+import {
+ ConnectorAddModal,
+ type ActionConnector,
+} from '@kbn/triggers-actions-ui-plugin/public/common/constants';
+import type { ActionType } from '@kbn/actions-plugin/common';
+import { useKibana } from '../../../../../../../common/lib/kibana';
+import { useFilteredActionTypes } from './hooks/use_load_action_types';
+
+const usePanelCss = () => {
+ const { euiTheme } = useEuiTheme();
+ return css`
+ .connectorSelectorPanel {
+ height: 160px;
+ &.euiPanel:hover {
+ background-color: ${euiTheme.colors.lightestShade};
+ }
+ }
+ `;
+};
+
+interface ConnectorSetupProps {
+ onConnectorSaved?: (savedAction: ActionConnector) => void;
+ onClose?: () => void;
+ compressed?: boolean;
+}
+export const ConnectorSetup = React.memo(
+ ({ onConnectorSaved, onClose, compressed = false }) => {
+ const panelCss = usePanelCss();
+ const {
+ http,
+ triggersActionsUi: { actionTypeRegistry },
+ notifications: { toasts },
+ } = useKibana().services;
+ const [selectedActionType, setSelectedActionType] = useState(null);
+
+ const onModalClose = useCallback(() => {
+ setSelectedActionType(null);
+ onClose?.();
+ }, [onClose]);
+
+ const actionTypes = useFilteredActionTypes(http, toasts);
+
+ if (!actionTypes) {
+ return ;
+ }
+
+ return (
+ <>
+ {compressed ? (
+ ({
+ id: actionType.id,
+ label: actionType.name,
+ size: 's',
+ icon: (
+
+ ),
+ isDisabled: !actionType.enabled,
+ onClick: () => setSelectedActionType(actionType),
+ }))}
+ />
+ ) : (
+
+ {actionTypes?.map((actionType: ActionType) => (
+
+ setSelectedActionType(actionType)}
+ data-test-subj={`actionType-${actionType.id}`}
+ className={panelCss}
+ >
+
+
+
+
+
+
+
+ {actionType.name}
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ {selectedActionType && (
+
+ )}
+ >
+ );
+ }
+);
+ConnectorSetup.displayName = 'ConnectorSetup';
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/create_connector_popover.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/create_connector_popover.tsx
new file mode 100644
index 0000000000000..ebfac618d195e
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/create_connector_popover.tsx
@@ -0,0 +1,45 @@
+/*
+ * 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, useState } from 'react';
+import { css } from '@emotion/css';
+import { EuiPopover, EuiLink, EuiText } from '@elastic/eui';
+import { ConnectorSetup } from './connector_setup';
+import * as i18n from '../../translations';
+
+interface CreateConnectorPopoverProps {
+ onConnectorSaved: () => void;
+}
+
+export const CreateConnectorPopover = React.memo(
+ ({ onConnectorSaved }) => {
+ const [isOpen, setIsPopoverOpen] = useState(false);
+ const closePopover = useCallback(() => setIsPopoverOpen(false), []);
+
+ const onButtonClick = () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen);
+
+ return (
+
+
+ {i18n.ASSISTANT_CARD_CREATE_NEW_CONNECTOR_POPOVER}
+
+
+ }
+ isOpen={isOpen}
+ closePopover={closePopover}
+ data-test-subj="createConnectorPopover"
+ >
+
+
+ );
+ }
+);
+CreateConnectorPopover.displayName = 'CreateConnectorPopover';
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/hooks/use_load_action_types.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/hooks/use_load_action_types.ts
new file mode 100644
index 0000000000000..106ce537099a8
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/components/connectors/hooks/use_load_action_types.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { useMemo } from 'react';
+import { useLoadActionTypes as loadActionTypes } from '@kbn/elastic-assistant/impl/connectorland/use_load_action_types';
+import type { HttpSetup } from '@kbn/core-http-browser';
+import type { IToasts } from '@kbn/core-notifications-browser';
+import { AllowedActionTypeIds } from '../../../constants';
+
+export const useFilteredActionTypes = (http: HttpSetup, toasts: IToasts) => {
+ const { data } = loadActionTypes({ http, toasts });
+ return useMemo(() => data?.filter(({ id }) => AllowedActionTypeIds.includes(id)), [data]);
+};
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/constants.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/constants.ts
new file mode 100644
index 0000000000000..35811c18de471
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/constants.ts
@@ -0,0 +1,8 @@
+/*
+ * 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.
+ */
+
+export const AllowedActionTypeIds = ['.bedrock', '.gen-ai', '.gemini'];
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/index.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/index.ts
new file mode 100644
index 0000000000000..c80c07604717f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/index.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 { AssistantAvatar } from '@kbn/elastic-assistant';
+import type { OnboardingCardConfig } from '../../../../types';
+import { OnboardingCardId } from '../../../../constants';
+import { ASSISTANT_CARD_TITLE } from './translations';
+import { checkAssistantCardComplete } from './assistant_check_complete';
+
+export const assistantCardConfig: OnboardingCardConfig = {
+ id: OnboardingCardId.assistant,
+ title: ASSISTANT_CARD_TITLE,
+ icon: AssistantAvatar,
+ Component: React.lazy(() => import('./assistant_card')),
+ checkComplete: checkAssistantCardComplete,
+};
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/translations.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/translations.ts
new file mode 100644
index 0000000000000..41e73bdacf061
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/translations.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 ASSISTANT_CARD_TITLE = i18n.translate(
+ 'xpack.securitySolution.onboarding.assistantCard.title',
+ {
+ defaultMessage: 'Configure AI Assistant',
+ }
+);
+
+export const ASSISTANT_CARD_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.onboarding.assistantCard.description',
+ {
+ defaultMessage:
+ 'Choose and configure any AI provider available to use with Elastic AI Assistant.',
+ }
+);
+
+export const ASSISTANT_CARD_CALLOUT_INTEGRATIONS_TEXT = i18n.translate(
+ 'xpack.securitySolution.onboarding.assistantCard.calloutIntegrationsText',
+ {
+ defaultMessage: 'To add Elastic rules add integrations first.',
+ }
+);
+
+export const ASSISTANT_CARD_CALLOUT_INTEGRATIONS_BUTTON = i18n.translate(
+ 'xpack.securitySolution.onboarding.assistantCard.calloutIntegrationsButton',
+ {
+ defaultMessage: 'Add integrations step',
+ }
+);
+
+export const ASSISTANT_CARD_CREATE_NEW_CONNECTOR_POPOVER = i18n.translate(
+ 'xpack.securitySolution.onboarding.assistantCard.createNewConnectorPopover',
+ {
+ defaultMessage: 'Create new connector',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/attack_discover_card.test.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/attack_discover_card.test.tsx
new file mode 100644
index 0000000000000..19b327f77487c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/attack_discover_card.test.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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 { render } from '@testing-library/react';
+import { AttackDiscoveryCard } from './attack_discovery_card';
+import { TestProviders } from '../../../../../common/mock';
+
+const props = {
+ setComplete: jest.fn(),
+ checkComplete: jest.fn(),
+ isCardComplete: jest.fn(),
+ setExpandedCardId: jest.fn(),
+};
+
+describe('RulesCard', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('description should be in the document', () => {
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId('attackDiscoveryCardDescription')).toBeInTheDocument();
+ });
+
+ it('card callout should be rendered if integrations cards is not complete', () => {
+ props.isCardComplete.mockReturnValueOnce(false);
+
+ const { getByText } = render(
+
+
+
+ );
+
+ expect(getByText('To use Attack Discovery add integrations first.')).toBeInTheDocument();
+ });
+
+ it('card button should be disabled if integrations cards is not complete', () => {
+ props.isCardComplete.mockReturnValueOnce(false);
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId('attackDiscoveryCardButton').querySelector('button')).toBeDisabled();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/attack_discovery_card.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/attack_discovery_card.tsx
new file mode 100644
index 0000000000000..b34aa4367b09c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/attack_discovery_card.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
+import { SecurityPageName } from '@kbn/security-solution-navigation';
+import { SecuritySolutionLinkButton } from '../../../../../common/components/links';
+import { OnboardingCardId } from '../../../../constants';
+import type { OnboardingCardComponent } from '../../../../types';
+import { OnboardingCardContentImagePanel } from '../common/card_content_image_panel';
+import { CardCallOut } from '../common/card_callout';
+import attackDiscoveryImageSrc from './images/attack_discovery.png';
+import * as i18n from './translations';
+
+export const AttackDiscoveryCard: OnboardingCardComponent = ({
+ isCardComplete,
+ setExpandedCardId,
+ setComplete,
+}) => {
+ const isIntegrationsCardComplete = useMemo(
+ () => isCardComplete(OnboardingCardId.integrations),
+ [isCardComplete]
+ );
+
+ const expandIntegrationsCard = useCallback(() => {
+ setExpandedCardId(OnboardingCardId.integrations, { scroll: true });
+ }, [setExpandedCardId]);
+
+ return (
+
+
+
+
+ {i18n.ATTACK_DISCOVERY_CARD_DESCRIPTION}
+
+ {!isIntegrationsCardComplete && (
+ <>
+
+
+
+
+ {i18n.ATTACK_DISCOVERY_CARD_CALLOUT_INTEGRATIONS_BUTTON}
+
+
+
+
+
+
+ }
+ />
+ >
+ )}
+
+
+ setComplete(true)}
+ deepLinkId={SecurityPageName.attackDiscovery}
+ fill
+ isDisabled={!isIntegrationsCardComplete}
+ >
+ {i18n.ATTACK_DISCOVERY_CARD_START_ATTACK_DISCOVERY_BUTTON}
+
+
+
+
+ );
+};
+
+// eslint-disable-next-line import/no-default-export
+export default AttackDiscoveryCard;
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/images/attack_discovery.png b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/images/attack_discovery.png
new file mode 100644
index 0000000000000..0d6b551e09661
Binary files /dev/null and b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/images/attack_discovery.png differ
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/images/attack_discovery_icon.png b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/images/attack_discovery_icon.png
new file mode 100644
index 0000000000000..912b0cae64733
Binary files /dev/null and b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/images/attack_discovery_icon.png differ
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/index.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/index.ts
new file mode 100644
index 0000000000000..cb47512fd8b45
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/index.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 type { OnboardingCardConfig } from '../../../../types';
+import { OnboardingCardId } from '../../../../constants';
+import { ATTACK_DISCOVERY_CARD_TITLE } from './translations';
+import attackDiscoveryIcon from './images/attack_discovery_icon.png';
+
+export const attackDiscoveryCardConfig: OnboardingCardConfig = {
+ id: OnboardingCardId.attackDiscovery,
+ title: ATTACK_DISCOVERY_CARD_TITLE,
+ icon: attackDiscoveryIcon,
+ Component: React.lazy(() => import('./attack_discovery_card')),
+};
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/translations.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/translations.ts
new file mode 100644
index 0000000000000..be1334b35b217
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/attack_discovery/translations.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 ATTACK_DISCOVERY_CARD_TITLE = i18n.translate(
+ 'xpack.securitySolution.onboarding.attackDiscoveryCard.title',
+ {
+ defaultMessage: 'Start using Attack Discovery',
+ }
+);
+
+export const ATTACK_DISCOVERY_CARD_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.onboarding.attackDiscoveryCard.description',
+ {
+ defaultMessage:
+ 'Visualize, sort, filter, and investigate alerts from across your infrastructure. Examine individual alerts of interest, and discover general patterns in alert volume and severity.',
+ }
+);
+
+export const ATTACK_DISCOVERY_CARD_CALLOUT_INTEGRATIONS_TEXT = i18n.translate(
+ 'xpack.securitySolution.onboarding.attackDiscoveryCard.calloutIntegrationsText',
+ {
+ defaultMessage: 'To use Attack Discovery add integrations first.',
+ }
+);
+
+export const ATTACK_DISCOVERY_CARD_CALLOUT_INTEGRATIONS_BUTTON = i18n.translate(
+ 'xpack.securitySolution.onboarding.attackDiscoveryCard.calloutIntegrationsButton',
+ {
+ defaultMessage: 'Add integrations step',
+ }
+);
+
+export const ATTACK_DISCOVERY_CARD_START_ATTACK_DISCOVERY_BUTTON = i18n.translate(
+ 'xpack.securitySolution.onboarding.attackDiscoveryCard.startAttackDiscoveryButton',
+ {
+ defaultMessage: 'Start using Attack Discovery',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_image_panel.styles.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_image_panel.styles.ts
index 16e3c3820b310..d8f6d6c278ee3 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_image_panel.styles.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_image_panel.styles.ts
@@ -12,8 +12,9 @@ export const useCardContentImagePanelStyles = () => {
const { euiTheme } = useEuiTheme();
const shadowStyles = useEuiShadow('m');
return css`
+ padding-top: 8px;
.cardSpacer {
- width: 10%;
+ width: 8%;
}
.cardImage {
width: 50%;
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_image_panel.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_image_panel.tsx
index 8b9fd9d9cc00c..0110e001af7ac 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_image_panel.tsx
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_image_panel.tsx
@@ -16,7 +16,7 @@ export const OnboardingCardContentImagePanel = React.memo<
>(({ children, imageSrc, imageAlt }) => {
const styles = useCardContentImagePanelStyles();
return (
-
+
{children}
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_panel.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_panel.tsx
index 981a60a648508..3d5489b9be1cc 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_panel.tsx
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_content_panel.tsx
@@ -5,20 +5,24 @@
* 2.0.
*/
import React, { type PropsWithChildren } from 'react';
-import { EuiPanel, type EuiPanelProps } from '@elastic/eui';
+import { COLOR_MODES_STANDARD, EuiPanel, useEuiTheme, type EuiPanelProps } from '@elastic/eui';
import { css } from '@emotion/react';
export const OnboardingCardContentPanel = React.memo>(
({ children, ...panelProps }) => {
+ const { euiTheme, colorMode } = useEuiTheme();
+ const isDarkMode = colorMode === COLOR_MODES_STANDARD.dark;
return (
-
-
+
+
{children}
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/dashboards_card.test.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/dashboards_card.test.tsx
new file mode 100644
index 0000000000000..f7aa198eccab4
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/dashboards_card.test.tsx
@@ -0,0 +1,76 @@
+/*
+ * 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 { fireEvent, render } from '@testing-library/react';
+import { DashboardsCard } from './dashboards_card';
+import { TestProviders } from '../../../../../common/mock';
+import { OnboardingCardId } from '../../../../constants';
+
+jest.mock('../../../onboarding_context');
+
+const props = {
+ setComplete: jest.fn(),
+ checkComplete: jest.fn(),
+ isCardComplete: jest.fn(),
+ setExpandedCardId: jest.fn(),
+};
+
+describe('RulesCard', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('description should be in the document', () => {
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId('dashboardsDescription')).toBeInTheDocument();
+ });
+
+ it('card callout should be rendered if integrations cards is not complete', () => {
+ props.isCardComplete.mockReturnValueOnce(false);
+
+ const { getByText } = render(
+
+
+
+ );
+
+ expect(getByText('To view dashboards add integrations first.')).toBeInTheDocument();
+ });
+
+ it('card button should be disabled if integrations cards is not complete', () => {
+ props.isCardComplete.mockReturnValueOnce(false);
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId('dashboardsCardButton').querySelector('button')).toBeDisabled();
+ });
+ it('should expand integrations card when callout link is clicked', () => {
+ props.isCardComplete.mockReturnValueOnce(false); // To show the callout
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ const link = getByTestId('dashboardsCardCalloutLink');
+ fireEvent.click(link);
+
+ expect(props.setExpandedCardId).toHaveBeenCalledWith(OnboardingCardId.integrations, {
+ scroll: true,
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/dashboards_card.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/dashboards_card.tsx
index 6e46380a8e300..df98800d83f32 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/dashboards_card.tsx
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/dashboards_card.tsx
@@ -42,7 +42,7 @@ export const DashboardsCard: OnboardingCardComponent = ({
alignItems="flexStart"
>
-
+
{i18n.DASHBOARDS_CARD_DESCRIPTION}
{!isIntegrationsCardComplete && (
@@ -53,7 +53,10 @@ export const DashboardsCard: OnboardingCardComponent = ({
icon="iInCircle"
text={i18n.DASHBOARDS_CARD_CALLOUT_INTEGRATIONS_TEXT}
action={
-
+
{i18n.DASHBOARDS_CARD_CALLOUT_INTEGRATIONS_BUTTON}
@@ -66,11 +69,9 @@ export const DashboardsCard: OnboardingCardComponent = ({
>
)}
-
+
{
- setComplete(true);
- }}
+ onClick={() => setComplete(true)}
linkId="goToDashboardsButton"
cardId={OnboardingCardId.dashboards}
deepLinkId={SecurityPageName.dashboards}
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/images/dashboards_icon.png b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/images/dashboards_icon.png
new file mode 100644
index 0000000000000..ddc024696e224
Binary files /dev/null and b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/images/dashboards_icon.png differ
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/index.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/index.ts
index b09a458847475..7c625b92feb67 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/index.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/index.ts
@@ -9,11 +9,12 @@ import React from 'react';
import type { OnboardingCardConfig } from '../../../../types';
import { OnboardingCardId } from '../../../../constants';
import { DASHBOARDS_CARD_TITLE } from './translations';
+import dashboardsIcon from './images/dashboards_icon.png';
export const dashboardsCardConfig: OnboardingCardConfig = {
id: OnboardingCardId.dashboards,
title: DASHBOARDS_CARD_TITLE,
- icon: 'dashboardApp',
+ icon: dashboardsIcon,
Component: React.lazy(
() => import('./dashboards_card' /* webpackChunkName: "onboarding_dashboards_card" */)
),
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/translations.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/translations.ts
index 33d7a2a9be98b..cf1a280122d79 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/translations.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/translations.ts
@@ -30,7 +30,7 @@ export const DASHBOARDS_CARD_CALLOUT_INTEGRATIONS_TEXT = i18n.translate(
export const DASHBOARDS_CARD_CALLOUT_INTEGRATIONS_BUTTON = i18n.translate(
'xpack.securitySolution.onboarding.dashboardsCard.calloutIntegrationsButton',
{
- defaultMessage: 'Add integrations',
+ defaultMessage: 'Add integrations step',
}
);
export const DASHBOARDS_CARD_GO_TO_DASHBOARDS_BUTTON = i18n.translate(
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/images/integrations_icon.png b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/images/integrations_icon.png
new file mode 100644
index 0000000000000..438e220084c46
Binary files /dev/null and b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/images/integrations_icon.png differ
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/index.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/index.ts
index 6da21d4e54581..509037edea985 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/index.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/index.ts
@@ -10,13 +10,14 @@ import { i18n } from '@kbn/i18n';
import type { OnboardingCardConfig } from '../../../../types';
import { checkIntegrationsCardComplete } from './integrations_check_complete';
import { OnboardingCardId } from '../../../../constants';
+import integrationsIcon from './images/integrations_icon.png';
export const integrationsCardConfig: OnboardingCardConfig = {
id: OnboardingCardId.integrations,
title: i18n.translate('xpack.securitySolution.onboarding.integrationsCard.title', {
defaultMessage: 'Add data with integrations',
}),
- icon: 'fleetApp',
+ icon: integrationsIcon,
Component: React.lazy(
() => import('./integrations_card' /* webpackChunkName: "onboarding_integrations_card" */)
),
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/images/rules.png b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/images/rules.png
new file mode 100644
index 0000000000000..5d88e8c95d43c
Binary files /dev/null and b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/images/rules.png differ
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/images/rules_icon.png b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/images/rules_icon.png
new file mode 100644
index 0000000000000..b2b4848e0be1d
Binary files /dev/null and b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/images/rules_icon.png differ
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/index.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/index.ts
new file mode 100644
index 0000000000000..09a9380516339
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/index.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 type { OnboardingCardConfig } from '../../../../types';
+import { OnboardingCardId } from '../../../../constants';
+import { RULES_CARD_TITLE } from './translations';
+import rulesIcon from './images/rules_icon.png';
+import { checkRulesComplete } from './rules_check_complete';
+
+export const rulesCardConfig: OnboardingCardConfig = {
+ id: OnboardingCardId.rules,
+ title: RULES_CARD_TITLE,
+ icon: rulesIcon,
+ Component: React.lazy(() => import('./rules_card')),
+ checkComplete: checkRulesComplete,
+};
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_card.test.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_card.test.tsx
new file mode 100644
index 0000000000000..f7156adc34eba
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_card.test.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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 { render } from '@testing-library/react';
+import { RulesCard } from './rules_card';
+import { TestProviders } from '../../../../../common/mock';
+
+const props = {
+ setComplete: jest.fn(),
+ checkComplete: jest.fn(),
+ isCardComplete: jest.fn(),
+ setExpandedCardId: jest.fn(),
+};
+
+describe('RulesCard', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('description should be in the document', () => {
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId('rulesCardDescription')).toBeInTheDocument();
+ });
+
+ it('card callout should be rendered if integrations cards is not complete', () => {
+ props.isCardComplete.mockReturnValueOnce(false);
+
+ const { getByText } = render(
+
+
+
+ );
+
+ expect(getByText('To add Elastic rules add integrations first.')).toBeInTheDocument();
+ });
+
+ it('card button should be disabled if integrations cards is not complete', () => {
+ props.isCardComplete.mockReturnValueOnce(false);
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ expect(getByTestId('rulesCardButton').querySelector('button')).toBeDisabled();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_card.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_card.tsx
new file mode 100644
index 0000000000000..7f283c0ffbc78
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_card.tsx
@@ -0,0 +1,77 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
+import { SecurityPageName } from '@kbn/security-solution-navigation';
+import { SecuritySolutionLinkButton } from '../../../../../common/components/links';
+import { OnboardingCardId } from '../../../../constants';
+import type { OnboardingCardComponent } from '../../../../types';
+import { OnboardingCardContentImagePanel } from '../common/card_content_image_panel';
+import { CardCallOut } from '../common/card_callout';
+import rulesImageSrc from './images/rules.png';
+import * as i18n from './translations';
+
+export const RulesCard: OnboardingCardComponent = ({ isCardComplete, setExpandedCardId }) => {
+ const isIntegrationsCardComplete = useMemo(
+ () => isCardComplete(OnboardingCardId.integrations),
+ [isCardComplete]
+ );
+
+ const expandIntegrationsCard = useCallback(() => {
+ setExpandedCardId(OnboardingCardId.integrations, { scroll: true });
+ }, [setExpandedCardId]);
+
+ return (
+
+
+
+
+ {i18n.RULES_CARD_DESCRIPTION}
+
+ {!isIntegrationsCardComplete && (
+ <>
+
+
+
+ {i18n.RULES_CARD_CALLOUT_INTEGRATIONS_BUTTON}
+
+
+
+
+
+ }
+ />
+ >
+ )}
+
+
+
+ {i18n.RULES_CARD_ADD_RULES_BUTTON}
+
+
+
+
+ );
+};
+
+// eslint-disable-next-line import/no-default-export
+export default RulesCard;
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_check_complete.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_check_complete.ts
new file mode 100644
index 0000000000000..3679141a255b4
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_check_complete.ts
@@ -0,0 +1,62 @@
+/*
+ * 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 { HttpSetup } from '@kbn/core/public';
+import type { OnboardingCardCheckComplete } from '../../../../types';
+import { ENABLED_FIELD } from '../../../../../../common';
+import { DETECTION_ENGINE_RULES_URL_FIND } from '../../../../../../common/constants';
+import type { FetchRulesResponse } from '../../../../../detection_engine/rule_management/logic';
+
+export const checkRulesComplete: OnboardingCardCheckComplete = async ({
+ http,
+ notifications: { toasts },
+}) => {
+ // Check if there are any rules installed and enabled
+ try {
+ const data = await fetchRuleManagementFilters({
+ http,
+ query: {
+ page: 1,
+ per_page: 20,
+ sort_field: 'enabled',
+ sort_order: 'desc',
+ filter: `${ENABLED_FIELD}: true`,
+ },
+ });
+ return {
+ isComplete: data?.total > 0,
+ };
+ } catch (e) {
+ toasts.addError(e, { title: `Failed to check Card Rules completion.` });
+
+ return {
+ isComplete: false,
+ };
+ }
+};
+
+const fetchRuleManagementFilters = async ({
+ http,
+ signal,
+ query,
+}: {
+ http: HttpSetup;
+ signal?: AbortSignal;
+ query?: {
+ page: number;
+ per_page: number;
+ sort_field: string;
+ sort_order: string;
+ filter: string;
+ };
+}): Promise =>
+ http.fetch(DETECTION_ENGINE_RULES_URL_FIND, {
+ method: 'GET',
+ version: '2023-10-31',
+ signal,
+ query,
+ });
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/translations.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/translations.ts
new file mode 100644
index 0000000000000..81e0919cd7184
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/translations.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 RULES_CARD_TITLE = i18n.translate(
+ 'xpack.securitySolution.onboarding.rulesCard.title',
+ {
+ defaultMessage: 'Enable rules',
+ }
+);
+
+export const RULES_CARD_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.onboarding.rulesCard.description',
+ {
+ defaultMessage:
+ 'Elastic Security comes with prebuilt detection rules that run in the background and create alerts when their conditions are met.',
+ }
+);
+
+export const RULES_CARD_CALLOUT_INTEGRATIONS_TEXT = i18n.translate(
+ 'xpack.securitySolution.onboarding.rulesCard.calloutIntegrationsText',
+ {
+ defaultMessage: 'To add Elastic rules add integrations first.',
+ }
+);
+
+export const RULES_CARD_CALLOUT_INTEGRATIONS_BUTTON = i18n.translate(
+ 'xpack.securitySolution.onboarding.rulesCard.calloutIntegrationsButton',
+ {
+ defaultMessage: 'Add integrations step',
+ }
+);
+
+export const RULES_CARD_ADD_RULES_BUTTON = i18n.translate(
+ 'xpack.securitySolution.onboarding.rulesCard.addRulesButton',
+ {
+ defaultMessage: 'Add Elastic rules',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_completed_cards.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_completed_cards.ts
index 98eb48a02365c..5903af89b5642 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_completed_cards.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/hooks/use_completed_cards.ts
@@ -6,16 +6,16 @@
*/
import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useKibana } from '../../../../common/lib/kibana';
import { useStoredCompletedCardIds } from '../../../hooks/use_stored_state';
import type { OnboardingCardId } from '../../../constants';
import type {
CheckCompleteResult,
CheckCompleteResponse,
- OnboardingCardConfig,
OnboardingGroupConfig,
+ OnboardingCardConfig,
} from '../../../types';
import { useOnboardingContext } from '../../onboarding_context';
-import { useKibana } from '../../../../common/lib/kibana';
export type IsCardComplete = (cardId: OnboardingCardId) => boolean;
export type SetCardComplete = (
@@ -118,7 +118,7 @@ export const useCompletedCards = (cardsGroupConfig: OnboardingGroupConfig[]) =>
});
}
},
- [cardsWithAutoCheck, services, processCardCheckCompleteResult]
+ [cardsWithAutoCheck, processCardCheckCompleteResult, services]
);
// Initial auto-check for all cards, it should run only once, after cardsGroupConfig is properly populated
@@ -128,7 +128,7 @@ export const useCompletedCards = (cardsGroupConfig: OnboardingGroupConfig[]) =>
processCardCheckCompleteResult(card.id, checkCompleteResult);
})
);
- }, [cardsWithAutoCheck, services, processCardCheckCompleteResult]);
+ }, [cardsWithAutoCheck, processCardCheckCompleteResult, services]);
return {
isCardComplete,
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/onboarding_body.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/onboarding_body.tsx
index d363bb702d192..3209028e1f0cd 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/onboarding_body.tsx
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/onboarding_body.tsx
@@ -41,6 +41,13 @@ export const OnboardingBody = React.memo(() => {
[setCardComplete]
);
+ const createCheckCardComplete = useCallback(
+ (cardId: OnboardingCardId) => () => {
+ checkCardComplete(cardId);
+ },
+ [checkCardComplete]
+ );
+
return (
{bodyConfig.map((group, index) => (
@@ -64,6 +71,7 @@ export const OnboardingBody = React.memo(() => {
}>
{
- const { euiTheme } = useEuiTheme();
+ const { euiTheme, colorMode } = useEuiTheme();
const successBackgroundColor = useEuiBackgroundColor('success');
+ const isDarkMode = colorMode === COLOR_MODES_STANDARD.dark;
return css`
.onboardingCardHeader {
@@ -22,7 +23,7 @@ export const useCardPanelStyles = () => {
.onboardingCardIcon {
padding: ${euiTheme.size.m};
border-radius: 50%;
- background-color: ${euiTheme.colors.lightestShade};
+ background-color: ${isDarkMode ? euiTheme.colors.body : euiTheme.colors.lightestShade};
}
.onboardingCardHeaderTitle {
font-weight: ${euiTheme.font.weight.semiBold};
@@ -56,5 +57,11 @@ export const useCardPanelStyles = () => {
background-color: ${successBackgroundColor};
}
}
+ ${isDarkMode
+ ? `
+ background-color: ${euiTheme.colors.lightestShade};
+ border: 1px solid ${euiTheme.colors.mediumShade};
+ `
+ : ''}
`;
};
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/common/link_card.styles.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/common/link_card.styles.ts
index c39c9b458f478..8643c3254a6ee 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/common/link_card.styles.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/common/link_card.styles.ts
@@ -14,7 +14,6 @@ export const useCardStyles = () => {
return css`
min-width: 315px;
-
/* We needed to add the "headerCard" class to make it take priority over the default EUI card styles */
&.headerCard:hover {
*:not(.headerCardLink) {
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/onboarding_header.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/onboarding_header.tsx
index 63314bd4c9864..0210c88186a9a 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/onboarding_header.tsx
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/onboarding_header.tsx
@@ -4,12 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-/*
- * 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 {
@@ -22,7 +16,6 @@ import {
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
-
import { useCurrentUser } from '../../../common/lib/kibana/hooks';
import { useOnboardingHeaderStyles } from './onboarding_header.styles';
import rocketImage from './images/header_rocket.png';
diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/translations.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/translations.ts
index 99ae806031643..c1f8ca8695bb6 100644
--- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/translations.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/translations.ts
@@ -23,6 +23,6 @@ export const GET_STARTED_DATA_INGESTION_HUB_SUBTITLE = i18n.translate(
export const GET_STARTED_DATA_INGESTION_HUB_DESCRIPTION = i18n.translate(
'xpack.securitySolution.onboarding.description',
{
- defaultMessage: `Follow these steps to set up your workspace.`,
+ defaultMessage: `A SIEM with AI-driven security analytics, XDR and Cloud Security.`,
}
);
diff --git a/x-pack/plugins/security_solution/public/onboarding/constants.ts b/x-pack/plugins/security_solution/public/onboarding/constants.ts
index 039b83f754093..0b3a1f885838c 100644
--- a/x-pack/plugins/security_solution/public/onboarding/constants.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/constants.ts
@@ -11,4 +11,6 @@ export enum OnboardingCardId {
dashboards = 'dashboards',
rules = 'rules',
alerts = 'alerts',
+ assistant = 'asistant',
+ attackDiscovery = 'attack_discovery',
}
diff --git a/x-pack/plugins/security_solution/public/onboarding/types.ts b/x-pack/plugins/security_solution/public/onboarding/types.ts
index 1f7e220a5c06b..7142949eb29b4 100644
--- a/x-pack/plugins/security_solution/public/onboarding/types.ts
+++ b/x-pack/plugins/security_solution/public/onboarding/types.ts
@@ -49,6 +49,10 @@ export type OnboardingCardComponent = React.ComponentType<{
* Function to set the current card completion status.
*/
setComplete: SetComplete;
+ /**
+ * Function to check the current card completion status again.
+ */
+ checkComplete: () => void;
/**
* Function to check if a specific card is complete.
*/