[8.16] [Security GenAI] When a "global" Knowledge Base entry is updated to "private", a duplicate "private" entry gets created and the global entry remains unchanged (#197157) (#197516) (#197918)

# Backport

This will backport the following commits from `main` to `8.16`:
- [[Security GenAI] When a "global" Knowledge Base entry is
updated to "private", a duplicate "private" entry
gets created and the global entry remains unchanged (#197157)
(#197516)](https://github.com/elastic/kibana/pull/197516)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Ievgen
Sorokopud","email":"ievgen.sorokopud@elastic.co"},"sourceCommit":{"committedDate":"2024-10-25T22:45:32Z","message":"[Security
GenAI] When a \"global\" Knowledge Base entry is updated to \"private\",
a duplicate \"private\" entry gets created and the global entry remains
unchanged (#197157) (#197516)\n\n## Summary\r\n\r\nOriginal ticket
describing the
BUG:\r\nhttps://github.com/elastic/kibana/issues/197157\r\n\r\nThese
changes fix two issues:\r\n1. Updating an entry from Global to Private
duplicates it. After\r\ndiscussing with the team we decided that this is
an expected behaviour\r\nand we would add a modal dialog which warns
users about it. See more\r\ndetails
here\r\nhttps://github.com/elastic/kibana/issues/197157#issuecomment-2432592394\r\n2.
Editing Private entry and switching the sharing option twice
from\r\nPrivate => Global => Private causes the issue where we would
treat\r\nselected entry as a new one and thus calling \"create entry\"
instead of\r\n\"update\".\r\n\r\n### Steps to reproduce second
issue:\r\n\r\n* Edit private entry\r\n* Update entry's name\r\n* Switch
sharing option to Global\r\n* Switch sharing option back to Private\r\n*
Save the entry\r\n\r\n**Current behaviour**: a new private entry is
created\r\n**Expected behaviour**: existing private entry is
updated\r\n\r\n### Screen recording of the fixed first
issue\r\n\r\n\r\nhttps://github.com/user-attachments/assets/e11e14bd-c557-401e-a23f-e01ac7aedf30\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] [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":"d17fc09034d69f15748ddb0a49eae78959401c5a","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:
SecuritySolution","backport:prev-minor","Team:Security Generative
AI","v8.16.0"],"title":"[Security GenAI] When a \"global\" Knowledge
Base entry is updated to \"private\", a duplicate \"private\" entry gets
created and the global entry remains unchanged
(#197157)","number":197516,"url":"https://github.com/elastic/kibana/pull/197516","mergeCommit":{"message":"[Security
GenAI] When a \"global\" Knowledge Base entry is updated to \"private\",
a duplicate \"private\" entry gets created and the global entry remains
unchanged (#197157) (#197516)\n\n## Summary\r\n\r\nOriginal ticket
describing the
BUG:\r\nhttps://github.com/elastic/kibana/issues/197157\r\n\r\nThese
changes fix two issues:\r\n1. Updating an entry from Global to Private
duplicates it. After\r\ndiscussing with the team we decided that this is
an expected behaviour\r\nand we would add a modal dialog which warns
users about it. See more\r\ndetails
here\r\nhttps://github.com/elastic/kibana/issues/197157#issuecomment-2432592394\r\n2.
Editing Private entry and switching the sharing option twice
from\r\nPrivate => Global => Private causes the issue where we would
treat\r\nselected entry as a new one and thus calling \"create entry\"
instead of\r\n\"update\".\r\n\r\n### Steps to reproduce second
issue:\r\n\r\n* Edit private entry\r\n* Update entry's name\r\n* Switch
sharing option to Global\r\n* Switch sharing option back to Private\r\n*
Save the entry\r\n\r\n**Current behaviour**: a new private entry is
created\r\n**Expected behaviour**: existing private entry is
updated\r\n\r\n### Screen recording of the fixed first
issue\r\n\r\n\r\nhttps://github.com/user-attachments/assets/e11e14bd-c557-401e-a23f-e01ac7aedf30\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] [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":"d17fc09034d69f15748ddb0a49eae78959401c5a"}},"sourceBranch":"main","suggestedTargetBranches":["8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197516","number":197516,"mergeCommit":{"message":"[Security
GenAI] When a \"global\" Knowledge Base entry is updated to \"private\",
a duplicate \"private\" entry gets created and the global entry remains
unchanged (#197157) (#197516)\n\n## Summary\r\n\r\nOriginal ticket
describing the
BUG:\r\nhttps://github.com/elastic/kibana/issues/197157\r\n\r\nThese
changes fix two issues:\r\n1. Updating an entry from Global to Private
duplicates it. After\r\ndiscussing with the team we decided that this is
an expected behaviour\r\nand we would add a modal dialog which warns
users about it. See more\r\ndetails
here\r\nhttps://github.com/elastic/kibana/issues/197157#issuecomment-2432592394\r\n2.
Editing Private entry and switching the sharing option twice
from\r\nPrivate => Global => Private causes the issue where we would
treat\r\nselected entry as a new one and thus calling \"create entry\"
instead of\r\n\"update\".\r\n\r\n### Steps to reproduce second
issue:\r\n\r\n* Edit private entry\r\n* Update entry's name\r\n* Switch
sharing option to Global\r\n* Switch sharing option back to Private\r\n*
Save the entry\r\n\r\n**Current behaviour**: a new private entry is
created\r\n**Expected behaviour**: existing private entry is
updated\r\n\r\n### Screen recording of the fixed first
issue\r\n\r\n\r\nhttps://github.com/user-attachments/assets/e11e14bd-c557-401e-a23f-e01ac7aedf30\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [ ] [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":"d17fc09034d69f15748ddb0a49eae78959401c5a"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Ievgen Sorokopud <ievgen.sorokopud@elastic.co>
This commit is contained in:
Kibana Machine 2024-10-26 11:26:30 +11:00 committed by GitHub
parent b33a0c4a5a
commit f25e599d25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 280 additions and 36 deletions

View file

@ -14,18 +14,28 @@ import {
EuiIcon,
EuiText,
} from '@elastic/eui';
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import { DocumentEntry } from '@kbn/elastic-assistant-common';
import * as i18n from './translations';
import { isGlobalEntry } from './helpers';
interface Props {
entry?: DocumentEntry;
originalEntry?: DocumentEntry;
setEntry: React.Dispatch<React.SetStateAction<Partial<DocumentEntry>>>;
hasManageGlobalKnowledgeBase: boolean;
}
export const DocumentEntryEditor: React.FC<Props> = React.memo(
({ entry, setEntry, hasManageGlobalKnowledgeBase }) => {
({ entry, setEntry, hasManageGlobalKnowledgeBase, originalEntry }) => {
const privateUsers = useMemo(() => {
const originalUsers = originalEntry?.users;
if (originalEntry && !isGlobalEntry(originalEntry)) {
return originalUsers;
}
return undefined;
}, [originalEntry]);
// Name
const setName = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) =>
@ -38,12 +48,13 @@ export const DocumentEntryEditor: React.FC<Props> = React.memo(
(value: string) =>
setEntry((prevEntry) => ({
...prevEntry,
users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : undefined,
users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : privateUsers,
})),
[setEntry]
[privateUsers, setEntry]
);
const sharingOptions = [
{
'data-test-subj': 'sharing-private-option',
value: i18n.SHARING_PRIVATE_OPTION_LABEL,
inputDisplay: (
<EuiText size={'s'}>
@ -57,6 +68,7 @@ export const DocumentEntryEditor: React.FC<Props> = React.memo(
),
},
{
'data-test-subj': 'sharing-global-option',
value: i18n.SHARING_GLOBAL_OPTION_LABEL,
inputDisplay: (
<EuiText size={'s'}>
@ -111,6 +123,7 @@ export const DocumentEntryEditor: React.FC<Props> = React.memo(
fullWidth
>
<EuiSuperSelect
data-test-subj="sharing-select"
options={sharingOptions}
valueOfSelected={selectedSharingOption}
onChange={setSharingOptions}

View file

@ -35,6 +35,7 @@ const mockContext = {
selectedSettingsTab: null,
assistantAvailability: {
isAssistantEnabled: true,
hasManageGlobalKnowledgeBase: true,
},
};
jest.mock('../../assistant_context');
@ -64,23 +65,67 @@ const wrapper = (props: { children: React.ReactNode }) => (
</I18nProvider>
);
describe('KnowledgeBaseSettingsManagement', () => {
const mockCreateEntry = jest.fn();
const mockUpdateEntry = jest.fn();
const mockDeleteEntry = jest.fn();
const mockData = [
{ id: '1', name: 'Test Entry 1', type: 'document', kbResource: 'user', users: [{ id: 'hi' }] },
{
id: '1',
createdAt: '2024-10-21T18:54:14.773Z',
createdBy: 'u_user_id_1',
updatedAt: '2024-10-23T17:33:15.933Z',
updatedBy: 'u_user_id_1',
users: [{ name: 'Test User 1' }],
name: 'Test Entry 1',
namespace: 'default',
type: 'document',
kbResource: 'user',
source: 'user',
text: 'Very nice text',
},
{
id: '2',
name: 'Test Entry 2',
type: 'index',
kbResource: 'global',
createdAt: '2024-10-25T09:55:56.596Z',
createdBy: 'u_user_id_2',
updatedAt: '2024-10-25T09:55:56.596Z',
updatedBy: 'u_user_id_2',
users: [],
index: 'missing-index',
name: 'Test Entry 2',
namespace: 'default',
type: 'index',
index: 'index-1',
field: 'semantic_field1',
description: 'Test description',
queryDescription: 'Test query instruction',
},
{
id: '3',
createdAt: '2024-10-25T09:55:56.596Z',
createdBy: 'u_user_id_1',
updatedAt: '2024-10-25T09:55:56.596Z',
updatedBy: 'u_user_id_1',
users: [{ name: 'Test User 1' }],
name: 'Test Entry 3',
namespace: 'default',
type: 'index',
kbResource: 'private',
users: [{ id: 'fake-user' }],
index: 'index-2',
field: 'semantic_field2',
description: 'Test description',
queryDescription: 'Test query instruction',
},
{
id: '4',
createdAt: '2024-10-21T18:54:14.773Z',
createdBy: 'u_user_id_3',
updatedAt: '2024-10-23T17:33:15.933Z',
updatedBy: 'u_user_id_3',
users: [],
name: 'Test Entry 4',
namespace: 'default',
type: 'document',
kbResource: 'user',
source: 'user',
text: 'Very nice text',
},
];
@ -114,15 +159,15 @@ describe('KnowledgeBaseSettingsManagement', () => {
closeFlyout: jest.fn(),
});
(useCreateKnowledgeBaseEntry as jest.Mock).mockReturnValue({
mutateAsync: jest.fn(),
mutateAsync: mockCreateEntry,
isLoading: false,
});
(useUpdateKnowledgeBaseEntries as jest.Mock).mockReturnValue({
mutateAsync: jest.fn(),
mutateAsync: mockUpdateEntry,
isLoading: false,
});
(useDeleteKnowledgeBaseEntries as jest.Mock).mockReturnValue({
mutateAsync: jest.fn(),
mutateAsync: mockDeleteEntry,
isLoading: false,
});
});
@ -258,6 +303,124 @@ describe('KnowledgeBaseSettingsManagement', () => {
expect(screen.queryByTestId('delete-entry-confirmation')).not.toBeInTheDocument();
});
it('does not create a duplicate document entry when switching sharing option twice', async () => {
(useFlyoutModalVisibility as jest.Mock).mockReturnValue({
isFlyoutOpen: true,
openFlyout: jest.fn(),
closeFlyout: jest.fn(),
});
render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, {
wrapper,
});
await waitFor(() => {
fireEvent.click(screen.getAllByTestId('edit-button')[0]);
});
expect(screen.getByTestId('flyout')).toBeVisible();
await waitFor(() => {
expect(screen.getByText('Edit document entry')).toBeInTheDocument();
});
const updatedName = 'New Entry Name';
await waitFor(() => {
const nameInput = screen.getByTestId('entryNameInput');
fireEvent.change(nameInput, { target: { value: updatedName } });
});
await waitFor(() => {
fireEvent.click(screen.getByTestId('sharing-select'));
fireEvent.click(screen.getByTestId('sharing-global-option'));
fireEvent.click(screen.getByTestId('sharing-select'));
fireEvent.click(screen.getByTestId('sharing-private-option'));
fireEvent.click(screen.getByTestId('save-button'));
});
await waitFor(() => {
expect(mockUpdateEntry).toHaveBeenCalledTimes(1);
});
expect(mockCreateEntry).toHaveBeenCalledTimes(0);
expect(mockUpdateEntry).toHaveBeenCalledWith([{ ...mockData[0], name: updatedName }]);
});
it('does not create a duplicate index entry when switching sharing option twice', async () => {
(useFlyoutModalVisibility as jest.Mock).mockReturnValue({
isFlyoutOpen: true,
openFlyout: jest.fn(),
closeFlyout: jest.fn(),
});
render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, {
wrapper,
});
await waitFor(() => {
fireEvent.click(screen.getAllByTestId('edit-button')[2]);
});
expect(screen.getByTestId('flyout')).toBeVisible();
await waitFor(() => {
expect(screen.getByText('Edit index entry')).toBeInTheDocument();
});
const updatedName = 'New Entry Name';
await waitFor(() => {
const nameInput = screen.getByTestId('entry-name');
fireEvent.change(nameInput, { target: { value: updatedName } });
});
await waitFor(() => {
fireEvent.click(screen.getByTestId('sharing-select'));
fireEvent.click(screen.getByTestId('sharing-global-option'));
fireEvent.click(screen.getByTestId('sharing-select'));
fireEvent.click(screen.getByTestId('sharing-private-option'));
fireEvent.click(screen.getByTestId('save-button'));
});
await waitFor(() => {
expect(mockUpdateEntry).toHaveBeenCalledTimes(1);
});
expect(mockCreateEntry).toHaveBeenCalledTimes(0);
expect(mockUpdateEntry).toHaveBeenCalledWith([{ ...mockData[2], name: updatedName }]);
});
it('shows duplicate entry modal when making global to private entry update', async () => {
(useFlyoutModalVisibility as jest.Mock).mockReturnValue({
isFlyoutOpen: true,
openFlyout: jest.fn(),
closeFlyout: jest.fn(),
});
render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, {
wrapper,
});
await waitFor(() => {
fireEvent.click(screen.getAllByTestId('edit-button')[3]);
});
expect(screen.getByTestId('flyout')).toBeVisible();
await waitFor(() => {
expect(screen.getByText('Edit document entry')).toBeInTheDocument();
});
await waitFor(() => {
fireEvent.click(screen.getByTestId('sharing-select'));
fireEvent.click(screen.getByTestId('sharing-private-option'));
fireEvent.click(screen.getByTestId('save-button'));
});
expect(screen.getByTestId('create-duplicate-entry-modal')).toBeInTheDocument();
await waitFor(() => {
fireEvent.click(screen.getByTestId('confirmModalConfirmButton'));
});
expect(screen.queryByTestId('create-duplicate-entry-modal')).not.toBeInTheDocument();
await waitFor(() => {
expect(mockCreateEntry).toHaveBeenCalledTimes(1);
});
expect(mockUpdateEntry).toHaveBeenCalledTimes(0);
expect(mockCreateEntry).toHaveBeenCalledWith({ ...mockData[3], users: undefined });
});
it('shows warning icon for index entries with missing indices', async () => {
render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, {
wrapper,

View file

@ -83,6 +83,12 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
const isKbSetup = isKnowledgeBaseSetup(kbStatus);
const [deleteKBItem, setDeleteKBItem] = useState<DocumentEntry | IndexEntry | null>(null);
const [duplicateKBItem, setDuplicateKBItem] = useState<KnowledgeBaseEntryCreateProps | null>(
null
);
const [originalEntry, setOriginalEntry] = useState<DocumentEntry | IndexEntry | undefined>(
undefined
);
// Only needed for legacy settings management
const { knowledgeBase, setUpdatedKnowledgeBaseSettings, resetSettings, saveSettings } =
@ -146,25 +152,6 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
});
const isModifyingEntry = isCreatingEntry || isUpdatingEntries || isDeletingEntries;
// Flyout Save/Cancel Actions
const onSaveConfirmed = useCallback(async () => {
if (isKnowledgeBaseEntryResponse(selectedEntry)) {
await updateEntries([selectedEntry]);
closeFlyout();
} else if (isKnowledgeBaseEntryCreateProps(selectedEntry)) {
await createEntry(selectedEntry);
closeFlyout();
} else if (isKnowledgeBaseEntryCreateProps(selectedEntry)) {
createEntry(selectedEntry);
closeFlyout();
}
}, [closeFlyout, selectedEntry, createEntry, updateEntries]);
const onSaveCancelled = useCallback(() => {
setSelectedEntry(undefined);
closeFlyout();
}, [closeFlyout]);
const {
data: entries,
isFetching: isFetchingEntries,
@ -175,6 +162,27 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
enabled: enableKnowledgeBaseByDefault,
});
// Flyout Save/Cancel Actions
const onSaveConfirmed = useCallback(async () => {
if (isKnowledgeBaseEntryResponse(selectedEntry)) {
await updateEntries([selectedEntry]);
closeFlyout();
} else if (isKnowledgeBaseEntryCreateProps(selectedEntry)) {
if (originalEntry) {
setDuplicateKBItem(selectedEntry);
return;
}
await createEntry(selectedEntry);
closeFlyout();
}
}, [selectedEntry, originalEntry, updateEntries, closeFlyout, createEntry]);
const onSaveCancelled = useCallback(() => {
setOriginalEntry(undefined);
setSelectedEntry(undefined);
closeFlyout();
}, [closeFlyout]);
const { value: existingIndices } = useAsync(() => {
const indices: string[] = [];
entries.data.forEach((entry) => {
@ -206,6 +214,7 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
},
onEditActionClicked: ({ id }: KnowledgeBaseEntryResponse) => {
const entry = entries.data.find((e) => e.id === id);
setOriginalEntry(entry);
setSelectedEntry(entry);
openFlyout();
},
@ -294,6 +303,18 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
}
}, [deleteEntry, deleteKBItem, setDeleteKBItem]);
const handleCancelDuplicateEntry = useCallback(() => {
setDuplicateKBItem(null);
}, [setDuplicateKBItem]);
const handleDuplicateEntry = useCallback(async () => {
if (duplicateKBItem) {
await createEntry(duplicateKBItem);
closeFlyout();
setDuplicateKBItem(null);
}
}, [closeFlyout, createEntry, duplicateKBItem]);
if (!enableKnowledgeBaseByDefault) {
return (
<>
@ -392,6 +413,7 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
{selectedEntry?.type === DocumentEntryType.value ? (
<DocumentEntryEditor
entry={selectedEntry as DocumentEntry}
originalEntry={originalEntry as DocumentEntry}
setEntry={
setSelectedEntry as React.Dispatch<React.SetStateAction<Partial<DocumentEntry>>>
}
@ -400,6 +422,7 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
) : (
<IndexEntryEditor
entry={selectedEntry as IndexEntry}
originalEntry={originalEntry as IndexEntry}
dataViews={dataViews}
setEntry={
setSelectedEntry as React.Dispatch<React.SetStateAction<Partial<IndexEntry>>>
@ -425,6 +448,19 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
<p>{i18n.DELETE_ENTRY_CONFIRMATION_CONTENT}</p>
</EuiConfirmModal>
)}
{duplicateKBItem && (
<EuiConfirmModal
title={i18n.DUPLICATE_ENTRY_CONFIRMATION_TITLE}
onCancel={handleCancelDuplicateEntry}
onConfirm={handleDuplicateEntry}
cancelButtonText={CANCEL_BUTTON_TEXT}
confirmButtonText={i18n.SAVE_BUTTON_TEXT}
defaultFocusedButton="confirm"
data-test-subj="create-duplicate-entry-modal"
>
<p>{i18n.DUPLICATE_ENTRY_CONFIRMATION_CONTENT}</p>
</EuiConfirmModal>
)}
<KnowledgeBaseTour isKbSettingsPage />
</>
);

View file

@ -21,16 +21,26 @@ import React, { useCallback, useMemo } from 'react';
import { IndexEntry } from '@kbn/elastic-assistant-common';
import { DataViewsContract } from '@kbn/data-views-plugin/public';
import * as i18n from './translations';
import { isGlobalEntry } from './helpers';
interface Props {
dataViews: DataViewsContract;
entry?: IndexEntry;
originalEntry?: IndexEntry;
setEntry: React.Dispatch<React.SetStateAction<Partial<IndexEntry>>>;
hasManageGlobalKnowledgeBase: boolean;
}
export const IndexEntryEditor: React.FC<Props> = React.memo(
({ dataViews, entry, setEntry, hasManageGlobalKnowledgeBase }) => {
({ dataViews, entry, setEntry, hasManageGlobalKnowledgeBase, originalEntry }) => {
const privateUsers = useMemo(() => {
const originalUsers = originalEntry?.users;
if (originalEntry && !isGlobalEntry(originalEntry)) {
return originalUsers;
}
return undefined;
}, [originalEntry]);
// Name
const setName = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) =>
@ -43,9 +53,9 @@ export const IndexEntryEditor: React.FC<Props> = React.memo(
(value: string) =>
setEntry((prevEntry) => ({
...prevEntry,
users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : undefined,
users: value === i18n.SHARING_GLOBAL_OPTION_LABEL ? [] : privateUsers,
})),
[setEntry]
[privateUsers, setEntry]
);
const sharingOptions = [
{

View file

@ -336,6 +336,28 @@ export const PRIVATE = i18n.translate(
}
);
export const SAVE_BUTTON_TEXT = i18n.translate(
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.saveButtonText',
{
defaultMessage: 'Save',
}
);
export const DUPLICATE_ENTRY_CONFIRMATION_TITLE = i18n.translate(
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.duplicateEntryConfirmationTitle',
{
defaultMessage: 'Duplicate entry?',
}
);
export const DUPLICATE_ENTRY_CONFIRMATION_CONTENT = i18n.translate(
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.duplicateEntryConfirmationContent',
{
defaultMessage:
'Changing a knowledge base entry from global to private will create a private copy of the original global entry. Please delete the global entry if you would like to revoke the content for other users.',
}
);
export const MISSING_INDEX_ERROR = i18n.translate(
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.missingIndexError',
{