Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [Security Assistant] Knowledge base switch to use `semantic_text` (#197007) #198437

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/kbn-data-stream-adapter/src/field_maps/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export type FieldMap<T extends string = string> = Record<
scaling_factor?: number;
dynamic?: boolean | 'strict';
properties?: Record<string, { type: string }>;
inference_id?: string;
copy_to?: string;
}
>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,5 @@ export const ReadKnowledgeBaseResponse = z.object({
is_setup_in_progress: z.boolean().optional(),
pipeline_exists: z.boolean().optional(),
security_labs_exists: z.boolean().optional(),
user_data_exists: z.boolean().optional(),
});
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ paths:
type: boolean
security_labs_exists:
type: boolean
user_data_exists:
type: boolean
400:
description: Generic Error
content:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface UseKnowledgeBaseEntriesParams {
signal?: AbortSignal | undefined;
toasts?: IToasts;
enabled?: boolean; // For disabling if FF is off
isRefetching?: boolean; // For enabling polling
}

const defaultQuery: FindKnowledgeBaseEntriesRequestQuery = {
Expand Down Expand Up @@ -56,6 +57,7 @@ export const useKnowledgeBaseEntries = ({
signal,
toasts,
enabled = false,
isRefetching = false,
}: UseKnowledgeBaseEntriesParams) =>
useQuery(
KNOWLEDGE_BASE_ENTRY_QUERY_KEY,
Expand All @@ -73,6 +75,7 @@ export const useKnowledgeBaseEntries = ({
enabled,
keepPreviousData: true,
initialData: { page: 1, perPage: 100, total: 0, data: [] },
refetchInterval: isRefetching ? 30000 : false,
onError: (error: IHttpFetchError<ResponseErrorBody>) => {
if (error.name !== 'AbortError') {
toasts?.addError(error, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const statusResponse = {
elser_exists: true,
index_exists: true,
pipeline_exists: true,
security_labs_exists: true,
};

const http = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const useKnowledgeBaseStatus = ({
{
retry: false,
keepPreviousData: true,
// Polling interval for Knowledge Base setup in progress
refetchInterval: (data) => (data?.is_setup_in_progress ? 30000 : false),
// Deprecated, hoist to `queryCache` w/in `QueryClient. See: https://stackoverflow.com/a/76961109
onError: (error: IHttpFetchError<ResponseErrorBody>) => {
if (error.name !== 'AbortError') {
Expand Down Expand Up @@ -86,12 +88,12 @@ export const useInvalidateKnowledgeBaseStatus = () => {
*
* @param kbStatus ReadKnowledgeBaseResponse
*/
export const isKnowledgeBaseSetup = (kbStatus: ReadKnowledgeBaseResponse | undefined): boolean => {
return (
(kbStatus?.elser_exists &&
kbStatus?.security_labs_exists &&
kbStatus?.index_exists &&
kbStatus?.pipeline_exists) ??
false
);
};
export const isKnowledgeBaseSetup = (kbStatus: ReadKnowledgeBaseResponse | undefined): boolean =>
(kbStatus?.elser_exists &&
kbStatus?.index_exists &&
kbStatus?.pipeline_exists &&
// Allows to use UI while importing Security Labs docs
(kbStatus?.security_labs_exists ||
kbStatus?.is_setup_in_progress ||
kbStatus?.user_data_exists)) ??
false;
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ export const KnowledgeBaseSettings: React.FC<Props> = React.memo(
const isSecurityLabsEnabled = kbStatus?.security_labs_exists ?? false;
const isKnowledgeBaseSetup =
(isElserEnabled &&
isSecurityLabsEnabled &&
kbStatus?.index_exists &&
kbStatus?.pipeline_exists) ??
kbStatus?.pipeline_exists &&
(isSecurityLabsEnabled || kbStatus?.user_data_exists)) ??
false;
const isSetupInProgress = kbStatus?.is_setup_in_progress ?? false;
const isSetupAvailable = kbStatus?.is_setup_available ?? false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
http,
toasts,
enabled: enableKnowledgeBaseByDefault,
isRefetching: kbStatus?.is_setup_in_progress,
});

// Flyout Save/Cancel Actions
Expand Down Expand Up @@ -190,13 +191,15 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
indices.push(entry.index);
}
});
return dataViews.getExistingIndices(indices);

return indices.length ? dataViews.getExistingIndices(indices) : Promise.resolve([]);
}, [entries.data]);

const { getColumns } = useKnowledgeBaseTable();
const columns = useMemo(
() =>
getColumns({
isKbSetupInProgress: kbStatus?.is_setup_in_progress ?? false,
existingIndices,
isDeleteEnabled: (entry: KnowledgeBaseEntryResponse) => {
return (
Expand All @@ -219,7 +222,14 @@ export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ d
openFlyout();
},
}),
[entries.data, existingIndices, getColumns, hasManageGlobalKnowledgeBase, openFlyout]
[
entries.data,
existingIndices,
getColumns,
hasManageGlobalKnowledgeBase,
kbStatus?.is_setup_in_progress,
openFlyout,
]
);

// Refresh button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const IndexEntryEditor: React.FC<Props> = React.memo(
dataViews.getFieldsForWildcard({
pattern: entry?.index ?? '',
}),
[]
[entry?.index]
);

const fieldOptions = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,10 @@ export const MISSING_INDEX_TOOLTIP_CONTENT = i18n.translate(
'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.',
}
);

export const SECURITY_LABS_NOT_FULLY_LOADED = i18n.translate(
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.securityLabsNotFullyLoadedTooltipContent',
{
defaultMessage: 'Security Labs content is not fully loaded. Click to reload.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
EuiBasicTableColumn,
EuiIcon,
EuiText,
EuiLoadingSpinner,
EuiToolTip,
} from '@elastic/eui';
import { css } from '@emotion/react';
Expand All @@ -29,11 +30,16 @@ import * as i18n from './translations';
import { BadgesColumn } from '../../assistant/common/components/assistant_settings_management/badges';
import { useInlineActions } from '../../assistant/common/components/assistant_settings_management/inline_actions';
import { isSystemEntry } from './helpers';
import { SetupKnowledgeBaseButton } from '../setup_knowledge_base_button';

const AuthorColumn = ({ entry }: { entry: KnowledgeBaseEntryResponse }) => {
const { userProfileService } = useAssistantContext();

const userProfile = useAsync(async () => {
if (isSystemEntry(entry) || entry.createdBy === 'unknown') {
return;
}

const profile = await userProfileService?.bulkGet<{ avatar: UserProfileAvatarData }>({
uids: new Set([entry.createdBy]),
dataPath: 'avatar',
Expand All @@ -45,7 +51,7 @@ const AuthorColumn = ({ entry }: { entry: KnowledgeBaseEntryResponse }) => {
() => userProfile?.value?.username ?? 'Unknown',
[userProfile?.value?.username]
);
const userAvatar = userProfile.value?.avatar;
const userAvatar = userProfile?.value?.avatar;
const badgeItem = isSystemEntry(entry) ? 'Elastic' : userName;
const userImage = isSystemEntry(entry) ? (
<EuiIcon
Expand Down Expand Up @@ -142,12 +148,14 @@ export const useKnowledgeBaseTable = () => {
isEditEnabled,
onDeleteActionClicked,
onEditActionClicked,
isKbSetupInProgress,
}: {
existingIndices?: string[];
isDeleteEnabled: (entry: KnowledgeBaseEntryResponse) => boolean;
isEditEnabled: (entry: KnowledgeBaseEntryResponse) => boolean;
onDeleteActionClicked: (entry: KnowledgeBaseEntryResponse) => void;
onEditActionClicked: (entry: KnowledgeBaseEntryResponse) => void;
isKbSetupInProgress: boolean;
}): Array<EuiBasicTableColumn<KnowledgeBaseEntryResponse>> => {
return [
{
Expand Down Expand Up @@ -180,11 +188,27 @@ export const useKnowledgeBaseTable = () => {
{
name: i18n.COLUMN_ENTRIES,
render: (entry: KnowledgeBaseEntryResponse) => {
return isSystemEntry(entry)
? entry.text
: entry.type === DocumentEntryType.value
? '1'
: '-';
return isSystemEntry(entry) ? (
<>
{`${entry.text}`}
{isKbSetupInProgress ? (
<EuiLoadingSpinner
size="m"
css={css`
margin-left: 8px;
`}
/>
) : (
<EuiToolTip content={i18n.SECURITY_LABS_NOT_FULLY_LOADED}>
<SetupKnowledgeBaseButton display="refresh" />
</EuiToolTip>
)}
</>
) : entry.type === DocumentEntryType.value ? (
'1'
) : (
'-'
);
},
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
*/

import React, { useCallback } from 'react';
import { EuiButton, EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
import { EuiButton, EuiButtonIcon, EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { css } from '@emotion/react';
import { useAssistantContext } from '../..';
import { useSetupKnowledgeBase } from '../assistant/api/knowledge_base/use_setup_knowledge_base';
import { useKnowledgeBaseStatus } from '../assistant/api/knowledge_base/use_knowledge_base_status';

interface Props {
display?: 'mini';
display?: 'mini' | 'refresh';
}

/**
Expand Down Expand Up @@ -48,6 +49,23 @@ export const SetupKnowledgeBaseButton: React.FC<Props> = React.memo(({ display }
})
: undefined;

if (display === 'refresh') {
return (
<EuiButtonIcon
color="primary"
data-test-subj="setup-knowledge-base-button"
disabled={!kbStatus?.is_setup_available}
isLoading={isSetupInProgress}
iconType="refresh"
onClick={onInstallKnowledgeBase}
size="xs"
css={css`
margin-left: 8px;
`}
/>
);
}

return (
<EuiToolTip position={'bottom'} content={toolTipContent}>
{display === 'mini' ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ export const mSearchQueryBody: MsearchQueryBody = {
],
must: [
{
text_expansion: {
'vector.tokens': {
model_id: '.elser_model_2',
model_text:
'Generate an ESQL query that will count the number of connections made to external IP addresses, broken down by user. If the count is greater than 100 for a specific user, add a new field called "follow_up" that contains a value of "true", otherwise, it should contain "false". The user names should also be enriched with their respective group names.',
},
semantic: {
field: 'semantic_text',
query:
'Generate an ESQL query that will count the number of connections made to external IP addresses, broken down by user. If the count is greater than 100 for a specific user, add a new field called "follow_up" that contains a value of "true", otherwise, it should contain "false". The user names should also be enriched with their respective group names.',
},
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ export const mockVectorSearchQuery: QueryDslQueryContainer = {
],
must: [
{
text_expansion: {
'vector.tokens': {
model_id: '.elser_model_2',
model_text:
'Generate an ES|QL query that will count the number of connections made to external IP addresses, broken down by user. If the count is greater than 100 for a specific user, add a new field called "follow_up" that contains a value of "true", otherwise, it should contain "false". The user names should also be enriched with their respective group names.',
},
semantic: {
field: 'semantic_text',
query:
'Generate an ES|QL query that will count the number of connections made to external IP addresses, broken down by user. If the count is greater than 100 for a specific user, add a new field called "follow_up" that contains a value of "true", otherwise, it should contain "false". The user names should also be enriched with their respective group names.',
},
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export const getUpdateScript = ({
isPatch?: boolean;
}) => {
return {
source: `
script: {
source: `
if (params.assignEmpty == true || params.containsKey('allowed')) {
ctx._source.allowed = params.allowed;
}
Expand All @@ -108,11 +109,12 @@ export const getUpdateScript = ({
}
ctx._source.updated_at = params.updated_at;
`,
lang: 'painless',
params: {
...anonymizationField, // when assigning undefined in painless, it will remove property and wil set it to null
// for patch we don't want to remove unspecified value in payload
assignEmpty: !(isPatch ?? true),
lang: 'painless',
params: {
...anonymizationField, // when assigning undefined in painless, it will remove property and wil set it to null
// for patch we don't want to remove unspecified value in payload
assignEmpty: !(isPatch ?? true),
},
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export const getUpdateScript = ({
isPatch?: boolean;
}) => {
return {
source: `
script: {
source: `
if (params.assignEmpty == true || params.containsKey('api_config')) {
if (ctx._source.api_config != null) {
if (params.assignEmpty == true || params.api_config.containsKey('connector_id')) {
Expand Down Expand Up @@ -70,11 +71,12 @@ export const getUpdateScript = ({
}
ctx._source.updated_at = params.updated_at;
`,
lang: 'painless',
params: {
...conversation, // when assigning undefined in painless, it will remove property and wil set it to null
// for patch we don't want to remove unspecified value in payload
assignEmpty: !(isPatch ?? true),
lang: 'painless',
params: {
...conversation, // when assigning undefined in painless, it will remove property and wil set it to null
// for patch we don't want to remove unspecified value in payload
assignEmpty: !(isPatch ?? true),
},
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const updateConversation = async ({
},
},
refresh: true,
script: getUpdateScript({ conversation: params, isPatch }),
script: getUpdateScript({ conversation: params, isPatch }).script,
});

if (response.failures && response.failures.length > 0) {
Expand Down
Loading