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 index 4b87f23dd2435..04b772681b2b3 100644 --- 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 @@ -6,7 +6,7 @@ */ import React, { useCallback, useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiText } from '@elastic/eui'; +import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiText } from '@elastic/eui'; import { css } from '@emotion/css'; import { OnboardingCardId } from '../../../../constants'; import type { OnboardingCardComponent } from '../../../../types'; @@ -15,6 +15,7 @@ import { OnboardingCardContentPanel } from '../common/card_content_panel'; import { ConnectorCards } from './connectors/connector_cards'; import { CardCallOut } from '../common/card_callout'; import type { AssistantCardMetadata } from './types'; +import { MissingPrivilegesDescription } from './connectors/missing_privileges_tooltip'; export const AssistantCard: OnboardingCardComponent = ({ isCardComplete, @@ -32,43 +33,55 @@ export const AssistantCard: OnboardingCardComponent = ({ }, [setExpandedCardId]); const connectors = checkCompleteMetadata?.connectors; + const canExecuteConnectors = checkCompleteMetadata?.canExecuteConnectors; + const canCreateConnectors = checkCompleteMetadata?.canCreateConnectors; return ( - - - - {i18n.ASSISTANT_CARD_DESCRIPTION} - - - - {isIntegrationsCardComplete ? ( - - ) : ( - - - - {i18n.ASSISTANT_CARD_CALLOUT_INTEGRATIONS_BUTTON} - - - - - - } + {canExecuteConnectors ? ( + + + + {i18n.ASSISTANT_CARD_DESCRIPTION} + + + + {isIntegrationsCardComplete ? ( + - - )} - - + ) : ( + + + + {i18n.ASSISTANT_CARD_CALLOUT_INTEGRATIONS_BUTTON} + + + + + + } + /> + + )} + + + ) : ( + + + + )} ); }; 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 index bdb52b3a0e614..8c0d029cee583 100644 --- 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 @@ -14,8 +14,11 @@ import type { AssistantCardMetadata } from './types'; export const checkAssistantCardComplete: OnboardingCardCheckComplete< AssistantCardMetadata -> = async ({ http }) => { +> = async ({ http, application }) => { const allConnectors = await loadConnectors({ http }); + const { + capabilities: { actions }, + } = application; const aiConnectors = allConnectors.reduce((acc: AIConnector[], connector) => { if (!connector.isMissingSecrets && AllowedActionTypeIds.includes(connector.actionTypeId)) { @@ -37,6 +40,8 @@ export const checkAssistantCardComplete: OnboardingCardCheckComplete< completeBadgeText, metadata: { connectors: aiConnectors, + canExecuteConnectors: Boolean(actions?.show && actions?.execute), + canCreateConnectors: Boolean(actions?.save), }, }; }; diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/connector_cards.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/connector_cards.tsx index 3cdefaa1fe490..472459b631b0a 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/connector_cards.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/connector_cards.tsx @@ -15,69 +15,104 @@ import { EuiText, EuiBadge, EuiSpacer, + EuiCallOut, } 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'; +import * as i18n from './translations'; +import { MissingPrivilegesDescription } from './missing_privileges_tooltip'; interface ConnectorCardsProps { connectors?: AIConnector[]; onConnectorSaved: () => void; + canCreateConnectors?: boolean; } export const ConnectorCards = React.memo( - ({ connectors, onConnectorSaved }) => { + ({ connectors, onConnectorSaved, canCreateConnectors }) => { const { triggersActionsUi: { actionTypeRegistry }, } = useKibana().services; - if (!connectors) return ; + if (!connectors) { + return ; + } + + const hasConnectors = connectors.length > 0; - if (connectors.length > 0) { + // show callout when user is missing actions.save privilege + if (!hasConnectors && !canCreateConnectors) { return ( - <> - - {connectors.map((connector) => ( - - - - - {connector.name} - - - - {actionTypeRegistry.get(connector.actionTypeId).actionTypeTitle} - - - - - - ))} - - - - + + + ); } - return ; + return ( + <> + {hasConnectors ? ( + <> + + + + + ) : ( + + )} + + ); } ); ConnectorCards.displayName = 'ConnectorCards'; + +interface ConnectorListProps { + connectors: AIConnector[]; + actionTypeRegistry: ReturnType< + typeof useKibana + >['services']['triggersActionsUi']['actionTypeRegistry']; +} + +const ConnectorList = React.memo(({ connectors, actionTypeRegistry }) => ( + + {connectors.map((connector) => ( + + + + + {connector.name} + + + + {actionTypeRegistry.get(connector.actionTypeId).actionTypeTitle} + + + + + + ))} + +)); + +ConnectorList.displayName = 'ConnectorList'; diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/create_connector_popover.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/create_connector_popover.tsx index de432d6597afd..32bcd66f49249 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/create_connector_popover.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/create_connector_popover.tsx @@ -8,14 +8,16 @@ 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'; +import * as i18n from './translations'; +import { MissingPrivilegesTooltip } from './missing_privileges_tooltip'; interface CreateConnectorPopoverProps { onConnectorSaved: () => void; + canCreateConnectors?: boolean; } export const CreateConnectorPopover = React.memo( - ({ onConnectorSaved }) => { + ({ onConnectorSaved, canCreateConnectors }) => { const [isOpen, setIsPopoverOpen] = useState(false); const closePopover = useCallback(() => setIsPopoverOpen(false), []); @@ -23,6 +25,15 @@ export const CreateConnectorPopover = React.memo( () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen), [] ); + if (!canCreateConnectors) { + return ( + + + {i18n.ASSISTANT_CARD_CREATE_NEW_CONNECTOR_POPOVER} + + + ); + } return ( (({ children }) => ( + } + > + {children} + +)); +MissingPrivilegesTooltip.displayName = 'MissingPrivilegesTooltip'; + +export const MissingPrivilegesDescription = React.memo(() => { + return ( + + {i18n.PRIVILEGES_REQUIRED_TITLE} + + +
    +
  • {i18n.REQUIRED_PRIVILEGES_CONNECTORS_ALL}
  • +
+
+
+ {i18n.CONTACT_ADMINISTRATOR} +
+ ); +}); +MissingPrivilegesDescription.displayName = 'MissingPrivilegesDescription'; diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/translations.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/translations.ts new file mode 100644 index 0000000000000..983a6a67f5b32 --- /dev/null +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/connectors/translations.ts @@ -0,0 +1,43 @@ +/* + * 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_CREATE_NEW_CONNECTOR_POPOVER = i18n.translate( + 'xpack.securitySolution.onboarding.assistantCard.createNewConnectorPopover', + { + defaultMessage: 'Create new connector', + } +); + +export const PRIVILEGES_MISSING_TITLE = i18n.translate( + 'xpack.securitySolution.onboarding.assistantCard.missingPrivileges.title', + { + defaultMessage: 'Missing privileges', + } +); + +export const PRIVILEGES_REQUIRED_TITLE = i18n.translate( + 'xpack.securitySolution.onboarding.assistantCard.requiredPrivileges', + { + defaultMessage: 'The minimum Kibana privileges required to use this feature are:', + } +); + +export const REQUIRED_PRIVILEGES_CONNECTORS_ALL = i18n.translate( + 'xpack.securitySolution.onboarding.assistantCard.requiredPrivileges.connectorsAll', + { + defaultMessage: 'Management > Connectors: All', + } +); + +export const CONTACT_ADMINISTRATOR = i18n.translate( + 'xpack.securitySolution.onboarding.assistantCard.missingPrivileges.contactAdministrator', + { + defaultMessage: 'Contact your administrator for assistance.', + } +); 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 index 27deda4190f2e..fedf975052327 100644 --- 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 @@ -27,6 +27,6 @@ export const assistantCardConfig: OnboardingCardConfig = checkComplete: checkAssistantCardComplete, // Both capabilities are needed for this card, so we should use a double array to create an AND conditional // (a single array would create an OR conditional between them) - capabilities: [['securitySolutionAssistant.ai-assistant', 'actions.show']], + capabilities: [['securitySolutionAssistant.ai-assistant']], licenseType: 'enterprise', }; 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 index 41e73bdacf061..de3c111280436 100644 --- 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 @@ -42,3 +42,10 @@ export const ASSISTANT_CARD_CREATE_NEW_CONNECTOR_POPOVER = i18n.translate( defaultMessage: 'Create new connector', } ); + +export const PRIVILEGES_MISSING_TITLE = i18n.translate( + 'xpack.securitySolution.onboarding.assistantCard.callout.title', + { + defaultMessage: 'Missing privileges', + } +); diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/types.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/types.ts index f1e0216406391..d6647adf054fe 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/types.ts +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/assistant/types.ts @@ -9,4 +9,6 @@ import type { ActionConnector } from '@kbn/alerts-ui-shared'; export interface AssistantCardMetadata { connectors: ActionConnector[]; + canExecuteConnectors: boolean; + canCreateConnectors: boolean; }