mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[8.10] [Security Solution] Adds new Elastic AI Assistant logo and global header menu item (#164763) (#164909)
# Backport This will backport the following commits from `main` to `8.10`: - [[Security Solution] Adds new Elastic AI Assistant logo and global header menu item (#164763)](https://github.com/elastic/kibana/pull/164763) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Garrett Spong","email":"spong@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-08-26T00:45:43Z","message":"[Security Solution] Adds new Elastic AI Assistant logo and global header menu item (#164763)\n\n## Summary\r\n\r\nAdds new Elastic AI Assistant logo and global header menu item to all\r\nSecurity Solution pages.\r\n\r\nResolves https://github.com/elastic/security-team/issues/7407\r\n\r\nNew logo within the assistant itself (header and assistant avatar):\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"2a94c2ca
-37d6-49f0-af59-2b15fd37d81e\"\r\n/>\r\n</p> \r\n\r\nNew global header menu for both on-prem and serverless security\r\n`complete` deployments:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"67b030fe
-fb36-4a68-9331-d636e15a68f4\"\r\n/>\r\n</p> \r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"74751e3a
-a88a-4b39-bec0-73497dcd98b1\"\r\n/>\r\n</p> \r\n\r\n\r\nNote: If Security Assistant RBAC privileges are `NONE` (which includes\r\nserverless deployments that are NOT security `complete`), the global\r\nheader button will be hidden. We can revisit the upsell messaging\r\nopportunity here for serverless deployments.\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [X] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n* @elastic/security-docs, will need to update images and make note of\r\nnew global header item, will create issue...\r\nhttps://github.com/elastic/security-docs/issues/3804\r\n- [X] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"5cac49a319371a2341618050e94c03d3591c121c","branchLabelMapping":{"^v8.11.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","needs_docs","Team: SecuritySolution","Feature:Elastic AI Assistant","v8.10.0","v8.11.0"],"number":164763,"url":"https://github.com/elastic/kibana/pull/164763","mergeCommit":{"message":"[Security Solution] Adds new Elastic AI Assistant logo and global header menu item (#164763)\n\n## Summary\r\n\r\nAdds new Elastic AI Assistant logo and global header menu item to all\r\nSecurity Solution pages.\r\n\r\nResolves https://github.com/elastic/security-team/issues/7407\r\n\r\nNew logo within the assistant itself (header and assistant avatar):\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"2a94c2ca
-37d6-49f0-af59-2b15fd37d81e\"\r\n/>\r\n</p> \r\n\r\nNew global header menu for both on-prem and serverless security\r\n`complete` deployments:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"67b030fe
-fb36-4a68-9331-d636e15a68f4\"\r\n/>\r\n</p> \r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"74751e3a
-a88a-4b39-bec0-73497dcd98b1\"\r\n/>\r\n</p> \r\n\r\n\r\nNote: If Security Assistant RBAC privileges are `NONE` (which includes\r\nserverless deployments that are NOT security `complete`), the global\r\nheader button will be hidden. We can revisit the upsell messaging\r\nopportunity here for serverless deployments.\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [X] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n* @elastic/security-docs, will need to update images and make note of\r\nnew global header item, will create issue...\r\nhttps://github.com/elastic/security-docs/issues/3804\r\n- [X] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"5cac49a319371a2341618050e94c03d3591c121c"}},"sourceBranch":"main","suggestedTargetBranches":["8.10"],"targetPullRequestStates":[{"branch":"8.10","label":"v8.10.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.11.0","labelRegex":"^v8.11.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/164763","number":164763,"mergeCommit":{"message":"[Security Solution] Adds new Elastic AI Assistant logo and global header menu item (#164763)\n\n## Summary\r\n\r\nAdds new Elastic AI Assistant logo and global header menu item to all\r\nSecurity Solution pages.\r\n\r\nResolves https://github.com/elastic/security-team/issues/7407\r\n\r\nNew logo within the assistant itself (header and assistant avatar):\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"2a94c2ca
-37d6-49f0-af59-2b15fd37d81e\"\r\n/>\r\n</p> \r\n\r\nNew global header menu for both on-prem and serverless security\r\n`complete` deployments:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"67b030fe
-fb36-4a68-9331-d636e15a68f4\"\r\n/>\r\n</p> \r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"74751e3a
-a88a-4b39-bec0-73497dcd98b1\"\r\n/>\r\n</p> \r\n\r\n\r\nNote: If Security Assistant RBAC privileges are `NONE` (which includes\r\nserverless deployments that are NOT security `complete`), the global\r\nheader button will be hidden. We can revisit the upsell messaging\r\nopportunity here for serverless deployments.\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [X] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n* @elastic/security-docs, will need to update images and make note of\r\nnew global header item, will create issue...\r\nhttps://github.com/elastic/security-docs/issues/3804\r\n- [X] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"5cac49a319371a2341618050e94c03d3591c121c"}}]}] BACKPORT--> Co-authored-by: Garrett Spong <spong@users.noreply.github.com>
This commit is contained in:
parent
a72dc6781f
commit
eb217d5997
12 changed files with 182 additions and 42 deletions
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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, { ReactNode } from 'react';
|
||||
|
||||
export interface AssistantAvatarProps {
|
||||
size?: keyof typeof sizeMap;
|
||||
// Required for EuiAvatar `iconType` prop
|
||||
// eslint-disable-next-line react/no-unused-prop-types
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const sizeMap = {
|
||||
xl: 64,
|
||||
l: 48,
|
||||
m: 32,
|
||||
s: 24,
|
||||
xs: 16,
|
||||
};
|
||||
|
||||
/**
|
||||
* Default Elastic AI Assistant logo
|
||||
*
|
||||
* TODO: Can be removed once added to EUI
|
||||
*/
|
||||
export const AssistantAvatar = ({ size = 's' }: AssistantAvatarProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={sizeMap[size]}
|
||||
height={sizeMap[size]}
|
||||
viewBox="0 0 64 64"
|
||||
fill="none"
|
||||
>
|
||||
<path fill="#F04E98" d="M36 28h24v36H36V28Z" />
|
||||
<path fill="#00BFB3" d="M4 46c0-9.941 8.059-18 18-18h6v36h-6c-9.941 0-18-8.059-18-18Z" />
|
||||
<path
|
||||
fill="#343741"
|
||||
d="M60 12c0 6.627-5.373 12-12 12s-12-5.373-12-12S41.373 0 48 0s12 5.373 12 12Z"
|
||||
/>
|
||||
<path fill="#FA744E" d="M6 23C6 10.85 15.85 1 28 1v22H6Z" />
|
||||
</svg>
|
||||
);
|
|
@ -13,10 +13,7 @@ import { alertConvo, emptyWelcomeConvo } from '../../mock/conversation';
|
|||
|
||||
const testProps = {
|
||||
currentConversation: emptyWelcomeConvo,
|
||||
currentTitle: {
|
||||
title: 'Test Title',
|
||||
titleIcon: 'logoSecurity',
|
||||
},
|
||||
title: 'Test Title',
|
||||
docLinks: {
|
||||
ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
|
||||
DOC_LINK_VERSION: 'master',
|
||||
|
|
|
@ -26,7 +26,6 @@ import * as i18n from '../translations';
|
|||
|
||||
interface OwnProps {
|
||||
currentConversation: Conversation;
|
||||
currentTitle: { title: string | JSX.Element; titleIcon: string };
|
||||
defaultConnectorId?: string;
|
||||
defaultProvider?: OpenAiProviderType;
|
||||
docLinks: Omit<DocLinksStart, 'links'>;
|
||||
|
@ -39,6 +38,7 @@ interface OwnProps {
|
|||
setSelectedConversationId: React.Dispatch<React.SetStateAction<string>>;
|
||||
shouldDisableKeyboardShortcut?: () => boolean;
|
||||
showAnonymizedValues: boolean;
|
||||
title: string | JSX.Element;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
@ -49,7 +49,6 @@ type Props = OwnProps;
|
|||
*/
|
||||
export const AssistantHeader: React.FC<Props> = ({
|
||||
currentConversation,
|
||||
currentTitle,
|
||||
defaultConnectorId,
|
||||
defaultProvider,
|
||||
docLinks,
|
||||
|
@ -62,6 +61,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
setSelectedConversationId,
|
||||
shouldDisableKeyboardShortcut,
|
||||
showAnonymizedValues,
|
||||
title,
|
||||
}) => {
|
||||
const showAnonymizedValuesChecked = useMemo(
|
||||
() =>
|
||||
|
@ -81,10 +81,10 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantTitle
|
||||
{...currentTitle}
|
||||
isDisabled={isDisabled}
|
||||
docLinks={docLinks}
|
||||
selectedConversation={currentConversation}
|
||||
title={title}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
|
|
|
@ -12,20 +12,18 @@ import { TestProviders } from '../../mock/test_providers/test_providers';
|
|||
|
||||
const testProps = {
|
||||
title: 'Test Title',
|
||||
titleIcon: 'globe',
|
||||
docLinks: { ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', DOC_LINK_VERSION: '7.15' },
|
||||
selectedConversation: undefined,
|
||||
};
|
||||
|
||||
describe('AssistantTitle', () => {
|
||||
it('the component renders correctly with valid props', () => {
|
||||
const { getByText, container } = render(
|
||||
const { getByText } = render(
|
||||
<TestProviders>
|
||||
<AssistantTitle {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(getByText('Test Title')).toBeInTheDocument();
|
||||
expect(container.querySelector('[data-euiicon-type="globe"]')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('clicking on the popover button opens the popover with the correct link', () => {
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiModalHeaderTitle,
|
||||
EuiPopover,
|
||||
|
@ -19,22 +18,21 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
import * as i18n from '../translations';
|
||||
import type { Conversation } from '../../..';
|
||||
import { ConnectorSelectorInline } from '../../connectorland/connector_selector_inline/connector_selector_inline';
|
||||
import { AssistantAvatar } from '../assistant_avatar/assistant_avatar';
|
||||
|
||||
/**
|
||||
* Renders a header title with an icon, a tooltip button, and a popover with
|
||||
* Renders a header title, a tooltip button, and a popover with
|
||||
* information about the assistant feature and access to documentation.
|
||||
*/
|
||||
export const AssistantTitle: React.FC<{
|
||||
isDisabled?: boolean;
|
||||
title: string | JSX.Element;
|
||||
titleIcon: string;
|
||||
docLinks: Omit<DocLinksStart, 'links'>;
|
||||
selectedConversation: Conversation | undefined;
|
||||
}> = ({ isDisabled = false, title, titleIcon, docLinks, selectedConversation }) => {
|
||||
}> = ({ isDisabled = false, title, docLinks, selectedConversation }) => {
|
||||
const selectedConnectorId = selectedConversation?.apiConfig?.connectorId;
|
||||
|
||||
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks;
|
||||
|
@ -75,13 +73,8 @@ export const AssistantTitle: React.FC<{
|
|||
return (
|
||||
<EuiModalHeaderTitle>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
margin-top: 3px;
|
||||
`}
|
||||
>
|
||||
<EuiIcon data-test-subj="titleIcon" type={titleIcon} size="xl" />
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAvatar data-test-subj="titleIcon" size={'m'} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="none" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -172,13 +172,8 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
},
|
||||
});
|
||||
|
||||
const currentTitle: { title: string | JSX.Element; titleIcon: string } =
|
||||
isWelcomeSetup && blockBotConversation.theme?.title && blockBotConversation.theme?.titleIcon
|
||||
? {
|
||||
title: blockBotConversation.theme?.title,
|
||||
titleIcon: blockBotConversation.theme?.titleIcon,
|
||||
}
|
||||
: { title, titleIcon: 'logoSecurity' };
|
||||
const currentTitle: string | JSX.Element =
|
||||
isWelcomeSetup && blockBotConversation.theme?.title ? blockBotConversation.theme?.title : title;
|
||||
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null);
|
||||
const lastCommentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
@ -426,7 +421,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
{showTitle && (
|
||||
<AssistantHeader
|
||||
currentConversation={currentConversation}
|
||||
currentTitle={currentTitle}
|
||||
defaultConnectorId={defaultConnectorId}
|
||||
defaultProvider={defaultProvider}
|
||||
docLinks={docLinks}
|
||||
|
@ -438,6 +432,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
setSelectedConversationId={setSelectedConversationId}
|
||||
showAnonymizedValues={showAnonymizedValues}
|
||||
title={currentTitle}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import { clearPresentationData, conversationHasNoPresentationData } from './help
|
|||
import * as i18n from '../translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useLoadConnectors } from '../use_load_connectors';
|
||||
import { AssistantAvatar } from '../../assistant/assistant_avatar/assistant_avatar';
|
||||
|
||||
const ConnectorButtonWrapper = styled.div`
|
||||
margin-bottom: 10px;
|
||||
|
@ -186,21 +187,14 @@ export const useConnectorSetup = ({
|
|||
name={i18n.CONNECTOR_SETUP_USER_ASSISTANT}
|
||||
size="l"
|
||||
color="subdued"
|
||||
iconType={conversation?.theme?.assistant?.icon ?? 'logoElastic'}
|
||||
iconType={AssistantAvatar}
|
||||
/>
|
||||
),
|
||||
timestamp: `${i18n.CONNECTOR_SETUP_TIMESTAMP_AT}: ${message.timestamp}`,
|
||||
};
|
||||
return commentProps;
|
||||
}),
|
||||
[
|
||||
assistantName,
|
||||
commentBody,
|
||||
conversation.messages,
|
||||
conversation?.theme?.assistant?.icon,
|
||||
currentMessageIndex,
|
||||
userName,
|
||||
]
|
||||
[assistantName, commentBody, conversation.messages, currentMessageIndex, userName]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -71,6 +71,9 @@ export { useAssistantOverlay } from './impl/assistant/use_assistant_overlay';
|
|||
/** a helper that enriches content returned from a query with action buttons */
|
||||
export { analyzeMarkdown } from './impl/assistant/use_conversation/helpers';
|
||||
|
||||
/** Default Elastic AI Assistant logo, can be removed once included in EUI **/
|
||||
export { AssistantAvatar } from './impl/assistant/assistant_avatar/assistant_avatar';
|
||||
|
||||
export {
|
||||
ELASTIC_AI_ASSISTANT_TITLE,
|
||||
WELCOME_CONVERSATION_TITLE,
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiHeaderLink, EuiToolTip } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useAssistantContext } from '@kbn/elastic-assistant/impl/assistant_context';
|
||||
import { AssistantAvatar } from '@kbn/elastic-assistant';
|
||||
|
||||
const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
|
||||
|
||||
/**
|
||||
* Elastic AI Assistant header link
|
||||
*/
|
||||
export const AssistantHeaderLink = React.memo(() => {
|
||||
const { showAssistantOverlay } = useAssistantContext();
|
||||
|
||||
const keyboardShortcut = isMac ? '⌘ ;' : 'Ctrl ;';
|
||||
|
||||
const tooltipContent = i18n.translate(
|
||||
'xpack.securitySolution.globalHeader.assistantHeaderLinkShortcutTooltip',
|
||||
{
|
||||
values: { keyboardShortcut },
|
||||
defaultMessage: 'Keyboard shortcut {keyboardShortcut}',
|
||||
}
|
||||
);
|
||||
|
||||
const showOverlay = useCallback(
|
||||
() => showAssistantOverlay({ showOverlay: true }),
|
||||
[showAssistantOverlay]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiToolTip content={tooltipContent}>
|
||||
<EuiHeaderLink data-test-subj="assistantHeaderLink" color="primary" onClick={showOverlay}>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AssistantAvatar size="xs" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{i18n.translate('xpack.securitySolution.globalHeader.assistantHeaderLink', {
|
||||
defaultMessage: 'AI Assistant',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiHeaderLink>
|
||||
</EuiToolTip>
|
||||
);
|
||||
});
|
||||
|
||||
AssistantHeaderLink.displayName = 'AssistantHeaderLink';
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useVariationMock } from '../../../common/components/utils.mocks';
|
||||
import { GlobalHeader } from '.';
|
||||
|
@ -26,6 +26,7 @@ import { TimelineId } from '../../../../common/types/timeline';
|
|||
import { createStore } from '../../../common/store';
|
||||
import { kibanaObservable } from '@kbn/timelines-plugin/public/mock';
|
||||
import { sourcererPaths } from '../../../common/containers/sourcerer';
|
||||
import { useAssistantAvailability } from '../../../assistant/use_assistant_availability';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
|
@ -48,6 +49,15 @@ jest.mock('react-reverse-portal', () => ({
|
|||
createHtmlPortalNode: () => ({ unmount: jest.fn() }),
|
||||
}));
|
||||
|
||||
jest.mock('../../../assistant/use_assistant_availability');
|
||||
|
||||
jest.mocked(useAssistantAvailability).mockReturnValue({
|
||||
hasAssistantPrivilege: false,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
});
|
||||
|
||||
describe('global header', () => {
|
||||
const mockSetHeaderActionMenu = jest.fn();
|
||||
const state = {
|
||||
|
@ -172,4 +182,46 @@ describe('global header', () => {
|
|||
|
||||
expect(queryByTestId('sourcerer-trigger')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows AI Assistant header link if user has necessary privileges', () => {
|
||||
(useLocation as jest.Mock).mockReturnValue([
|
||||
{ pageName: SecurityPageName.overview, detailName: undefined },
|
||||
]);
|
||||
|
||||
jest.mocked(useAssistantAvailability).mockReturnValue({
|
||||
hasAssistantPrivilege: true,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
});
|
||||
|
||||
const { findByTestId } = render(
|
||||
<TestProviders store={store}>
|
||||
<GlobalHeader setHeaderActionMenu={mockSetHeaderActionMenu} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
waitFor(() => expect(findByTestId('assistantHeaderLink')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('does not show AI Assistant header link if user does not have necessary privileges', () => {
|
||||
(useLocation as jest.Mock).mockReturnValue([
|
||||
{ pageName: SecurityPageName.overview, detailName: undefined },
|
||||
]);
|
||||
|
||||
jest.mocked(useAssistantAvailability).mockReturnValue({
|
||||
hasAssistantPrivilege: false,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
});
|
||||
|
||||
const { findByTestId } = render(
|
||||
<TestProviders store={store}>
|
||||
<GlobalHeader setHeaderActionMenu={mockSetHeaderActionMenu} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
waitFor(() => expect(findByTestId('assistantHeaderLink')).not.toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,13 +27,15 @@ import { timelineSelectors } from '../../../timelines/store/timeline';
|
|||
import { useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { getScopeFromPath, showSourcererByPath } from '../../../common/containers/sourcerer';
|
||||
import { useAddIntegrationsUrl } from '../../../common/hooks/use_add_integrations_url';
|
||||
import { AssistantHeaderLink } from './assistant_header_link';
|
||||
import { useAssistantAvailability } from '../../../assistant/use_assistant_availability';
|
||||
|
||||
const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.buttonAddData', {
|
||||
defaultMessage: 'Add integrations',
|
||||
});
|
||||
|
||||
/**
|
||||
* This component uses the reverse portal to add the Add Data and ML job settings buttons on the
|
||||
* This component uses the reverse portal to add the Add Data, ML job settings, and AI Assistant buttons on the
|
||||
* right hand side of the Kibana global header
|
||||
*/
|
||||
export const GlobalHeader = React.memo(
|
||||
|
@ -52,6 +54,8 @@ export const GlobalHeader = React.memo(
|
|||
|
||||
const { href, onClick } = useAddIntegrationsUrl();
|
||||
|
||||
const { hasAssistantPrivilege } = useAssistantAvailability();
|
||||
|
||||
useEffect(() => {
|
||||
setHeaderActionMenu((element) => {
|
||||
const mount = toMountPoint(<OutPortal node={portalNode} />, { theme$: theme.theme$ });
|
||||
|
@ -87,6 +91,7 @@ export const GlobalHeader = React.memo(
|
|||
{showSourcerer && !showTimeline && (
|
||||
<Sourcerer scope={sourcererScope} data-test-subj="sourcerer" />
|
||||
)}
|
||||
{hasAssistantPrivilege && <AssistantHeaderLink />}
|
||||
</EuiHeaderLinks>
|
||||
</EuiHeaderSectionItem>
|
||||
</EuiHeaderSection>
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { Conversation } from '@kbn/elastic-assistant';
|
|||
import { EuiAvatar, EuiMarkdownFormat, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { AssistantAvatar } from '@kbn/elastic-assistant';
|
||||
import { CommentActions } from '../comment_actions';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -57,7 +58,7 @@ export const getComments = ({
|
|||
timelineAvatar: isUser ? (
|
||||
<EuiAvatar name="user" size="l" color="subdued" iconType="userAvatar" />
|
||||
) : (
|
||||
<EuiAvatar name="machine" size="l" color="subdued" iconType="logoSecurity" />
|
||||
<EuiAvatar name="machine" size="l" color="subdued" iconType={AssistantAvatar} />
|
||||
),
|
||||
timestamp: i18n.AT(
|
||||
message.timestamp.length === 0 ? new Date().toLocaleString() : message.timestamp
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue