mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
# Backport This will backport the following commits from `main` to `8.10`: - [[Security Solution] Fixes Assistant Connector and Actions RBAC Flow (#164382)](https://github.com/elastic/kibana/pull/164382) <!--- 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-23T21:22:39Z","message":"[Security Solution] Fixes Assistant Connector and Actions RBAC Flow (#164382)\n\n## Summary\r\n\r\nResolves https://github.com/elastic/kibana/issues/159374 by ensuring\r\nthat if a user doesn't have the appropriate `Connectors & Actions`\r\nprivileges, they will be shown the appropriate messaging and any UI\r\ncontrols for adding Connectors will be disabled or unavailable.\r\n\r\n#### Connectors and Actions `NONE` or Connectors and Actions `READ` if\r\n*NO* existing connectors exist:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"d9535ae9
-a31e-499b-9b18-6004e3db64de\"\r\n/>\r\n</p> \r\n\r\n#### Connectors and Actions `READ` if existing connector count > 0:\r\n\r\n`Add Connector...` option isn't available:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"bd6a06a7
-ffa2-4cfc-a2b7-844da99cb171\"\r\n/>\r\n</p> \r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"4681086e
-1015-45b9-9afb-ff604c52cd38\"\r\n/>\r\n</p> \r\n\r\n\r\n\r\nAlso addresses:\r\n\r\n* Fixes disabled state of header connector selector for setup flows.\r\n* Adds `AssistantAvailability` interface to `AssistantContext` for\r\nexposing ui feature controls like `Connectors & Actions` privileges.\r\n* Hides `Add new connector...` option if user doesn't have `ALL`\r\n`Connectors & Actions` privileges.\r\n* Hoists dependencies from `assistant/index.tsx` to `connector_setup` as\r\nit was already fetching dependencies from `useAssistantContext`.\r\n\r\nNote: `ConnectorButton` and `ConnectorMissingCallout` should probably be\r\ncombined into a single component and show appropriate messaging given\r\nthe user's `Connectors & Actions` privileges. I kept them separate for\r\nnow as to not modify the control flow around the two components (till we\r\ncan further refactor `assistant/index.tsx`), which means the missing\r\nconnector callout is sort of doing double duty at the moment.\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- [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":"db7ac1bb417a4c84d29e1d7e9e831bdaf650358c","branchLabelMapping":{"^v8.11.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team: SecuritySolution","Feature:Elastic AI Assistant","v8.10.0","v8.11.0"],"number":164382,"url":"https://github.com/elastic/kibana/pull/164382","mergeCommit":{"message":"[Security Solution] Fixes Assistant Connector and Actions RBAC Flow (#164382)\n\n## Summary\r\n\r\nResolves https://github.com/elastic/kibana/issues/159374 by ensuring\r\nthat if a user doesn't have the appropriate `Connectors & Actions`\r\nprivileges, they will be shown the appropriate messaging and any UI\r\ncontrols for adding Connectors will be disabled or unavailable.\r\n\r\n#### Connectors and Actions `NONE` or Connectors and Actions `READ` if\r\n*NO* existing connectors exist:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"d9535ae9
-a31e-499b-9b18-6004e3db64de\"\r\n/>\r\n</p> \r\n\r\n#### Connectors and Actions `READ` if existing connector count > 0:\r\n\r\n`Add Connector...` option isn't available:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"bd6a06a7
-ffa2-4cfc-a2b7-844da99cb171\"\r\n/>\r\n</p> \r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"4681086e
-1015-45b9-9afb-ff604c52cd38\"\r\n/>\r\n</p> \r\n\r\n\r\n\r\nAlso addresses:\r\n\r\n* Fixes disabled state of header connector selector for setup flows.\r\n* Adds `AssistantAvailability` interface to `AssistantContext` for\r\nexposing ui feature controls like `Connectors & Actions` privileges.\r\n* Hides `Add new connector...` option if user doesn't have `ALL`\r\n`Connectors & Actions` privileges.\r\n* Hoists dependencies from `assistant/index.tsx` to `connector_setup` as\r\nit was already fetching dependencies from `useAssistantContext`.\r\n\r\nNote: `ConnectorButton` and `ConnectorMissingCallout` should probably be\r\ncombined into a single component and show appropriate messaging given\r\nthe user's `Connectors & Actions` privileges. I kept them separate for\r\nnow as to not modify the control flow around the two components (till we\r\ncan further refactor `assistant/index.tsx`), which means the missing\r\nconnector callout is sort of doing double duty at the moment.\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- [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":"db7ac1bb417a4c84d29e1d7e9e831bdaf650358c"}},"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/164382","number":164382,"mergeCommit":{"message":"[Security Solution] Fixes Assistant Connector and Actions RBAC Flow (#164382)\n\n## Summary\r\n\r\nResolves https://github.com/elastic/kibana/issues/159374 by ensuring\r\nthat if a user doesn't have the appropriate `Connectors & Actions`\r\nprivileges, they will be shown the appropriate messaging and any UI\r\ncontrols for adding Connectors will be disabled or unavailable.\r\n\r\n#### Connectors and Actions `NONE` or Connectors and Actions `READ` if\r\n*NO* existing connectors exist:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"d9535ae9
-a31e-499b-9b18-6004e3db64de\"\r\n/>\r\n</p> \r\n\r\n#### Connectors and Actions `READ` if existing connector count > 0:\r\n\r\n`Add Connector...` option isn't available:\r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"bd6a06a7
-ffa2-4cfc-a2b7-844da99cb171\"\r\n/>\r\n</p> \r\n\r\n<p align=\"center\">\r\n<img width=\"500\"\r\nsrc=\"4681086e
-1015-45b9-9afb-ff604c52cd38\"\r\n/>\r\n</p> \r\n\r\n\r\n\r\nAlso addresses:\r\n\r\n* Fixes disabled state of header connector selector for setup flows.\r\n* Adds `AssistantAvailability` interface to `AssistantContext` for\r\nexposing ui feature controls like `Connectors & Actions` privileges.\r\n* Hides `Add new connector...` option if user doesn't have `ALL`\r\n`Connectors & Actions` privileges.\r\n* Hoists dependencies from `assistant/index.tsx` to `connector_setup` as\r\nit was already fetching dependencies from `useAssistantContext`.\r\n\r\nNote: `ConnectorButton` and `ConnectorMissingCallout` should probably be\r\ncombined into a single component and show appropriate messaging given\r\nthe user's `Connectors & Actions` privileges. I kept them separate for\r\nnow as to not modify the control flow around the two components (till we\r\ncan further refactor `assistant/index.tsx`), which means the missing\r\nconnector callout is sort of doing double duty at the moment.\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- [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":"db7ac1bb417a4c84d29e1d7e9e831bdaf650358c"}}]}] BACKPORT--> Co-authored-by: Garrett Spong <spong@users.noreply.github.com>
This commit is contained in:
parent
ea9ec1fdb0
commit
c94b0f883b
24 changed files with 290 additions and 89 deletions
|
@ -82,6 +82,7 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<AssistantTitle
|
||||
{...currentTitle}
|
||||
isDisabled={isDisabled}
|
||||
docLinks={docLinks}
|
||||
selectedConversation={currentConversation}
|
||||
/>
|
||||
|
|
|
@ -29,11 +29,12 @@ import { ConnectorSelectorInline } from '../../connectorland/connector_selector_
|
|||
* 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;
|
||||
}> = ({ title, titleIcon, docLinks, selectedConversation }) => {
|
||||
}> = ({ isDisabled = false, title, titleIcon, docLinks, selectedConversation }) => {
|
||||
const selectedConnectorId = selectedConversation?.apiConfig?.connectorId;
|
||||
|
||||
const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks;
|
||||
|
@ -116,7 +117,7 @@ export const AssistantTitle: React.FC<{
|
|||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ConnectorSelectorInline
|
||||
isDisabled={selectedConversation === undefined}
|
||||
isDisabled={isDisabled || selectedConversation === undefined}
|
||||
onConnectorModalVisibilityChange={() => {}}
|
||||
onConnectorSelectionChange={() => {}}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
|
|
|
@ -71,7 +71,6 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
setConversationId,
|
||||
}) => {
|
||||
const {
|
||||
actionTypeRegistry,
|
||||
assistantTelemetry,
|
||||
augmentMessageCodeBlocks,
|
||||
conversations,
|
||||
|
@ -98,11 +97,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
const { createConversation } = useConversation();
|
||||
|
||||
// Connector details
|
||||
const {
|
||||
data: connectors,
|
||||
isSuccess: areConnectorsFetched,
|
||||
refetch: refetchConnectors,
|
||||
} = useLoadConnectors({ http });
|
||||
const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ http });
|
||||
const defaultConnectorId = useMemo(() => getDefaultConnector(connectors)?.id, [connectors]);
|
||||
const defaultProvider = useMemo(
|
||||
() =>
|
||||
|
@ -171,14 +166,10 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
}, [areConnectorsFetched, connectors?.length, currentConversation, setLastConversationId]);
|
||||
|
||||
const { comments: connectorComments, prompt: connectorPrompt } = useConnectorSetup({
|
||||
actionTypeRegistry,
|
||||
http,
|
||||
refetchConnectors,
|
||||
conversation: blockBotConversation,
|
||||
onSetupComplete: () => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: 'auto' });
|
||||
},
|
||||
conversation: blockBotConversation,
|
||||
isConnectorConfigured: !!connectors?.length,
|
||||
});
|
||||
|
||||
const currentTitle: { title: string | JSX.Element; titleIcon: string } =
|
||||
|
@ -475,6 +466,7 @@ const AssistantComponent: React.FC<Props> = ({
|
|||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={connectors?.length > 0}
|
||||
isSettingsModalVisible={isSettingsModalVisible}
|
||||
setIsSettingsModalVisible={setIsSettingsModalVisible}
|
||||
/>
|
||||
|
|
|
@ -11,15 +11,23 @@ import React from 'react';
|
|||
import { AssistantProvider, useAssistantContext } from '.';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock';
|
||||
import { AssistantAvailability } from '../..';
|
||||
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const mockGetInitialConversations = jest.fn(() => ({}));
|
||||
const mockGetComments = jest.fn(() => []);
|
||||
const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' });
|
||||
const mockAssistantAvailability: AssistantAvailability = {
|
||||
hasAssistantPrivilege: false,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
};
|
||||
|
||||
const ContextWrapper: React.FC = ({ children }) => (
|
||||
<AssistantProvider
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
assistantAvailability={mockAssistantAvailability}
|
||||
augmentMessageCodeBlocks={jest.fn()}
|
||||
baseAllow={[]}
|
||||
baseAllowReplacement={[]}
|
||||
|
|
|
@ -33,7 +33,7 @@ import {
|
|||
SYSTEM_PROMPT_LOCAL_STORAGE_KEY,
|
||||
} from './constants';
|
||||
import { CONVERSATIONS_TAB, SettingsTabs } from '../assistant/settings/assistant_settings';
|
||||
import { AssistantTelemetry } from './types';
|
||||
import { AssistantAvailability, AssistantTelemetry } from './types';
|
||||
|
||||
export interface ShowAssistantOverlayProps {
|
||||
showOverlay: boolean;
|
||||
|
@ -48,6 +48,7 @@ type ShowAssistantOverlay = ({
|
|||
}: ShowAssistantOverlayProps) => void;
|
||||
export interface AssistantProviderProps {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
assistantAvailability: AssistantAvailability;
|
||||
assistantTelemetry?: AssistantTelemetry;
|
||||
augmentMessageCodeBlocks: (currentConversation: Conversation) => CodeBlockDetails[][];
|
||||
baseAllow: string[];
|
||||
|
@ -79,6 +80,7 @@ export interface AssistantProviderProps {
|
|||
|
||||
export interface UseAssistantContext {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
assistantAvailability: AssistantAvailability;
|
||||
assistantTelemetry?: AssistantTelemetry;
|
||||
augmentMessageCodeBlocks: (currentConversation: Conversation) => CodeBlockDetails[][];
|
||||
allQuickPrompts: QuickPrompt[];
|
||||
|
@ -126,6 +128,7 @@ const AssistantContext = React.createContext<UseAssistantContext | undefined>(un
|
|||
|
||||
export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
||||
actionTypeRegistry,
|
||||
assistantAvailability,
|
||||
assistantTelemetry,
|
||||
augmentMessageCodeBlocks,
|
||||
baseAllow,
|
||||
|
@ -244,6 +247,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
const value = useMemo(
|
||||
() => ({
|
||||
actionTypeRegistry,
|
||||
assistantAvailability,
|
||||
assistantTelemetry,
|
||||
augmentMessageCodeBlocks,
|
||||
allQuickPrompts: localStorageQuickPrompts ?? [],
|
||||
|
@ -279,6 +283,7 @@ export const AssistantProvider: React.FC<AssistantProviderProps> = ({
|
|||
}),
|
||||
[
|
||||
actionTypeRegistry,
|
||||
assistantAvailability,
|
||||
assistantTelemetry,
|
||||
augmentMessageCodeBlocks,
|
||||
baseAllow,
|
||||
|
|
|
@ -62,3 +62,14 @@ export interface AssistantTelemetry {
|
|||
reportAssistantMessageSent: (params: { conversationId: string; role: string }) => void;
|
||||
reportAssistantQuickPrompt: (params: { conversationId: string; promptTitle: string }) => void;
|
||||
}
|
||||
|
||||
export interface AssistantAvailability {
|
||||
// True when user is Enterprise, or Security Complete PLI for serverless. When false, the Assistant is disabled and unavailable
|
||||
isAssistantEnabled: boolean;
|
||||
// When true, the Assistant is hidden and unavailable
|
||||
hasAssistantPrivilege: boolean;
|
||||
// When true, user has `All` privilege for `Connectors and Actions` (show/execute/delete/save ui capabilities)
|
||||
hasConnectorsAllPrivilege: boolean;
|
||||
// When true, user has `Read` privilege for `Connectors and Actions` (show/execute ui capabilities)
|
||||
hasConnectorsReadPrivilege: boolean;
|
||||
}
|
||||
|
|
|
@ -5,32 +5,46 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
|
||||
|
||||
import { GenAiLogo } from '@kbn/stack-connectors-plugin/public/common';
|
||||
import * as i18n from '../translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
|
||||
export interface ConnectorButtonProps {
|
||||
setIsConnectorModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setIsConnectorModalVisible?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple button component for adding a connector. Note: component is basic and does not handle connector
|
||||
* add logic. Must pass in `setIsConnectorModalVisible`, see ConnectorSetup component if wanting to manage
|
||||
* connector add logic.
|
||||
* add logic. See ConnectorSetup component if wanting to manage connector add logic.
|
||||
*/
|
||||
export const ConnectorButton: React.FC<ConnectorButtonProps> = React.memo<ConnectorButtonProps>(
|
||||
({ setIsConnectorModalVisible }) => {
|
||||
const { assistantAvailability } = useAssistantContext();
|
||||
|
||||
const title = assistantAvailability.hasConnectorsAllPrivilege
|
||||
? i18n.ADD_CONNECTOR_TITLE
|
||||
: i18n.ADD_CONNECTOR_MISSING_PRIVILEGES_TITLE;
|
||||
const description = assistantAvailability.hasConnectorsAllPrivilege
|
||||
? i18n.ADD_CONNECTOR_DESCRIPTION
|
||||
: i18n.ADD_CONNECTOR_MISSING_PRIVILEGES_DESCRIPTION;
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
setIsConnectorModalVisible?.(true);
|
||||
}, [setIsConnectorModalVisible]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="l" justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCard
|
||||
data-test-subj="connectorButton"
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon size="xl" type={GenAiLogo} />}
|
||||
title={i18n.ADD_CONNECTOR_TITLE}
|
||||
description={i18n.ADD_CONNECTOR_DESCRIPTION}
|
||||
onClick={() => setIsConnectorModalVisible(true)}
|
||||
title={title}
|
||||
description={description}
|
||||
onClick={assistantAvailability.hasConnectorsAllPrivilege ? onClick : undefined}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -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 from 'react';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { ConnectorMissingCallout } from '.';
|
||||
import { AssistantAvailability } from '../../..';
|
||||
import { TestProviders } from '../../mock/test_providers/test_providers';
|
||||
|
||||
describe('connectorMissingCallout', () => {
|
||||
describe('when connectors and actions privileges', () => {
|
||||
describe('are `READ`', () => {
|
||||
const assistantAvailability: AssistantAvailability = {
|
||||
hasAssistantPrivilege: true,
|
||||
hasConnectorsAllPrivilege: false,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
};
|
||||
|
||||
it('should show connector privileges required button if no connectors exist', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders assistantAvailability={assistantAvailability}>
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={false}
|
||||
isSettingsModalVisible={false}
|
||||
setIsSettingsModalVisible={jest.fn()}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByTestId('connectorButton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show connector privileges required button if at least one connector exists', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders assistantAvailability={assistantAvailability}>
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={true}
|
||||
isSettingsModalVisible={false}
|
||||
setIsSettingsModalVisible={jest.fn()}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByTestId('connectorButton')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('are `NONE`', () => {
|
||||
const assistantAvailability: AssistantAvailability = {
|
||||
hasAssistantPrivilege: true,
|
||||
hasConnectorsAllPrivilege: false,
|
||||
hasConnectorsReadPrivilege: false,
|
||||
isAssistantEnabled: true,
|
||||
};
|
||||
|
||||
it('should show connector privileges required button', async () => {
|
||||
const { queryByTestId } = render(
|
||||
<TestProviders assistantAvailability={assistantAvailability}>
|
||||
<ConnectorMissingCallout
|
||||
isConnectorConfigured={true}
|
||||
isSettingsModalVisible={false}
|
||||
setIsSettingsModalVisible={jest.fn()}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(queryByTestId('connectorButton')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,22 +12,24 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import * as i18n from '../translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { CONVERSATIONS_TAB } from '../../assistant/settings/assistant_settings';
|
||||
import { ConnectorButton } from '../connector_button';
|
||||
|
||||
interface Props {
|
||||
isConnectorConfigured: boolean;
|
||||
isSettingsModalVisible: boolean;
|
||||
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error callout to be displayed when there is no connector configured for a conversation. Includes deep-link
|
||||
* to conversation settings to quickly resolve.
|
||||
* to conversation settings to quickly resolve. Falls back to <ConnectorButton /> connector if privileges aren't met.
|
||||
*
|
||||
* TODO: Add 'quick fix' button to just pick a connector
|
||||
* TODO: Add setting for 'default connector' so we can auto-resolve and not even show this
|
||||
*/
|
||||
export const ConnectorMissingCallout: React.FC<Props> = React.memo(
|
||||
({ isSettingsModalVisible, setIsSettingsModalVisible }) => {
|
||||
const { setSelectedSettingsTab } = useAssistantContext();
|
||||
({ isConnectorConfigured, isSettingsModalVisible, setIsSettingsModalVisible }) => {
|
||||
const { assistantAvailability, setSelectedSettingsTab } = useAssistantContext();
|
||||
|
||||
const onConversationSettingsClicked = useCallback(() => {
|
||||
if (!isSettingsModalVisible) {
|
||||
|
@ -36,28 +38,40 @@ export const ConnectorMissingCallout: React.FC<Props> = React.memo(
|
|||
}
|
||||
}, [isSettingsModalVisible, setIsSettingsModalVisible, setSelectedSettingsTab]);
|
||||
|
||||
// Show missing callout if user has all privileges or read privileges and at least 1 connector configured
|
||||
const showMissingCallout =
|
||||
assistantAvailability.hasConnectorsAllPrivilege ||
|
||||
(assistantAvailability.hasConnectorsReadPrivilege && isConnectorConfigured);
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="controlsVertical"
|
||||
size="m"
|
||||
title={i18n.MISSING_CONNECTOR_CALLOUT_TITLE}
|
||||
>
|
||||
<p>
|
||||
{' '}
|
||||
<FormattedMessage
|
||||
defaultMessage="Select a connector above or from the {link} to continue"
|
||||
id="xpack.elasticAssistant.assistant.connectors.connectorMissingCallout.calloutDescription"
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink onClick={onConversationSettingsClicked}>
|
||||
{i18n.MISSING_CONNECTOR_CONVERSATION_SETTINGS_LINK}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<>
|
||||
{showMissingCallout ? (
|
||||
<EuiCallOut
|
||||
data-test-subj="connectorMissingCallout"
|
||||
color="danger"
|
||||
iconType="controlsVertical"
|
||||
size="m"
|
||||
title={i18n.MISSING_CONNECTOR_CALLOUT_TITLE}
|
||||
>
|
||||
<p>
|
||||
{' '}
|
||||
<FormattedMessage
|
||||
defaultMessage="Select a connector above or from the {link} to continue"
|
||||
id="xpack.elasticAssistant.assistant.connectors.connectorMissingCallout.calloutDescription"
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink onClick={onConversationSettingsClicked}>
|
||||
{i18n.MISSING_CONNECTOR_CONVERSATION_SETTINGS_LINK}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
) : (
|
||||
<ConnectorButton />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
import { useLoadConnectors } from '../use_load_connectors';
|
||||
import * as i18n from '../translations';
|
||||
import { useLoadActionTypes } from '../use_load_action_types';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
|
||||
export const ADD_NEW_CONNECTOR = 'ADD_NEW_CONNECTOR';
|
||||
interface Props {
|
||||
|
@ -47,6 +48,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
selectedConnectorId,
|
||||
onConnectorSelectionChange,
|
||||
}) => {
|
||||
const { assistantAvailability } = useAssistantContext();
|
||||
// Connector Modal State
|
||||
const [isConnectorModalVisible, setIsConnectorModalVisible] = useState<boolean>(false);
|
||||
const { data: actionTypes } = useLoadActionTypes({ http });
|
||||
|
@ -68,6 +70,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
refetch: refetchConnectors,
|
||||
} = useLoadConnectors({ http });
|
||||
const isLoading = isLoadingActionTypes || isFetchingActionTypes;
|
||||
const localIsDisabled = isDisabled || !assistantAvailability.hasConnectorsReadPrivilege;
|
||||
|
||||
const addNewConnectorOption = useMemo(() => {
|
||||
return {
|
||||
|
@ -113,6 +116,15 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
);
|
||||
}, [connectors]);
|
||||
|
||||
// Only include add new connector option if user has privilege
|
||||
const allConnectorOptions = useMemo(
|
||||
() =>
|
||||
assistantAvailability.hasConnectorsAllPrivilege
|
||||
? [...connectorOptions, addNewConnectorOption]
|
||||
: [...connectorOptions],
|
||||
[addNewConnectorOption, assistantAvailability.hasConnectorsAllPrivilege, connectorOptions]
|
||||
);
|
||||
|
||||
const cleanupAndCloseModal = useCallback(() => {
|
||||
onConnectorModalVisibilityChange?.(false);
|
||||
setIsConnectorModalVisible(false);
|
||||
|
@ -139,11 +151,11 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
<EuiSuperSelect
|
||||
aria-label={i18n.CONNECTOR_SELECTOR_TITLE}
|
||||
compressed={true}
|
||||
disabled={isDisabled}
|
||||
disabled={localIsDisabled}
|
||||
hasDividers={true}
|
||||
isLoading={isLoading}
|
||||
onChange={onChange}
|
||||
options={[...connectorOptions, addNewConnectorOption]}
|
||||
options={allConnectorOptions}
|
||||
valueOfSelected={selectedConnectorId ?? ''}
|
||||
/>
|
||||
{isConnectorModalVisible && (
|
||||
|
|
|
@ -85,7 +85,7 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
onConnectorSelectionChange,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const { actionTypeRegistry, http } = useAssistantContext();
|
||||
const { actionTypeRegistry, assistantAvailability, http } = useAssistantContext();
|
||||
const { setApiConfig } = useConversation();
|
||||
// Connector Modal State
|
||||
const [isConnectorModalVisible, setIsConnectorModalVisible] = useState<boolean>(false);
|
||||
|
@ -111,6 +111,7 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
const selectedConnectorName =
|
||||
connectors?.find((c) => c.id === selectedConnectorId)?.name ??
|
||||
i18n.INLINE_CONNECTOR_PLACEHOLDER;
|
||||
const localIsDisabled = isDisabled || !assistantAvailability.hasConnectorsReadPrivilege;
|
||||
|
||||
const addNewConnectorOption = useMemo(() => {
|
||||
return {
|
||||
|
@ -160,6 +161,15 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
);
|
||||
}, [connectors]);
|
||||
|
||||
// Only include add new connector option if user has privilege
|
||||
const allConnectorOptions = useMemo(
|
||||
() =>
|
||||
assistantAvailability.hasConnectorsAllPrivilege
|
||||
? [...connectorOptions, addNewConnectorOption]
|
||||
: [...connectorOptions],
|
||||
[addNewConnectorOption, assistantAvailability.hasConnectorsAllPrivilege, connectorOptions]
|
||||
);
|
||||
|
||||
const cleanupAndCloseModal = useCallback(() => {
|
||||
onConnectorModalVisibilityChange?.(false);
|
||||
setIsConnectorModalVisible(false);
|
||||
|
@ -236,13 +246,13 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
<EuiSuperSelect
|
||||
aria-label={i18n.CONNECTOR_SELECTOR_TITLE}
|
||||
compressed={true}
|
||||
disabled={isDisabled}
|
||||
disabled={localIsDisabled}
|
||||
hasDividers={true}
|
||||
isLoading={isLoading}
|
||||
isOpen={isOpen}
|
||||
onBlur={handleOnBlur}
|
||||
onChange={onChange}
|
||||
options={[...connectorOptions, addNewConnectorOption]}
|
||||
options={allConnectorOptions}
|
||||
placeholder={placeholderComponent}
|
||||
valueOfSelected={selectedConnectorId}
|
||||
/>
|
||||
|
@ -254,7 +264,7 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
data-test-subj="connectorSelectorPlaceholderButton"
|
||||
iconSide={'right'}
|
||||
iconType="arrowDown"
|
||||
isDisabled={isDisabled}
|
||||
isDisabled={localIsDisabled}
|
||||
onClick={onConnectorClick}
|
||||
size="xs"
|
||||
>
|
||||
|
|
|
@ -13,8 +13,7 @@ import styled from 'styled-components';
|
|||
import { ConnectorAddModal } from '@kbn/triggers-actions-ui-plugin/public/common/constants';
|
||||
import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { ActionType, ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { ActionType } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import {
|
||||
GEN_AI_CONNECTOR_ID,
|
||||
OpenAiProviderType,
|
||||
|
@ -29,6 +28,7 @@ import { useConversation } from '../../assistant/use_conversation';
|
|||
import { clearPresentationData, conversationHasNoPresentationData } from './helpers';
|
||||
import * as i18n from '../translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useLoadConnectors } from '../use_load_connectors';
|
||||
|
||||
const ConnectorButtonWrapper = styled.div`
|
||||
margin-bottom: 10px;
|
||||
|
@ -43,21 +43,13 @@ interface Config {
|
|||
}
|
||||
|
||||
export interface ConnectorSetupProps {
|
||||
isConnectorConfigured: boolean;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
conversation?: Conversation;
|
||||
http: HttpSetup;
|
||||
onSetupComplete?: () => void;
|
||||
refetchConnectors?: () => void;
|
||||
}
|
||||
|
||||
export const useConnectorSetup = ({
|
||||
actionTypeRegistry,
|
||||
conversation = WELCOME_CONVERSATION,
|
||||
http,
|
||||
isConnectorConfigured = false,
|
||||
onSetupComplete,
|
||||
refetchConnectors,
|
||||
}: ConnectorSetupProps): {
|
||||
comments: EuiCommentProps[];
|
||||
prompt: React.ReactElement;
|
||||
|
@ -65,7 +57,13 @@ export const useConnectorSetup = ({
|
|||
const { appendMessage, setApiConfig, setConversation } = useConversation();
|
||||
const bottomRef = useRef<HTMLDivElement | null>(null);
|
||||
// Access all conversations so we can add connector to all on initial setup
|
||||
const { conversations } = useAssistantContext();
|
||||
const { actionTypeRegistry, conversations, http } = useAssistantContext();
|
||||
const {
|
||||
data: connectors,
|
||||
isSuccess: areConnectorsFetched,
|
||||
refetch: refetchConnectors,
|
||||
} = useLoadConnectors({ http });
|
||||
const isConnectorConfigured = areConnectorsFetched && !!connectors?.length;
|
||||
|
||||
const [isConnectorModalVisible, setIsConnectorModalVisible] = useState<boolean>(false);
|
||||
const [showAddConnectorButton, setShowAddConnectorButton] = useState<boolean>(() => {
|
||||
|
|
|
@ -73,17 +73,17 @@ export const ADD_CONNECTOR_DESCRIPTION = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_ADDED_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.addConnectorButton.connectorAddedTitle',
|
||||
export const ADD_CONNECTOR_MISSING_PRIVILEGES_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.addConnectorButton.missingPrivilegesTitle',
|
||||
{
|
||||
defaultMessage: 'Generative AI Connector added!',
|
||||
defaultMessage: 'Generative AI Connector Required',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_ADDED_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.addConnectorButton.connectorAddedDescription',
|
||||
export const ADD_CONNECTOR_MISSING_PRIVILEGES_DESCRIPTION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.addConnectorButton.missingPrivilegesDescription',
|
||||
{
|
||||
defaultMessage: 'Ready to continue the conversation...',
|
||||
defaultMessage: 'Please contact your administrator to enable a Generative AI Connector.',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -15,9 +15,10 @@ import { ThemeProvider } from 'styled-components';
|
|||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { AssistantProvider, AssistantProviderProps } from '../../assistant_context';
|
||||
import { Conversation } from '../../assistant_context/types';
|
||||
import { AssistantAvailability, Conversation } from '../../assistant_context/types';
|
||||
|
||||
interface Props {
|
||||
assistantAvailability?: AssistantAvailability;
|
||||
children: React.ReactNode;
|
||||
getInitialConversations?: () => Record<string, Conversation>;
|
||||
providerContext?: Partial<AssistantProviderProps>;
|
||||
|
@ -28,8 +29,16 @@ window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
|||
|
||||
const mockGetInitialConversations = () => ({});
|
||||
|
||||
const mockAssistantAvailability: AssistantAvailability = {
|
||||
hasAssistantPrivilege: false,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
};
|
||||
|
||||
/** A utility for wrapping children in the providers required to run tests */
|
||||
export const TestProvidersComponent: React.FC<Props> = ({
|
||||
assistantAvailability = mockAssistantAvailability,
|
||||
children,
|
||||
getInitialConversations = mockGetInitialConversations,
|
||||
providerContext,
|
||||
|
@ -56,6 +65,7 @@ export const TestProvidersComponent: React.FC<Props> = ({
|
|||
<QueryClientProvider client={queryClient}>
|
||||
<AssistantProvider
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
assistantAvailability={assistantAvailability}
|
||||
augmentMessageCodeBlocks={jest.fn().mockReturnValue([])}
|
||||
baseAllow={[]}
|
||||
baseAllowReplacement={[]}
|
||||
|
|
|
@ -89,8 +89,16 @@ export type {
|
|||
QueryType,
|
||||
} from './impl/assistant/use_conversation/helpers';
|
||||
|
||||
/** serialized conversations */
|
||||
export type { AssistantTelemetry, Conversation, Message } from './impl/assistant_context/types';
|
||||
export type {
|
||||
/** Feature Availability Interface */
|
||||
AssistantAvailability,
|
||||
/** Telemetry Interface */
|
||||
AssistantTelemetry,
|
||||
/** Conversation Interface */
|
||||
Conversation,
|
||||
/** Message Interface */
|
||||
Message,
|
||||
} from './impl/assistant_context/types';
|
||||
|
||||
/** Interface for defining system/user prompts */
|
||||
export type { Prompt } from './impl/assistant/types';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { AssistantProvider } from '@kbn/elastic-assistant';
|
||||
import { AssistantAvailability, AssistantProvider } from '@kbn/elastic-assistant';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { euiDarkVars } from '@kbn/ui-theme';
|
||||
import React from 'react';
|
||||
|
@ -32,11 +32,19 @@ export const TestProvidersComponent: React.FC<Props> = ({ children }) => {
|
|||
reportDataQualityIndexChecked: jest.fn(),
|
||||
reportDataQualityCheckAllCompleted: jest.fn(),
|
||||
};
|
||||
const mockAssistantAvailability: AssistantAvailability = {
|
||||
hasAssistantPrivilege: false,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
|
||||
<AssistantProvider
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
assistantAvailability={mockAssistantAvailability}
|
||||
augmentMessageCodeBlocks={jest.fn()}
|
||||
baseAllow={[]}
|
||||
baseAllowReplacement={[]}
|
||||
|
|
|
@ -42,6 +42,7 @@ import { PROMPT_CONTEXTS } from '../assistant/content/prompt_contexts';
|
|||
import { BASE_SECURITY_QUICK_PROMPTS } from '../assistant/content/quick_prompts';
|
||||
import { BASE_SECURITY_SYSTEM_PROMPTS } from '../assistant/content/prompts/system';
|
||||
import { useAnonymizationStore } from '../assistant/use_anonymization_store';
|
||||
import { useAssistantAvailability } from '../assistant/use_assistant_availability';
|
||||
|
||||
interface StartAppComponent {
|
||||
children: React.ReactNode;
|
||||
|
@ -70,6 +71,7 @@ const StartAppComponent: FC<StartAppComponent> = ({
|
|||
upselling,
|
||||
} = services;
|
||||
|
||||
const assistantAvailability = useAssistantAvailability();
|
||||
const { conversations, setConversations } = useConversationStore();
|
||||
const { defaultAllow, defaultAllowReplacement, setDefaultAllow, setDefaultAllowReplacement } =
|
||||
useAnonymizationStore();
|
||||
|
@ -96,6 +98,7 @@ const StartAppComponent: FC<StartAppComponent> = ({
|
|||
<AssistantProvider
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
augmentMessageCodeBlocks={augmentMessageCodeBlocks}
|
||||
assistantAvailability={assistantAvailability}
|
||||
assistantTelemetry={assistantTelemetry}
|
||||
defaultAllow={defaultAllow}
|
||||
defaultAllowReplacement={defaultAllowReplacement}
|
||||
|
|
|
@ -14,15 +14,31 @@ export interface UseAssistantAvailability {
|
|||
isAssistantEnabled: boolean;
|
||||
// When true, the Assistant is hidden and unavailable
|
||||
hasAssistantPrivilege: boolean;
|
||||
// When true, user has `All` privilege for `Connectors and Actions` (show/execute/delete/save ui capabilities)
|
||||
hasConnectorsAllPrivilege: boolean;
|
||||
// When true, user has `Read` privilege for `Connectors and Actions` (show/execute ui capabilities)
|
||||
hasConnectorsReadPrivilege: boolean;
|
||||
}
|
||||
|
||||
export const useAssistantAvailability = (): UseAssistantAvailability => {
|
||||
const isEnterprise = useLicense().isEnterprise();
|
||||
const capabilities = useKibana().services.application.capabilities;
|
||||
const isAssistantEnabled = capabilities[ASSISTANT_FEATURE_ID]?.['ai-assistant'] === true;
|
||||
const hasAssistantPrivilege = capabilities[ASSISTANT_FEATURE_ID]?.['ai-assistant'] === true;
|
||||
|
||||
// Connectors & Actions capabilities as defined in x-pack/plugins/actions/server/feature.ts
|
||||
// `READ` ui capabilities defined as: { ui: ['show', 'execute'] }
|
||||
const hasConnectorsReadPrivilege =
|
||||
capabilities.actions?.show === true && capabilities.actions?.execute === true;
|
||||
// `ALL` ui capabilities defined as: { ui: ['show', 'execute', 'save', 'delete'] }
|
||||
const hasConnectorsAllPrivilege =
|
||||
hasConnectorsReadPrivilege &&
|
||||
capabilities.actions?.delete === true &&
|
||||
capabilities.actions?.save === true;
|
||||
|
||||
return {
|
||||
hasAssistantPrivilege,
|
||||
hasConnectorsAllPrivilege,
|
||||
hasConnectorsReadPrivilege,
|
||||
isAssistantEnabled: isEnterprise,
|
||||
hasAssistantPrivilege: isAssistantEnabled,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock';
|
||||
import React from 'react';
|
||||
import type { AssistantAvailability } from '@kbn/elastic-assistant';
|
||||
import { AssistantProvider } from '@kbn/elastic-assistant';
|
||||
|
||||
interface Props {
|
||||
|
@ -22,10 +23,17 @@ export const MockAssistantProviderComponent: React.FC<Props> = ({ children }) =>
|
|||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const mockHttp = httpServiceMock.createStartContract({ basePath: '/test' });
|
||||
mockHttp.get.mockResolvedValue([]);
|
||||
const mockAssistantAvailability: AssistantAvailability = {
|
||||
hasAssistantPrivilege: false,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<AssistantProvider
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
assistantAvailability={mockAssistantAvailability}
|
||||
augmentMessageCodeBlocks={jest.fn(() => [])}
|
||||
baseAllow={[]}
|
||||
baseAllowReplacement={[]}
|
||||
|
|
|
@ -28,9 +28,12 @@ describe('useAssistant', () => {
|
|||
let hookResult: RenderHookResult<UseAssistantParams, UseAssistantResult>;
|
||||
|
||||
it(`should return showAssistant true and a value for promptContextId`, () => {
|
||||
jest
|
||||
.mocked(useAssistantAvailability)
|
||||
.mockReturnValue({ hasAssistantPrivilege: true, isAssistantEnabled: true });
|
||||
jest.mocked(useAssistantAvailability).mockReturnValue({
|
||||
hasAssistantPrivilege: true,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
});
|
||||
jest
|
||||
.mocked(useAssistantOverlay)
|
||||
.mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' });
|
||||
|
@ -42,9 +45,12 @@ describe('useAssistant', () => {
|
|||
});
|
||||
|
||||
it(`should return showAssistant false if hasAssistantPrivilege is false`, () => {
|
||||
jest
|
||||
.mocked(useAssistantAvailability)
|
||||
.mockReturnValue({ hasAssistantPrivilege: false, isAssistantEnabled: true });
|
||||
jest.mocked(useAssistantAvailability).mockReturnValue({
|
||||
hasAssistantPrivilege: false,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
});
|
||||
jest
|
||||
.mocked(useAssistantOverlay)
|
||||
.mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' });
|
||||
|
@ -56,9 +62,12 @@ describe('useAssistant', () => {
|
|||
});
|
||||
|
||||
it('returns anonymized prompt context data', async () => {
|
||||
jest
|
||||
.mocked(useAssistantAvailability)
|
||||
.mockReturnValue({ hasAssistantPrivilege: true, isAssistantEnabled: true });
|
||||
jest.mocked(useAssistantAvailability).mockReturnValue({
|
||||
hasAssistantPrivilege: true,
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
isAssistantEnabled: true,
|
||||
});
|
||||
jest
|
||||
.mocked(useAssistantOverlay)
|
||||
.mockReturnValue({ showAssistantOverlay: jest.fn, promptContextId: '123' });
|
||||
|
|
|
@ -114,6 +114,8 @@ describe('Details Panel Component', () => {
|
|||
describe('DetailsPanel: rendering', () => {
|
||||
beforeEach(() => {
|
||||
(useAssistantAvailability as jest.Mock).mockReturnValue({
|
||||
hasConnectorsAllPrivilege: true,
|
||||
hasConnectorsReadPrivilege: true,
|
||||
hasAssistantPrivilege: false,
|
||||
isAssistantEnabled: true,
|
||||
});
|
||||
|
|
|
@ -11938,8 +11938,6 @@
|
|||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableTooltip": "{total} champs dans ce contexte sont disponibles pour être inclus dans la conversation",
|
||||
"xpack.elasticAssistant.assistant.apiErrorTitle": "Une erreur s’est produite lors de l’envoi de votre message. Si le problème persiste, veuillez tester la configuration du connecteur.",
|
||||
"xpack.elasticAssistant.assistant.clearChat": "Effacer le chat",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.connectorAddedDescription": "Prêt à continuer la conversation...",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.connectorAddedTitle": "Connecteur d'intelligence artificielle générative ajouté.",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.description": "Configurez un connecteur pour continuer la conversation",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.title": "Ajouter un connecteur d'intelligence artificielle générative",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.ariaLabel": "Sélecteur de conversation",
|
||||
|
|
|
@ -11952,8 +11952,6 @@
|
|||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableTooltip": "このコンテキストの{total}個のフィールドを会話に含めることができます",
|
||||
"xpack.elasticAssistant.assistant.apiErrorTitle": "メッセージの送信中にエラーが発生しました。問題が解決しない場合は、コネクター構成をテストしてください。",
|
||||
"xpack.elasticAssistant.assistant.clearChat": "チャットを消去",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.connectorAddedDescription": "会話を続行する準備ができました...",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.connectorAddedTitle": "生成AIコネクターが追加されました。",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.description": "会話を続行するようにコネクターを構成",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.title": "生成AIコネクターを追加",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.ariaLabel": "会話セレクター",
|
||||
|
|
|
@ -11952,8 +11952,6 @@
|
|||
"xpack.elasticAssistant.dataAnonymizationEditor.stats.availableStat.availableTooltip": "可在对话中包含此上下文中的 {total} 个字段",
|
||||
"xpack.elasticAssistant.assistant.apiErrorTitle": "发送消息时出错。如果问题持续存在,请测试连接器配置。",
|
||||
"xpack.elasticAssistant.assistant.clearChat": "清除聊天",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.connectorAddedDescription": "准备继续对话……",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.connectorAddedTitle": "已添加生成式 AI 连接器!",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.description": "配置连接器以继续对话",
|
||||
"xpack.elasticAssistant.assistant.connectors.addConnectorButton.title": "添加生成式 AI 连接器",
|
||||
"xpack.elasticAssistant.assistant.connectors.connectorSelector.ariaLabel": "对话选择器",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue