Skip to content

Commit

Permalink
[Security GenAI] When indices referenced in KB index entries are dele…
Browse files Browse the repository at this point in the history
…ted from OUTSIDE the AI Assistant KB UI, there is not indication to the user (elastic#197156) (elastic#197722)

## Summary

Bug elastic#197156

This is a UI part of the bug that warns a user about missing indices
used in knowledge base entries.

### To test

1. Add an index entry that uses existing index
2. Remove that index
3. Go back to knowledge base entries page
4. You should see warning icon next to the name of the index entry which
uses removed index. Also, when you edit that entry you will see `Index
doesn't exist` error next to the `Index` field in the flyout

<img width="1458" alt="Screenshot 2024-10-24 at 19 54 36"
src="https://github.com/user-attachments/assets/7d4468f9-fada-4416-9480-99bfca3de220">

<img width="615" alt="Screenshot 2024-10-24 at 19 54 52"
src="https://github.com/user-attachments/assets/fd9bbe80-0a3c-40b8-909a-93f8082e69eb">

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

(cherry picked from commit a1d755a)
  • Loading branch information
e40pud committed Oct 25, 2024
1 parent 07c7f72 commit 884070b
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const useInlineActions = <T extends { isDefault?: boolean | undefined }>(
actions: [
{
name: i18n.EDIT_BUTTON,
'data-test-subj': 'edit-button',
description: i18n.EDIT_BUTTON,
icon: 'pencil',
type: 'icon',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const mockDataViews = {
{ name: 'field-2', esTypes: ['text'] },
{ name: 'field-3', esTypes: ['semantic_text'] },
]),
getExistingIndices: jest.fn().mockResolvedValue(['index-2']),
} as unknown as DataViewsContract;
const queryClient = new QueryClient();
const wrapper = (props: { children: React.ReactNode }) => (
Expand All @@ -65,7 +66,22 @@ const wrapper = (props: { children: React.ReactNode }) => (
describe('KnowledgeBaseSettingsManagement', () => {
const mockData = [
{ id: '1', name: 'Test Entry 1', type: 'document', kbResource: 'user', users: [{ id: 'hi' }] },
{ id: '2', name: 'Test Entry 2', type: 'index', kbResource: 'global', users: [] },
{
id: '2',
name: 'Test Entry 2',
type: 'index',
kbResource: 'global',
users: [],
index: 'missing-index',
},
{
id: '3',
name: 'Test Entry 3',
type: 'index',
kbResource: 'private',
users: [{ id: 'fake-user' }],
index: 'index-2',
},
];

beforeEach(() => {
Expand Down Expand Up @@ -241,4 +257,24 @@ describe('KnowledgeBaseSettingsManagement', () => {
});
expect(screen.queryByTestId('delete-entry-confirmation')).not.toBeInTheDocument();
});

it('shows warning icon for index entries with missing indices', async () => {
render(<KnowledgeBaseSettingsManagement dataViews={mockDataViews} />, {
wrapper,
});

await waitFor(() => expect(screen.getByTestId('missing-index-icon')).toBeInTheDocument());

expect(screen.getAllByTestId('missing-index-icon').length).toEqual(1);

fireEvent.mouseOver(screen.getByTestId('missing-index-icon'));

await waitFor(() => screen.getByTestId('missing-index-tooltip'));

expect(
screen.getByText(
'The index assigned to this knowledge base entry is unavailable. Check the permissions on the configured index, or that the index has not been deleted. You can update the index to be used for this knowledge entry, or delete the entry entirely.'
)
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
} from '@kbn/elastic-assistant-common';
import { css } from '@emotion/react';
import { DataViewsContract } from '@kbn/data-views-plugin/public';
import useAsync from 'react-use/lib/useAsync';
import { KnowledgeBaseTour } from '../../tour/knowledge_base';
import { AlertsSettingsManagement } from '../../assistant/settings/alerts_settings/alerts_settings_management';
import { useKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_knowledge_base_entries';
Expand Down Expand Up @@ -173,10 +174,22 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
toasts,
enabled: enableKnowledgeBaseByDefault,
});

const { value: existingIndices } = useAsync(() => {
const indices: string[] = [];
entries.data.forEach((entry) => {
if (entry.type === 'index') {
indices.push(entry.index);
}
});
return dataViews.getExistingIndices(indices);
}, [entries.data]);

const { getColumns } = useKnowledgeBaseTable();
const columns = useMemo(
() =>
getColumns({
existingIndices,
isDeleteEnabled: (entry: KnowledgeBaseEntryResponse) => {
return (
!isSystemEntry(entry) && (isGlobalEntry(entry) ? hasManageGlobalKnowledgeBase : true)
Expand All @@ -197,7 +210,7 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
openFlyout();
},
}),
[entries.data, getColumns, hasManageGlobalKnowledgeBase, openFlyout]
[entries.data, existingIndices, getColumns, hasManageGlobalKnowledgeBase, openFlyout]
);

// Refresh button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('IndexEntryEditor', () => {
{ name: 'field-2', esTypes: ['text'] },
{ name: 'field-3', esTypes: ['semantic_text'] },
]),
getExistingIndices: jest.fn().mockResolvedValue(['index-1']),
} as unknown as DataViewsContract;

const defaultProps = {
Expand Down Expand Up @@ -147,4 +148,20 @@ describe('IndexEntryEditor', () => {
expect(getByRole('combobox', { name: i18n.ENTRY_FIELD_PLACEHOLDER })).toBeDisabled();
});
});

it('fetches index options and updates on selection 2', async () => {
(mockDataViews.getExistingIndices as jest.Mock).mockResolvedValue([]);
const { getByText } = render(
<IndexEntryEditor
{...defaultProps}
entry={{ ...defaultProps.entry, index: 'missing-index' }}
/>
);

await waitFor(() => {
expect(mockDataViews.getExistingIndices).toHaveBeenCalled();
});

expect(getByText("Index doesn't exist")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ export const IndexEntryEditor: React.FC<Props> = React.memo(
}));
}, [dataViews]);

const { value: isMissingIndex } = useAsync(async () => {
if (!entry?.index?.length) return false;

return !(await dataViews.getExistingIndices([entry.index])).length;
}, [entry?.index]);

const indexFields = useAsync(
async () =>
dataViews.getFieldsForWildcard({
Expand Down Expand Up @@ -251,11 +257,17 @@ export const IndexEntryEditor: React.FC<Props> = React.memo(
fullWidth
/>
</EuiFormRow>
<EuiFormRow label={i18n.ENTRY_INDEX_NAME_INPUT_LABEL} fullWidth>
<EuiFormRow
label={i18n.ENTRY_INDEX_NAME_INPUT_LABEL}
fullWidth
isInvalid={isMissingIndex}
error={isMissingIndex && <>{i18n.MISSING_INDEX_ERROR}</>}
>
<EuiComboBox
data-test-subj="index-combobox"
aria-label={i18n.ENTRY_INDEX_NAME_INPUT_LABEL}
isClearable={true}
isInvalid={isMissingIndex}
singleSelection={{ asPlainText: true }}
onCreateOption={onCreateIndexOption}
fullWidth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,18 @@ export const PRIVATE = i18n.translate(
defaultMessage: 'Private',
}
);

export const MISSING_INDEX_ERROR = i18n.translate(
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.missingIndexError',
{
defaultMessage: `Index doesn't exist`,
}
);

export const MISSING_INDEX_TOOLTIP_CONTENT = i18n.translate(
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.missingIndexTootipContent',
{
defaultMessage:
'The index assigned to this knowledge base entry is unavailable. Check the permissions on the configured index, or that the index has not been deleted. You can update the index to be used for this knowledge entry, or delete the entry entirely.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
* 2.0.
*/

import { EuiAvatar, EuiBadge, EuiBasicTableColumn, EuiIcon, EuiText } from '@elastic/eui';
import {
EuiAvatar,
EuiBadge,
EuiBasicTableColumn,
EuiIcon,
EuiText,
EuiToolTip,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { useCallback, useMemo } from 'react';
import { FormattedDate } from '@kbn/i18n-react';
Expand Down Expand Up @@ -77,6 +84,39 @@ const AuthorColumn = ({ entry }: { entry: KnowledgeBaseEntryResponse }) => {
);
};

const NameColumn = ({
entry,
existingIndices,
}: {
entry: KnowledgeBaseEntryResponse;
existingIndices?: string[];
}) => {
let showMissingIndexWarning = false;
if (existingIndices && entry.type === 'index') {
showMissingIndexWarning = !existingIndices.includes(entry.index);
}
return (
<>
<EuiText size={'s'}>{entry.name}</EuiText>
{showMissingIndexWarning && (
<EuiToolTip
data-test-subj="missing-index-tooltip"
content={i18n.MISSING_INDEX_TOOLTIP_CONTENT}
>
<EuiIcon
data-test-subj="missing-index-icon"
type="warning"
color="danger"
css={css`
margin-left: 10px;
`}
/>
</EuiToolTip>
)}
</>
);
};

export const useKnowledgeBaseTable = () => {
const getActions = useInlineActions<KnowledgeBaseEntryResponse & { isDefault?: undefined }>();

Expand All @@ -97,11 +137,13 @@ export const useKnowledgeBaseTable = () => {

const getColumns = useCallback(
({
existingIndices,
isDeleteEnabled,
isEditEnabled,
onDeleteActionClicked,
onEditActionClicked,
}: {
existingIndices?: string[];
isDeleteEnabled: (entry: KnowledgeBaseEntryResponse) => boolean;
isEditEnabled: (entry: KnowledgeBaseEntryResponse) => boolean;
onDeleteActionClicked: (entry: KnowledgeBaseEntryResponse) => void;
Expand All @@ -115,7 +157,9 @@ export const useKnowledgeBaseTable = () => {
},
{
name: i18n.COLUMN_NAME,
render: ({ name }: KnowledgeBaseEntryResponse) => name,
render: (entry: KnowledgeBaseEntryResponse) => (
<NameColumn entry={entry} existingIndices={existingIndices} />
),
sortable: ({ name }: KnowledgeBaseEntryResponse) => name,
width: '30%',
},
Expand Down

0 comments on commit 884070b

Please sign in to comment.