mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.16`: - [[Search] [Playground] [Bug] Add playground index validation (#201032)](https://github.com/elastic/kibana/pull/201032) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Yan Savitski","email":"yan.savitski@elastic.co"},"sourceCommit":{"committedDate":"2024-11-25T21:06:54Z","message":"[Search] [Playground] [Bug] Add playground index validation (#201032)\n\nFix bug when localStorage has invalid index, then playground chat page\r\nopens with empty source.","sha":"d99431e51ea976baa69d51d4f7ba30592adecf7e","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Search","backport:prev-minor"],"number":201032,"url":"https://github.com/elastic/kibana/pull/201032","mergeCommit":{"message":"[Search] [Playground] [Bug] Add playground index validation (#201032)\n\nFix bug when localStorage has invalid index, then playground chat page\r\nopens with empty source.","sha":"d99431e51ea976baa69d51d4f7ba30592adecf7e"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201032","number":201032,"mergeCommit":{"message":"[Search] [Playground] [Bug] Add playground index validation (#201032)\n\nFix bug when localStorage has invalid index, then playground chat page\r\nopens with empty source.","sha":"d99431e51ea976baa69d51d4f7ba30592adecf7e"}}]}] BACKPORT-->
This commit is contained in:
parent
327336da5d
commit
fd924c247a
9 changed files with 100 additions and 25 deletions
|
@ -49,6 +49,7 @@ describe('SelectIndicesFlyout', () => {
|
|||
mockedUseQueryIndices.mockReturnValue({
|
||||
indices: ['index1', 'index2', 'index3'],
|
||||
isLoading: false,
|
||||
isFetched: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ interface SelectIndicesFlyout {
|
|||
|
||||
export const SelectIndicesFlyout: React.FC<SelectIndicesFlyout> = ({ onClose }) => {
|
||||
const [query, setQuery] = useState<string>('');
|
||||
const { indices, isLoading: isIndicesLoading } = useQueryIndices(query);
|
||||
const { indices, isLoading: isIndicesLoading } = useQueryIndices({ query });
|
||||
const { indices: selectedIndices, setIndices: setSelectedIndices } = useSourceIndicesFields();
|
||||
const [selectedTempIndices, setSelectedTempIndices] = useState<string[]>(selectedIndices);
|
||||
const handleSelectOptions = (options: EuiSelectableOption[]) => {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { useEffect, useState } from 'react';
|
||||
import { useQueryIndices } from './use_query_indices';
|
||||
|
||||
export const useIndicesValidation = (unvalidatedIndices: string[]) => {
|
||||
const [isValidated, setIsValidated] = useState<boolean>(false);
|
||||
const [validIndices, setValidIndices] = useState<string[]>([]);
|
||||
const { indices, isFetched: isIndicesLoaded } = useQueryIndices({
|
||||
query: unvalidatedIndices.join(','),
|
||||
exact: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isIndicesLoaded) {
|
||||
setValidIndices(indices.filter((index) => unvalidatedIndices.includes(index)));
|
||||
setIsValidated(true);
|
||||
}
|
||||
}, [unvalidatedIndices, indices, isIndicesLoaded]);
|
||||
|
||||
return { isValidated, validIndices };
|
||||
};
|
|
@ -11,26 +11,41 @@ import { useKibana } from './use_kibana';
|
|||
import { APIRoutes } from '../types';
|
||||
|
||||
export const useQueryIndices = (
|
||||
query: string = ''
|
||||
): { indices: IndexName[]; isLoading: boolean } => {
|
||||
{
|
||||
query,
|
||||
exact,
|
||||
}: {
|
||||
query?: string;
|
||||
exact?: boolean;
|
||||
} = { query: '', exact: false }
|
||||
): { indices: IndexName[]; isLoading: boolean; isFetched: boolean } => {
|
||||
const { services } = useKibana();
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
const { data, isLoading, isFetched } = useQuery({
|
||||
queryKey: ['indices', query],
|
||||
queryFn: async () => {
|
||||
const response = await services.http.get<{
|
||||
indices: string[];
|
||||
}>(APIRoutes.GET_INDICES, {
|
||||
query: {
|
||||
search_query: query,
|
||||
size: 10,
|
||||
},
|
||||
});
|
||||
try {
|
||||
const response = await services.http.get<{
|
||||
indices: string[];
|
||||
}>(APIRoutes.GET_INDICES, {
|
||||
query: {
|
||||
search_query: query,
|
||||
exact,
|
||||
size: 50,
|
||||
},
|
||||
});
|
||||
|
||||
return response.indices;
|
||||
return response.indices;
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 404) {
|
||||
return [];
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
initialData: [],
|
||||
});
|
||||
|
||||
return { indices: data, isLoading };
|
||||
return { indices: data, isLoading, isFetched };
|
||||
};
|
||||
|
|
|
@ -19,6 +19,9 @@ jest.mock('../hooks/use_llms_models');
|
|||
jest.mock('react-router-dom-v5-compat', () => ({
|
||||
useSearchParams: jest.fn(() => [{ get: jest.fn() }]),
|
||||
}));
|
||||
jest.mock('../hooks/use_indices_validation', () => ({
|
||||
useIndicesValidation: jest.fn((indices) => ({ isValidated: true, validIndices: indices })),
|
||||
}));
|
||||
|
||||
let formHookSpy: jest.SpyInstance;
|
||||
|
||||
|
@ -216,6 +219,7 @@ describe('FormProvider', () => {
|
|||
});
|
||||
|
||||
it('updates indices from search params', async () => {
|
||||
expect.assertions(1);
|
||||
const mockSearchParams = new URLSearchParams();
|
||||
mockSearchParams.get = jest.fn().mockReturnValue('new-index');
|
||||
mockUseSearchParams.mockReturnValue([mockSearchParams]);
|
||||
|
@ -237,10 +241,12 @@ describe('FormProvider', () => {
|
|||
</FormProvider>
|
||||
);
|
||||
|
||||
const { getValues } = formHookSpy.mock.results[0].value;
|
||||
await act(async () => {
|
||||
const { getValues } = formHookSpy.mock.results[0].value;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getValues(ChatFormFields.indices)).toEqual(['new-index']);
|
||||
await waitFor(() => {
|
||||
expect(getValues(ChatFormFields.indices)).toEqual(['new-index']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { FormProvider as ReactHookFormProvider, useForm } from 'react-hook-form';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { useIndicesValidation } from '../hooks/use_indices_validation';
|
||||
import { useLoadFieldsByIndices } from '../hooks/use_load_fields_by_indices';
|
||||
import { ChatForm, ChatFormFields } from '../types';
|
||||
import { useLLMsModels } from '../hooks/use_llms_models';
|
||||
|
@ -53,16 +54,27 @@ export const FormProvider: React.FC<React.PropsWithChildren<FormProviderProps>>
|
|||
}) => {
|
||||
const models = useLLMsModels();
|
||||
const [searchParams] = useSearchParams();
|
||||
const index = useMemo(() => searchParams.get('default-index'), [searchParams]);
|
||||
const defaultIndex = useMemo(() => {
|
||||
const index = searchParams.get('default-index');
|
||||
|
||||
return index ? [index] : null;
|
||||
}, [searchParams]);
|
||||
const sessionState = useMemo(() => getLocalSession(storage), [storage]);
|
||||
const form = useForm<ChatForm>({
|
||||
defaultValues: {
|
||||
...sessionState,
|
||||
indices: index ? [index] : sessionState.indices,
|
||||
indices: [],
|
||||
search_query: '',
|
||||
},
|
||||
});
|
||||
useLoadFieldsByIndices({ watch: form.watch, setValue: form.setValue, getValues: form.getValues });
|
||||
const { isValidated: isValidatedIndices, validIndices } = useIndicesValidation(
|
||||
defaultIndex || sessionState.indices || []
|
||||
);
|
||||
useLoadFieldsByIndices({
|
||||
watch: form.watch,
|
||||
setValue: form.setValue,
|
||||
getValues: form.getValues,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = form.watch((values) =>
|
||||
|
@ -80,5 +92,11 @@ export const FormProvider: React.FC<React.PropsWithChildren<FormProviderProps>>
|
|||
}
|
||||
}, [form, models]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isValidatedIndices) {
|
||||
form.setValue(ChatFormFields.indices, validIndices);
|
||||
}
|
||||
}, [form, isValidatedIndices, validIndices]);
|
||||
|
||||
return <ReactHookFormProvider {...form}>{children}</ReactHookFormProvider>;
|
||||
};
|
||||
|
|
|
@ -21,11 +21,12 @@ function isClosed(index: IndicesIndexState): boolean {
|
|||
|
||||
export const fetchIndices = async (
|
||||
client: ElasticsearchClient,
|
||||
searchQuery: string | undefined
|
||||
searchQuery: string | undefined,
|
||||
{ exact }: { exact?: boolean } = { exact: false }
|
||||
): Promise<{
|
||||
indexNames: string[];
|
||||
}> => {
|
||||
const indexPattern = searchQuery ? `*${searchQuery}*` : '*';
|
||||
const indexPattern = exact && searchQuery ? searchQuery : searchQuery ? `*${searchQuery}*` : '*';
|
||||
const allIndexMatches = await client.indices.get({
|
||||
expand_wildcards: ['open'],
|
||||
// for better performance only compute aliases and settings of indices but not mappings
|
||||
|
|
|
@ -181,17 +181,17 @@ export function defineRoutes({
|
|||
query: schema.object({
|
||||
search_query: schema.maybe(schema.string()),
|
||||
size: schema.number({ defaultValue: 10, min: 0 }),
|
||||
exact: schema.maybe(schema.boolean({ defaultValue: false })),
|
||||
}),
|
||||
},
|
||||
},
|
||||
errorHandler(logger)(async (context, request, response) => {
|
||||
const { search_query: searchQuery, size } = request.query;
|
||||
const { search_query: searchQuery, exact, size } = request.query;
|
||||
const {
|
||||
client: { asCurrentUser },
|
||||
} = (await context.core).elasticsearch;
|
||||
|
||||
const { indexNames } = await fetchIndices(asCurrentUser, searchQuery);
|
||||
|
||||
const { indexNames } = await fetchIndices(asCurrentUser, searchQuery, { exact });
|
||||
const indexNameSlice = indexNames.slice(0, size).filter(isNotNullish);
|
||||
|
||||
return response.ok({
|
||||
|
|
|
@ -129,6 +129,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectToSelectIndicesAndLoadChat();
|
||||
});
|
||||
|
||||
it('load start page after removing selected index', async () => {
|
||||
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectToSelectIndicesAndLoadChat();
|
||||
await esArchiver.unload(esArchiveIndex);
|
||||
await browser.refresh();
|
||||
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectCreateIndexButtonToExists();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await removeOpenAIConnector?.();
|
||||
await esArchiver.unload(esArchiveIndex);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue