mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.14`: - [[Search] [Playground] Improve UX + fix vector field issue (#182342)](https://github.com/elastic/kibana/pull/182342) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Joe McElroy","email":"joseph.mcelroy@elastic.co"},"sourceCommit":{"committedDate":"2024-05-02T14:45:07Z","message":"[Search] [Playground] Improve UX + fix vector field issue (#182342)\n\nImproves the experience for Edit context and view query.\r\n\r\nNow select boxes for context fields\r\n\r\n\r\n\r\n\r\nand now view query is checkboxes and a label for when fields are hidden\r\n\r\n\r\n\r\n\r\nAlso fixes an issue where if an index has a dense vector field but\r\ndoesn't have an inference processor setup. we now skip over these type\r\nof fields.","sha":"8cc8be50d75ac64ebe05985c0916aa248a78b61d","branchLabelMapping":{"^v8.15.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:EnterpriseSearch","v8.14.0","v8.15.0"],"title":"[Search] [Playground] Improve UX + fix vector field issue","number":182342,"url":"https://github.com/elastic/kibana/pull/182342","mergeCommit":{"message":"[Search] [Playground] Improve UX + fix vector field issue (#182342)\n\nImproves the experience for Edit context and view query.\r\n\r\nNow select boxes for context fields\r\n\r\n\r\n\r\n\r\nand now view query is checkboxes and a label for when fields are hidden\r\n\r\n\r\n\r\n\r\nAlso fixes an issue where if an index has a dense vector field but\r\ndoesn't have an inference processor setup. we now skip over these type\r\nof fields.","sha":"8cc8be50d75ac64ebe05985c0916aa248a78b61d"}},"sourceBranch":"main","suggestedTargetBranches":["8.14"],"targetPullRequestStates":[{"branch":"8.14","label":"v8.14.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.15.0","branchLabelMappingKey":"^v8.15.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/182342","number":182342,"mergeCommit":{"message":"[Search] [Playground] Improve UX + fix vector field issue (#182342)\n\nImproves the experience for Edit context and view query.\r\n\r\nNow select boxes for context fields\r\n\r\n\r\n\r\n\r\nand now view query is checkboxes and a label for when fields are hidden\r\n\r\n\r\n\r\n\r\nAlso fixes an issue where if an index has a dense vector field but\r\ndoesn't have an inference processor setup. we now skip over these type\r\nof fields.","sha":"8cc8be50d75ac64ebe05985c0916aa248a78b61d"}}]}] BACKPORT--> Co-authored-by: Joe McElroy <joseph.mcelroy@elastic.co>
This commit is contained in:
parent
6785bb1f8d
commit
9e1d156c72
10 changed files with 244 additions and 107 deletions
|
@ -19,6 +19,7 @@ export interface QuerySourceFields {
|
|||
dense_vector_query_fields: ModelFields[];
|
||||
bm25_query_fields: string[];
|
||||
source_fields: string[];
|
||||
skipped_fields: number;
|
||||
}
|
||||
|
||||
export enum APIRoutes {
|
||||
|
|
|
@ -20,6 +20,12 @@ jest.mock('../../hooks/use_indices_fields', () => ({
|
|||
bm25_query_fields: ['field1', 'field2'],
|
||||
source_fields: ['context_field1', 'context_field2'],
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [],
|
||||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: ['field1', 'field2'],
|
||||
source_fields: ['context_field1', 'context_field2'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
@ -36,6 +42,11 @@ const MockFormProvider = ({ children }: { children: React.ReactElement }) => {
|
|||
const methods = useForm({
|
||||
values: {
|
||||
indices: ['index1'],
|
||||
docSize: 1,
|
||||
sourceFields: {
|
||||
index1: ['context_field1'],
|
||||
index2: ['context_field2'],
|
||||
},
|
||||
},
|
||||
});
|
||||
return <FormProvider {...methods}>{children}</FormProvider>;
|
||||
|
@ -55,13 +66,14 @@ describe('EditContextFlyout component tests', () => {
|
|||
});
|
||||
|
||||
it('calls onClose when the close button is clicked', () => {
|
||||
fireEvent.click(screen.getByTestId('euiFlyoutCloseButton'));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Close' }));
|
||||
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should see the context fields', async () => {
|
||||
expect(screen.getByTestId('contextFieldsSelectable')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('contextFieldsSelectable')).toHaveTextContent(`context_field2`);
|
||||
expect(screen.getByTestId('contextFieldsSelectable')).toHaveTextContent(`context_field1`);
|
||||
expect(screen.getByTestId('contextFieldsSelectable_index1')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId('contextFieldsSelectable_index1'));
|
||||
expect(screen.getByRole('option', { name: 'context_field1' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('option', { name: 'context_field2' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,11 +18,9 @@ import {
|
|||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiPanel,
|
||||
EuiAccordion,
|
||||
EuiSelectable,
|
||||
EuiSelect,
|
||||
EuiSelectableOption,
|
||||
EuiSuperSelect,
|
||||
EuiFormRow,
|
||||
} from '@elastic/eui';
|
||||
import { useController, useFormContext } from 'react-hook-form';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -62,10 +60,10 @@ export const EditContextFlyout: React.FC<EditContextFlyoutProps> = ({ onClose })
|
|||
|
||||
const [tempSourceFields, setTempSourceFields] = useState(sourceFields);
|
||||
|
||||
const toggleSourceField = (index: string, f: EuiSelectableOption[]) => {
|
||||
const updateSourceField = (index: string, field: string) => {
|
||||
setTempSourceFields({
|
||||
...tempSourceFields,
|
||||
[index]: f.filter(({ checked }) => checked === 'on').map(({ label }) => label),
|
||||
[index]: [field],
|
||||
});
|
||||
usageTracker?.click(AnalyticsEvents.editContextFieldToggled);
|
||||
};
|
||||
|
@ -76,6 +74,7 @@ export const EditContextFlyout: React.FC<EditContextFlyoutProps> = ({ onClose })
|
|||
onChangeSize(docSize);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleDocSizeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
usageTracker?.click(AnalyticsEvents.editContextDocSizeChanged);
|
||||
setDocSize(Number(e.target.value));
|
||||
|
@ -119,71 +118,69 @@ export const EditContextFlyout: React.FC<EditContextFlyoutProps> = ({ onClose })
|
|||
<EuiFlyoutBody>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexGroup direction="column" gutterSize="l">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSelect
|
||||
prepend={i18n.translate(
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.searchPlayground.editContext.flyout.docsRetrievedCount',
|
||||
{
|
||||
defaultMessage: 'Retrieved documents',
|
||||
defaultMessage: 'Number of documents to retrieve',
|
||||
}
|
||||
)}
|
||||
options={[
|
||||
{
|
||||
value: 3,
|
||||
text: '3',
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
text: '5',
|
||||
},
|
||||
{
|
||||
value: 10,
|
||||
text: '10',
|
||||
},
|
||||
]}
|
||||
value={docSize}
|
||||
onChange={handleDocSizeChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.searchPlayground.editContext.flyout.table.title"
|
||||
defaultMessage="Selected fields"
|
||||
>
|
||||
<EuiSelect
|
||||
options={[
|
||||
{
|
||||
value: 1,
|
||||
text: '1',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
text: '3',
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
text: '5',
|
||||
},
|
||||
{
|
||||
value: 10,
|
||||
text: '10',
|
||||
},
|
||||
]}
|
||||
value={docSize}
|
||||
onChange={handleDocSizeChange}
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
{Object.entries(fields).map(([index, group], i) => (
|
||||
<EuiFlexItem grow={false} key={index}>
|
||||
<EuiPanel grow={false} hasShadow={false} hasBorder>
|
||||
<EuiAccordion
|
||||
initialIsOpen={i === 0}
|
||||
id={index}
|
||||
buttonContent={
|
||||
<EuiText>
|
||||
<h5>{index}</h5>
|
||||
</EuiText>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiSelectable
|
||||
aria-label="Select context fields"
|
||||
data-test-subj="contextFieldsSelectable"
|
||||
options={group.source_fields.map((field) => ({
|
||||
label: field,
|
||||
checked: tempSourceFields[index]?.includes(field) ? 'on' : undefined,
|
||||
}))}
|
||||
onChange={(newOptions) => toggleSourceField(index, newOptions)}
|
||||
listProps={{ bordered: false }}
|
||||
singleSelection="always"
|
||||
>
|
||||
{(list) => list}
|
||||
</EuiSelectable>
|
||||
</EuiAccordion>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.searchPlayground.editContext.flyout.table.title"
|
||||
defaultMessage="Select fields"
|
||||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{Object.entries(fields).map(([index, group]) => (
|
||||
<EuiFlexItem grow={false} key={index}>
|
||||
<EuiFormRow label={index}>
|
||||
<EuiSuperSelect
|
||||
data-test-subj={`contextFieldsSelectable_${index}`}
|
||||
options={group.source_fields.map((field) => ({
|
||||
value: field,
|
||||
inputDisplay: field,
|
||||
}))}
|
||||
valueOfSelected={tempSourceFields[index]?.[0]}
|
||||
onChange={(value) => updateSourceField(index, value)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -18,6 +18,13 @@ jest.mock('../../hooks/use_indices_fields', () => ({
|
|||
elser_query_fields: [],
|
||||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: ['field1', 'field2'],
|
||||
skipped_fields: 1,
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [],
|
||||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: ['field1', 'field2'],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
@ -34,7 +41,7 @@ jest.mock('../../hooks/use_usage_tracker', () => ({
|
|||
const MockFormProvider = ({ children }: { children: React.ReactElement }) => {
|
||||
const methods = useForm({
|
||||
values: {
|
||||
indices: ['index1'],
|
||||
indices: ['index1', 'index2'],
|
||||
},
|
||||
});
|
||||
return <FormProvider {...methods}>{children}</FormProvider>;
|
||||
|
@ -61,7 +68,19 @@ describe('ViewQueryFlyout component tests', () => {
|
|||
it('should see the view elasticsearch query', async () => {
|
||||
expect(screen.getByTestId('ViewElasticsearchQueryResult')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('ViewElasticsearchQueryResult')).toHaveTextContent(
|
||||
`{ "retriever": { "standard": { "query": { "multi_match": { "query": "{query}", "fields": [ "field1" ] } } } } }`
|
||||
`{ "retriever": { "rrf": { "retrievers": [ { "standard": { "query": { "multi_match": { "query": "{query}", "fields": [ "field1" ] } } } }, { "standard": { "query": { "multi_match": { "query": "{query}", "fields": [ "field1" ] } } } } ] } } }`
|
||||
);
|
||||
});
|
||||
|
||||
it('displays query fields and indicates hidden fields', () => {
|
||||
expect(screen.getByTestId('queryFieldsSelectable_index1')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('queryFieldsSelectable_index2')).toBeInTheDocument();
|
||||
|
||||
// Check if hidden fields indicator is shown
|
||||
expect(screen.getByTestId('skipped_fields_index1')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('skipped_fields_index1')).toHaveTextContent('1 fields are hidden.');
|
||||
|
||||
// Check if hidden fields indicator is shown
|
||||
expect(screen.queryByTestId('skipped_fields_index2')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,12 +22,16 @@ import {
|
|||
EuiSelectableOption,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiCheckbox,
|
||||
EuiLink,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useController } from 'react-hook-form';
|
||||
import { AnalyticsEvents } from '../../analytics/constants';
|
||||
import { docLinks } from '../../../common/doc_links';
|
||||
import { useIndicesFields } from '../../hooks/use_indices_fields';
|
||||
import { useUsageTracker } from '../../hooks/use_usage_tracker';
|
||||
import { ChatForm, ChatFormFields, IndicesQuerySourceFields } from '../../types';
|
||||
|
@ -157,7 +161,7 @@ export const ViewQueryFlyout: React.FC<ViewQueryFlyoutProps> = ({ onClose }) =>
|
|||
</EuiCodeBlock>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiText>
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
|
@ -166,12 +170,12 @@ export const ViewQueryFlyout: React.FC<ViewQueryFlyoutProps> = ({ onClose }) =>
|
|||
/>
|
||||
</h5>
|
||||
</EuiText>
|
||||
{Object.entries(fields).map(([index, group], i) => (
|
||||
{Object.entries(fields).map(([index, group]) => (
|
||||
<EuiFlexItem grow={false} key={index}>
|
||||
<EuiPanel grow={false} hasShadow={false} hasBorder>
|
||||
<EuiAccordion
|
||||
id={index}
|
||||
initialIsOpen={i === 0}
|
||||
initialIsOpen
|
||||
buttonContent={
|
||||
<EuiText>
|
||||
<h5>{index}</h5>
|
||||
|
@ -181,24 +185,70 @@ export const ViewQueryFlyout: React.FC<ViewQueryFlyoutProps> = ({ onClose }) =>
|
|||
<EuiSpacer size="s" />
|
||||
<EuiSelectable
|
||||
aria-label="Select query fields"
|
||||
data-test-subj={`queryFieldsSelectable_${index}`}
|
||||
options={[
|
||||
...group.elser_query_fields,
|
||||
...group.dense_vector_query_fields,
|
||||
...group.bm25_query_fields,
|
||||
].map((field) => ({
|
||||
label: typeof field === 'string' ? field : field.field,
|
||||
checked: isQueryFieldSelected(
|
||||
].map((field, idx) => {
|
||||
const checked = isQueryFieldSelected(
|
||||
index,
|
||||
typeof field === 'string' ? field : field.field
|
||||
)
|
||||
? 'on'
|
||||
: undefined,
|
||||
}))}
|
||||
);
|
||||
return {
|
||||
label: typeof field === 'string' ? field : field.field,
|
||||
prepend: (
|
||||
<EuiCheckbox
|
||||
id={`checkbox_${idx}`}
|
||||
checked={checked}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
),
|
||||
checked: checked ? 'on' : undefined,
|
||||
};
|
||||
})}
|
||||
listProps={{
|
||||
bordered: false,
|
||||
showIcons: false,
|
||||
}}
|
||||
onChange={(newOptions) => updateFields(index, newOptions)}
|
||||
listProps={{ bordered: false }}
|
||||
>
|
||||
{(list) => list}
|
||||
</EuiSelectable>
|
||||
{group.skipped_fields > 0 && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiText
|
||||
size="s"
|
||||
color="subdued"
|
||||
data-test-subj={`skipped_fields_${index}`}
|
||||
>
|
||||
<EuiIcon type="eyeClosed" />
|
||||
{` `}
|
||||
<FormattedMessage
|
||||
id="xpack.searchPlayground.viewQuery.flyout.hiddenFields"
|
||||
defaultMessage="{skippedFields} fields are hidden."
|
||||
values={{ skippedFields: group.skipped_fields }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
href={docLinks.chatPlayground}
|
||||
target="_blank"
|
||||
data-test-subj="context-optimization-documentation-link"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.searchPlayground.viewQuery.flyout.learnMoreLink"
|
||||
defaultMessage="Learn more."
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</EuiAccordion>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -43,6 +43,7 @@ describe.skip('useSourceIndicesFields Hook', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: ['field1'],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -144,6 +145,7 @@ describe.skip('useSourceIndicesFields Hook', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -196,6 +198,7 @@ describe.skip('useSourceIndicesFields Hook', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ export const PlaygroundProvider: React.FC<PlaygroundProviderProps> = ({
|
|||
const form = useForm<ChatForm>({
|
||||
defaultValues: {
|
||||
prompt: 'You are an assistant for question-answering tasks.',
|
||||
doc_size: 5,
|
||||
doc_size: 3,
|
||||
source_fields: [],
|
||||
indices: defaultValues?.indices || [],
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -55,6 +56,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -92,6 +94,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [
|
||||
|
@ -100,6 +103,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -133,6 +137,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [
|
||||
|
@ -141,6 +146,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -196,6 +202,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -228,6 +235,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -257,6 +265,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: ['content', 'title'],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [
|
||||
|
@ -265,6 +274,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -321,6 +331,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: ['content', 'title'],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [
|
||||
|
@ -329,6 +340,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -390,6 +402,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: ['content', 'title'],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [
|
||||
|
@ -398,6 +411,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -435,6 +449,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: ['content', 'title'],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -487,6 +502,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -509,6 +525,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [
|
||||
|
@ -524,6 +541,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -549,6 +567,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
index2: {
|
||||
elser_query_fields: [
|
||||
|
@ -564,6 +583,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -582,6 +602,7 @@ describe('create_query', () => {
|
|||
],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -595,6 +616,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: ['title', 'text', 'content'],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -610,6 +632,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: ['unknown1', 'unknown2'],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -659,6 +682,7 @@ describe('create_query', () => {
|
|||
'url_path_dir2',
|
||||
'url_path_dir1',
|
||||
],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -674,6 +698,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -691,6 +716,7 @@ describe('create_query', () => {
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: ['non_suggested_field'],
|
||||
skipped_fields: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ describe('fetch_query_source_fields', () => {
|
|||
indices: ['workplace_index'],
|
||||
},
|
||||
],
|
||||
skipped_fields: 8,
|
||||
source_fields: ['metadata.summary', 'metadata.rolePermissions', 'text', 'metadata.name'],
|
||||
},
|
||||
workplace_index2: {
|
||||
|
@ -58,6 +59,7 @@ describe('fetch_query_source_fields', () => {
|
|||
'metadata.name',
|
||||
],
|
||||
dense_vector_query_fields: [],
|
||||
skipped_fields: 8,
|
||||
elser_query_fields: [
|
||||
{
|
||||
field: 'content_vector.tokens',
|
||||
|
@ -110,6 +112,7 @@ describe('fetch_query_source_fields', () => {
|
|||
},
|
||||
],
|
||||
elser_query_fields: [],
|
||||
skipped_fields: 30,
|
||||
source_fields: [
|
||||
'page_content_key',
|
||||
'title',
|
||||
|
@ -150,11 +153,13 @@ describe('fetch_query_source_fields', () => {
|
|||
},
|
||||
],
|
||||
source_fields: ['body_content', 'headings', 'title'],
|
||||
skipped_fields: 4,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the correct fields for a document first index', () => {
|
||||
// Skips the nested dense vector field.
|
||||
expect(
|
||||
parseFieldsCapabilities(DENSE_VECTOR_DOCUMENT_FIRST_FIELD_CAPS, [
|
||||
{
|
||||
|
@ -174,14 +179,7 @@ describe('fetch_query_source_fields', () => {
|
|||
'metadata.summary',
|
||||
'metadata.content',
|
||||
],
|
||||
dense_vector_query_fields: [
|
||||
{
|
||||
field: 'passages.vector.predicted_value',
|
||||
model_id: '.multilingual-e5-small',
|
||||
nested: true,
|
||||
indices: ['workplace_index_nested'],
|
||||
},
|
||||
],
|
||||
dense_vector_query_fields: [],
|
||||
elser_query_fields: [],
|
||||
source_fields: [
|
||||
'metadata.category',
|
||||
|
@ -193,6 +191,7 @@ describe('fetch_query_source_fields', () => {
|
|||
'metadata.summary',
|
||||
'metadata.content',
|
||||
],
|
||||
skipped_fields: 18,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { FieldCapsResponse, SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
|
||||
import { get } from 'lodash';
|
||||
import { get, has } from 'lodash';
|
||||
import { IndicesQuerySourceFields } from '../types';
|
||||
|
||||
export const fetchFields = async (
|
||||
|
@ -45,10 +45,23 @@ export const fetchFields = async (
|
|||
return parseFieldsCapabilities(fieldCapabilities, indexDocs);
|
||||
};
|
||||
|
||||
const INFERENCE_MODEL_FIELD_REGEXP = /\.predicted_value|\.tokens/;
|
||||
|
||||
const hasModelField = (field: string, indexDoc: any, nestedField: string | false) => {
|
||||
if (field.match(INFERENCE_MODEL_FIELD_REGEXP)) {
|
||||
const path = nestedField ? field.replace(`${nestedField}.`, `${nestedField}[0].`) : field;
|
||||
return has(
|
||||
indexDoc.doc,
|
||||
`_source.${[path.replace(INFERENCE_MODEL_FIELD_REGEXP, '.model_id')]}`
|
||||
);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const getModelField = (field: string, indexDoc: any, nestedField: string | false) => {
|
||||
// If the field is nested, we need to get the first occurrence as its an array
|
||||
const path = nestedField ? field.replace(`${nestedField}.`, `${nestedField}[0].`) : field;
|
||||
return get(indexDoc.doc, `_source.${[path.replace(/\.predicted_value|\.tokens/, '.model_id')]}`);
|
||||
return get(indexDoc.doc, `_source.${[path.replace(INFERENCE_MODEL_FIELD_REGEXP, '.model_id')]}`);
|
||||
};
|
||||
|
||||
const isFieldNested = (field: string, fieldCapsResponse: FieldCapsResponse) => {
|
||||
|
@ -83,6 +96,7 @@ export const parseFieldsCapabilities = (
|
|||
dense_vector_query_fields: [],
|
||||
bm25_query_fields: [],
|
||||
source_fields: [],
|
||||
skipped_fields: 0,
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
@ -106,25 +120,41 @@ export const parseFieldsCapabilities = (
|
|||
if ('rank_features' in field || 'sparse_vector' in field) {
|
||||
const nestedField = isFieldNested(fieldKey, fieldCapsResponse);
|
||||
|
||||
const elserModelField = {
|
||||
field: fieldKey,
|
||||
model_id: getModelField(fieldKey, indexDoc, nestedField),
|
||||
nested: !!isFieldNested(fieldKey, fieldCapsResponse),
|
||||
indices: indicesPresentIn,
|
||||
};
|
||||
acc[index].elser_query_fields.push(elserModelField);
|
||||
if (!nestedField) {
|
||||
const elserModelField = {
|
||||
field: fieldKey,
|
||||
model_id: getModelField(fieldKey, indexDoc, nestedField),
|
||||
nested: !!isFieldNested(fieldKey, fieldCapsResponse),
|
||||
indices: indicesPresentIn,
|
||||
};
|
||||
acc[index].elser_query_fields.push(elserModelField);
|
||||
} else {
|
||||
acc[index].skipped_fields++;
|
||||
}
|
||||
} else if ('dense_vector' in field) {
|
||||
const nestedField = isFieldNested(fieldKey, fieldCapsResponse);
|
||||
const denseVectorField = {
|
||||
field: fieldKey,
|
||||
model_id: getModelField(fieldKey, indexDoc, nestedField),
|
||||
nested: !!nestedField,
|
||||
indices: indicesPresentIn,
|
||||
};
|
||||
acc[index].dense_vector_query_fields.push(denseVectorField);
|
||||
|
||||
// Check if the dense vector field has a model_id associated with it
|
||||
// skip this field if has no model associated with it
|
||||
// and the vectors were embedded outside of stack
|
||||
if (hasModelField(fieldKey, indexDoc, nestedField) && !nestedField) {
|
||||
const denseVectorField = {
|
||||
field: fieldKey,
|
||||
model_id: getModelField(fieldKey, indexDoc, nestedField),
|
||||
nested: !!nestedField,
|
||||
indices: indicesPresentIn,
|
||||
};
|
||||
acc[index].dense_vector_query_fields.push(denseVectorField);
|
||||
} else {
|
||||
acc[index].skipped_fields++;
|
||||
}
|
||||
} else if ('text' in field && field.text.searchable && shouldIgnoreField(fieldKey)) {
|
||||
acc[index].bm25_query_fields.push(fieldKey);
|
||||
acc[index].source_fields.push(fieldKey);
|
||||
} else {
|
||||
if (fieldKey !== '_id' && fieldKey !== '_index' && fieldKey !== '_type') {
|
||||
acc[index].skipped_fields++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue