From 02215924d87fd8a45f4b9d57351171cfcf584a61 Mon Sep 17 00:00:00 2001 From: Joanne Wang Date: Mon, 11 Nov 2024 22:41:52 -0800 Subject: [PATCH 1/6] update sample query impl and fix saved query load Signed-off-by: Joanne Wang --- .../query_string/dataset_service/types.ts | 2 +- .../public/ui/filter_bar/filter_options.tsx | 4 +- .../open_saved_query_flyout.tsx | 52 +++++++++++++---- .../saved_query_management_component.tsx | 5 +- .../ui/search_bar/create_search_bar.tsx | 1 + .../data/public/ui/search_bar/search_bar.tsx | 10 +++- .../components/no_results/no_results.tsx | 57 ++++++++++++------- 7 files changed, 94 insertions(+), 37 deletions(-) diff --git a/src/plugins/data/public/query/query_string/dataset_service/types.ts b/src/plugins/data/public/query/query_string/dataset_service/types.ts index f303fa6af56d..1e5af41ef785 100644 --- a/src/plugins/data/public/query/query_string/dataset_service/types.ts +++ b/src/plugins/data/public/query/query_string/dataset_service/types.ts @@ -83,5 +83,5 @@ export interface DatasetTypeConfig { /** * Returns a list of sample queries for this dataset type */ - getSampleQueries?: (dataset: Dataset, language: string) => any; + getSampleQueries?: (dataset?: Dataset, language?: string) => Promise | any; } diff --git a/src/plugins/data/public/ui/filter_bar/filter_options.tsx b/src/plugins/data/public/ui/filter_bar/filter_options.tsx index 3cda39731fa7..4af53fa28df1 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_options.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_options.tsx @@ -59,7 +59,7 @@ import { import { FilterEditor } from './filter_editor'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { SavedQueryManagementComponent } from '../saved_query_management'; -import { SavedQuery, SavedQueryService } from '../../query'; +import { QueryStringManager, SavedQuery, SavedQueryService } from '../../query'; import { SavedQueryMeta } from '../saved_query_form'; import { getUseNewSavedQueriesUI } from '../../services'; @@ -79,6 +79,7 @@ interface Props { useSaveQueryMenu: boolean; isQueryEditorControl: boolean; saveQuery: (savedQueryMeta: SavedQueryMeta, saveAsNew?: boolean) => Promise; + queryStringManager: QueryStringManager; } const maxFilterWidth = 600; @@ -310,6 +311,7 @@ const FilterOptionsUI = (props: Props) => { key={'savedQueryManagement'} useNewSavedQueryUI={getUseNewSavedQueriesUI()} saveQuery={props.saveQuery} + queryStringManager={props.queryStringManager} />, ]} data-test-subj="save-query-panel" diff --git a/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx b/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx index c7f13f27db08..4a1ca3c8a128 100644 --- a/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx +++ b/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx @@ -23,14 +23,16 @@ import { } from '@elastic/eui'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { i18n } from '@osd/i18n'; -import { SavedQuery, SavedQueryService } from '../../query'; +import { QueryStringManager, SavedQuery, SavedQueryService } from '../../query'; import { SavedQueryCard } from './saved_query_card'; +import { Query } from '../../../common'; export interface OpenSavedQueryFlyoutProps { savedQueryService: SavedQueryService; onClose: () => void; onQueryOpen: (query: SavedQuery) => void; handleQueryDelete: (query: SavedQuery) => Promise; + queryStringManager: QueryStringManager; } interface SavedQuerySearchableItem { @@ -47,6 +49,7 @@ export function OpenSavedQueryFlyout({ onClose, onQueryOpen, handleQueryDelete, + queryStringManager, }: OpenSavedQueryFlyoutProps) { const [selectedTabId, setSelectedTabId] = useState('mutable-saved-queries'); const [savedQueries, setSavedQueries] = useState([]); @@ -60,17 +63,29 @@ export function OpenSavedQueryFlyout({ const [selectedQuery, setSelectedQuery] = useState(undefined); const [searchQuery, setSearchQuery] = useState(EuiSearchBar.Query.MATCH_ALL); + const currentTabIdRef = useRef(selectedTabId); + const fetchAllSavedQueriesForSelectedTab = useCallback(async () => { - const allQueries = await savedQueryService.getAllSavedQueries(); - const templateQueriesPresent = allQueries.some((q) => q.attributes.isTemplate); - const queriesForSelectedTab = allQueries.filter( - (q) => - (selectedTabId === 'mutable-saved-queries' && !q.attributes.isTemplate) || - (selectedTabId === 'template-saved-queries' && q.attributes.isTemplate) - ); - setSavedQueries(queriesForSelectedTab); - setHasTemplateQueries(templateQueriesPresent); - }, [savedQueryService, selectedTabId, setSavedQueries]); + if (queryStringManager.getQuery()?.dataset?.type === 'SECURITY_LAKE') { + setHasTemplateQueries(true); + } + if (currentTabIdRef.current === 'mutable-saved-queries') { + const allQueries = await savedQueryService.getAllSavedQueries(); + const mutableSavedQueries = allQueries.filter((q) => !q.attributes.isTemplate); + if (currentTabIdRef.current === 'mutable-saved-queries') { + setSavedQueries(mutableSavedQueries); + } + } else if (currentTabIdRef.current === 'template-saved-queries') { + const query = queryStringManager.getQuery(); + if (query?.dataset?.type) { + const templateQueries = await queryStringManager + .getDatasetService() + ?.getType(query.dataset.type) + ?.getSampleQueries?.(); + if (Array.isArray(templateQueries)) setSavedQueries(templateQueries); + } + } + }, [savedQueryService, currentTabIdRef, setSavedQueries, queryStringManager]); const updatePageIndex = useCallback((index: number) => { pager.current.goToPageIndex(index); @@ -252,6 +267,7 @@ export function OpenSavedQueryFlyout({ initialSelectedTab={tabs[0]} onTabClick={(tab) => { setSelectedTabId(tab.id); + currentTabIdRef.current = tab.id; }} /> @@ -268,7 +284,19 @@ export function OpenSavedQueryFlyout({ fill onClick={() => { if (selectedQuery) { - onQueryOpen(selectedQuery); + if ( + // Template queries are not associated with data sources. Apply data source from current query + selectedQuery.attributes.isTemplate + ) { + const updatedQuery: Query = { + ...queryStringManager?.getQuery(), + query: selectedQuery.attributes.query.query, + language: selectedQuery.attributes.query.language, + }; + queryStringManager.setQuery(updatedQuery); + } else { + onQueryOpen(selectedQuery); + } onClose(); } }} diff --git a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx index 01f9b97e978f..44c5ef384966 100644 --- a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx @@ -45,7 +45,7 @@ import { import { i18n } from '@osd/i18n'; import React, { useCallback, useEffect, useState, Fragment, useRef } from 'react'; import { sortBy } from 'lodash'; -import { SavedQuery, SavedQueryService } from '../..'; +import { QueryStringManager, SavedQuery, SavedQueryService } from '../..'; import { SavedQueryListItem } from './saved_query_list_item'; import { toMountPoint, @@ -70,6 +70,7 @@ interface Props { onClearSavedQuery: () => void; closeMenuPopover: () => void; saveQuery: (savedQueryMeta: SavedQueryMeta, saveAsNew?: boolean) => Promise; + queryStringManager: QueryStringManager; } export function SavedQueryManagementComponent({ @@ -83,6 +84,7 @@ export function SavedQueryManagementComponent({ closeMenuPopover, useNewSavedQueryUI, saveQuery, + queryStringManager, }: Props) { const [savedQueries, setSavedQueries] = useState([] as SavedQuery[]); const [count, setTotalCount] = useState(0); @@ -256,6 +258,7 @@ export function SavedQueryManagementComponent({ onClose={() => openSavedQueryFlyout?.close().then()} onQueryOpen={onLoad} handleQueryDelete={handleDelete} + queryStringManager={queryStringManager} /> ) ); diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index f8b9694caabc..d3f89d0f559d 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -202,6 +202,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) isRefreshPaused={refreshInterval.pause} filters={filters} query={query} + queryStringManager={data.query.queryString} onFiltersUpdated={defaultFiltersUpdated(data.query)} onRefreshChange={defaultOnRefreshChange(data.query)} savedQuery={savedQuery} diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index 1f1b20b8c952..3cd6cdcca25e 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -38,7 +38,13 @@ import { withOpenSearchDashboards, } from '../../../../opensearch_dashboards_react/public'; import { Filter, IIndexPattern, Query, TimeRange, UI_SETTINGS } from '../../../common'; -import { SavedQuery, SavedQueryAttributes, TimeHistoryContract, QueryStatus } from '../../query'; +import { + SavedQuery, + SavedQueryAttributes, + TimeHistoryContract, + QueryStatus, + QueryStringManager, +} from '../../query'; import { IDataPluginServices } from '../../types'; import { FilterBar } from '../filter_bar/filter_bar'; import { QueryEditorTopRow } from '../query_editor'; @@ -95,6 +101,7 @@ export interface SearchBarOwnProps { onRefresh?: (payload: { dateRange: TimeRange }) => void; indicateNoData?: boolean; queryStatus?: QueryStatus; + queryStringManager: QueryStringManager; } export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps; @@ -467,6 +474,7 @@ class SearchBarUI extends Component { useSaveQueryMenu={useSaveQueryMenu} isQueryEditorControl={isQueryEditorControl} saveQuery={this.onSave} + queryStringManager={this.props.queryStringManager} /> ) ); diff --git a/src/plugins/discover/public/application/components/no_results/no_results.tsx b/src/plugins/discover/public/application/components/no_results/no_results.tsx index b1ec382b4fd5..24a4b80c7204 100644 --- a/src/plugins/discover/public/application/components/no_results/no_results.tsx +++ b/src/plugins/discover/public/application/components/no_results/no_results.tsx @@ -174,6 +174,7 @@ export const DiscoverNoResults = ({ queryString, query, savedQuery, timeFieldNam // } const [savedQueries, setSavedQueries] = useState([]); + const [sampleQueries, setSampleQueries] = useState([]); useEffect(() => { const fetchSavedQueries = async () => { @@ -186,6 +187,39 @@ export const DiscoverNoResults = ({ queryString, query, savedQuery, timeFieldNam fetchSavedQueries(); }, [setSavedQueries, query, savedQuery]); + useEffect(() => { + // Samples for the language + const newSampleQueries: any = []; + if (query?.language) { + const languageSampleQueries = queryString.getLanguageService()?.getLanguage(query.language) + ?.sampleQueries; + if (Array.isArray(languageSampleQueries)) { + newSampleQueries.push(...languageSampleQueries); + } + } + + // Samples for the dataset type + if (query?.dataset?.type) { + const datasetType = queryString.getDatasetService()?.getType(query.dataset.type); + if (datasetType?.getSampleQueries) { + const sampleQueriesResponse = datasetType.getSampleQueries(query.dataset, query.language); + if (Array.isArray(sampleQueriesResponse)) { + setSampleQueries([...sampleQueriesResponse, ...newSampleQueries]); + } else if (sampleQueriesResponse instanceof Promise) { + sampleQueriesResponse + .then((datasetSampleQueries: any) => { + if (Array.isArray(datasetSampleQueries)) { + setSampleQueries([...datasetSampleQueries, ...newSampleQueries]); + } + }) + .catch((error: any) => { + // noop + }); + } + } + } + }, [queryString, query]); + const tabs = useMemo(() => { const buildSampleQueryBlock = (sampleTitle: string, sampleQuery: string) => { return ( @@ -197,25 +231,6 @@ export const DiscoverNoResults = ({ queryString, query, savedQuery, timeFieldNam ); }; - - const sampleQueries = []; - - // Samples for the dataset type - if (query?.dataset?.type) { - const datasetSampleQueries = queryString - .getDatasetService() - ?.getType(query.dataset.type) - ?.getSampleQueries?.(query.dataset, query.language); - if (Array.isArray(datasetSampleQueries)) sampleQueries.push(...datasetSampleQueries); - } - - // Samples for the language - if (query?.language) { - const languageSampleQueries = queryString.getLanguageService()?.getLanguage(query.language) - ?.sampleQueries; - if (Array.isArray(languageSampleQueries)) sampleQueries.push(...languageSampleQueries); - } - return [ ...(sampleQueries.length > 0 ? [ @@ -229,7 +244,7 @@ export const DiscoverNoResults = ({ queryString, query, savedQuery, timeFieldNam {sampleQueries .slice(0, 5) - .map((sampleQuery) => + .map((sampleQuery: any) => buildSampleQueryBlock(sampleQuery.title, sampleQuery.query) )} @@ -256,7 +271,7 @@ export const DiscoverNoResults = ({ queryString, query, savedQuery, timeFieldNam ] : []), ]; - }, [queryString, query, savedQueries]); + }, [savedQueries, sampleQueries]); return ( From 5b4b3a88ddf36188ea612daf573da9d65689faa3 Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 06:54:21 +0000 Subject: [PATCH 2/6] Changeset file for PR #8848 created/updated --- changelogs/fragments/8848.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/8848.yml diff --git a/changelogs/fragments/8848.yml b/changelogs/fragments/8848.yml new file mode 100644 index 000000000000..528fb7c86f66 --- /dev/null +++ b/changelogs/fragments/8848.yml @@ -0,0 +1,2 @@ +fix: +- Fix saved query loading and update getSampleQuery interface ([#8848](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8848)) \ No newline at end of file From 0e07d48da42f15e9bcfcf74008706a47fffd0e41 Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 07:14:07 +0000 Subject: [PATCH 3/6] Changeset file for PR #8848 created/updated --- changelogs/fragments/8848.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/fragments/8848.yml b/changelogs/fragments/8848.yml index 528fb7c86f66..f8cc51214cf5 100644 --- a/changelogs/fragments/8848.yml +++ b/changelogs/fragments/8848.yml @@ -1,2 +1,2 @@ fix: -- Fix saved query loading and update getSampleQuery interface ([#8848](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8848)) \ No newline at end of file +- Fix template queries loading and update getSampleQuery interface ([#8848](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8848)) \ No newline at end of file From 10e8a9e175ac9c7bb65d35947cf52d898dfaee05 Mon Sep 17 00:00:00 2001 From: Joanne Wang Date: Tue, 12 Nov 2024 09:08:48 -0800 Subject: [PATCH 4/6] add loading spinner Signed-off-by: Joanne Wang --- .../open_saved_query_flyout.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx b/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx index 4a1ca3c8a128..c34e1cba5ff2 100644 --- a/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx +++ b/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx @@ -13,6 +13,7 @@ import { EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, + EuiLoadingSpinner, EuiSearchBar, EuiSearchBarProps, EuiSpacer, @@ -62,10 +63,11 @@ export function OpenSavedQueryFlyout({ const [languageFilterOptions, setLanguageFilterOptions] = useState([]); const [selectedQuery, setSelectedQuery] = useState(undefined); const [searchQuery, setSearchQuery] = useState(EuiSearchBar.Query.MATCH_ALL); - + const [isLoading, setIsLoading] = useState(false); const currentTabIdRef = useRef(selectedTabId); const fetchAllSavedQueriesForSelectedTab = useCallback(async () => { + setIsLoading(true); if (queryStringManager.getQuery()?.dataset?.type === 'SECURITY_LAKE') { setHasTemplateQueries(true); } @@ -85,6 +87,7 @@ export function OpenSavedQueryFlyout({ if (Array.isArray(templateQueries)) setSavedQueries(templateQueries); } } + setIsLoading(false); }, [savedQueryService, currentTabIdRef, setSavedQueries, queryStringManager]); const updatePageIndex = useCallback((index: number) => { @@ -194,7 +197,13 @@ export function OpenSavedQueryFlyout({ onChange={onChange} /> - {queriesOnCurrentPage.length > 0 ? ( + {isLoading ? ( + + + + + + ) : queriesOnCurrentPage.length > 0 ? ( queriesOnCurrentPage.map((query) => ( )} - {queriesOnCurrentPage.length > 0 && ( + {!isLoading && queriesOnCurrentPage.length > 0 && ( Date: Tue, 12 Nov 2024 12:45:00 -0800 Subject: [PATCH 5/6] check if tab is shown based on if sample query isTemplate Signed-off-by: Joanne Wang --- .../open_saved_query_flyout.tsx | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx b/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx index c34e1cba5ff2..41aa344bbaef 100644 --- a/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx +++ b/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.tsx @@ -68,9 +68,23 @@ export function OpenSavedQueryFlyout({ const fetchAllSavedQueriesForSelectedTab = useCallback(async () => { setIsLoading(true); - if (queryStringManager.getQuery()?.dataset?.type === 'SECURITY_LAKE') { - setHasTemplateQueries(true); + const query = queryStringManager.getQuery(); + let templateQueries: any[] = []; + + // fetch sample query based on dataset type + if (query?.dataset?.type) { + templateQueries = + (await queryStringManager + .getDatasetService() + ?.getType(query.dataset.type) + ?.getSampleQueries?.()) || []; + + // Check if any sample query has isTemplate set to true + const hasTemplates = templateQueries.some((q) => q?.attributes?.isTemplate); + setHasTemplateQueries(hasTemplates); } + + // Set queries based on the current tab if (currentTabIdRef.current === 'mutable-saved-queries') { const allQueries = await savedQueryService.getAllSavedQueries(); const mutableSavedQueries = allQueries.filter((q) => !q.attributes.isTemplate); @@ -78,14 +92,7 @@ export function OpenSavedQueryFlyout({ setSavedQueries(mutableSavedQueries); } } else if (currentTabIdRef.current === 'template-saved-queries') { - const query = queryStringManager.getQuery(); - if (query?.dataset?.type) { - const templateQueries = await queryStringManager - .getDatasetService() - ?.getType(query.dataset.type) - ?.getSampleQueries?.(); - if (Array.isArray(templateQueries)) setSavedQueries(templateQueries); - } + setSavedQueries(templateQueries); } setIsLoading(false); }, [savedQueryService, currentTabIdRef, setSavedQueries, queryStringManager]); From c73d3579b3c97c6cef0cc77a670865a1fabe06eb Mon Sep 17 00:00:00 2001 From: Riya Saxena Date: Tue, 12 Nov 2024 14:38:56 -0800 Subject: [PATCH 6/6] added UTs for svaed queries flyout Signed-off-by: Riya Saxena --- .../open_saved_query_flyout.test.tsx | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.test.tsx diff --git a/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.test.tsx b/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.test.tsx new file mode 100644 index 000000000000..8daaafe0fdcb --- /dev/null +++ b/src/plugins/data/public/ui/saved_query_flyouts/open_saved_query_flyout.test.tsx @@ -0,0 +1,228 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; +import { OpenSavedQueryFlyout } from './open_saved_query_flyout'; +import { createSavedQueryService } from '../../../public/query/saved_query/saved_query_service'; +import { applicationServiceMock, uiSettingsServiceMock } from '../../../../../core/public/mocks'; +import { SavedQueryAttributes } from '../../../public/query/saved_query/types'; +import '@testing-library/jest-dom'; +import { queryStringManagerMock } from '../../../../data/public/query/query_string/query_string_manager.mock'; + +const savedQueryAttributesWithTemplate: SavedQueryAttributes = { + title: 'foo', + description: 'bar', + query: { + language: 'kuery', + query: 'response:200', + dataset: 'my_dataset', + }, +}; + +const mockSavedObjectsClient = { + create: jest.fn(), + error: jest.fn(), + find: jest.fn(), + get: jest.fn(), + delete: jest.fn(), +}; + +mockSavedObjectsClient.create.mockReturnValue({ + id: 'foo', + attributes: { + ...savedQueryAttributesWithTemplate, + query: { + ...savedQueryAttributesWithTemplate.query, + }, + }, +}); + +jest.mock('./saved_query_card', () => ({ + SavedQueryCard: ({ + savedQuery = { + id: 'foo1', + attributes: savedQueryAttributesWithTemplate, + }, + onSelect, + handleQueryDelete, + }) => ( +
+
{savedQuery?.attributes?.title}
+ + +
+ ), +})); + +jest.mock('@osd/i18n', () => ({ + i18n: { + translate: jest.fn((id, { defaultMessage }) => defaultMessage), + }, +})); + +const mockSavedQueryService = createSavedQueryService( + // @ts-ignore + mockSavedObjectsClient, + { + application: applicationServiceMock.create(), + uiSettings: uiSettingsServiceMock.createStartContract(), + } +); + +const mockHandleQueryDelete = jest.fn(); +const mockOnQueryOpen = jest.fn(); +const mockOnClose = jest.fn(); + +const savedQueries = [ + { + id: '1', + attributes: { + title: 'Saved Query 1', + description: 'Description for Query 1', + query: { query: 'SELECT * FROM table1', language: 'sql' }, + }, + }, + { + id: '2', + attributes: { + title: 'Saved Query 2', + description: 'Description for Query 2', + query: { query: 'SELECT * FROM table2', language: 'sql' }, + }, + }, +]; + +jest.spyOn(mockSavedQueryService, 'getAllSavedQueries').mockResolvedValue(savedQueries); + +describe('OpenSavedQueryFlyout', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render the flyout with correct tabs and content', async () => { + render( + + ); + + const savedQueriesTextElements = screen.getAllByText('Saved queries'); + + expect(savedQueriesTextElements).toHaveLength(2); + + await waitFor(() => screen.getByPlaceholderText('Search')); + + await waitFor(() => screen.getByText('Saved Query 1')); + await waitFor(() => screen.getByText('Saved Query 2')); + + const openQueryButton = screen.getByText('Open query'); + + fireEvent.change(screen.getByPlaceholderText('Search'), { target: { value: 'Saved Query 1' } }); + + await waitFor(() => screen.getByText('Saved Query 1')); + expect(screen.queryByText('Saved Query 2')).not.toBeInTheDocument(); + + fireEvent.click(screen.getByText('Saved Query 1')); + + expect(openQueryButton).toBeEnabled(); + }); + + it('should filter saved queries based on search input', async () => { + render( + + ); + + await waitFor(() => screen.getByText('Saved Query 1')); + await waitFor(() => screen.getByText('Saved Query 2')); + + const searchBar = screen.getByPlaceholderText('Search'); + fireEvent.change(searchBar, { target: { value: 'Saved Query 1' } }); + + expect(screen.getByText('Saved Query 1')).toBeInTheDocument(); + expect(screen.queryByText('Saved Query 2')).toBeNull(); + }); + + it('should select a query when clicking on it and enable the "Open query" button', async () => { + render( + + ); + + await waitFor(() => screen.getByText('Saved Query 1')); + + fireEvent.click(screen.getByText('Saved Query 1')); + + expect(screen.getByText('Open query')).toBeEnabled(); + }); + + it('should call handleQueryDelete when deleting a query', async () => { + mockHandleQueryDelete.mockResolvedValueOnce(); + render( + + ); + + await waitFor(() => screen.getByText('Saved Query 1')); + + const deleteButtons = screen.getAllByText('Delete'); + + fireEvent.click(deleteButtons[0]); + + await waitFor(() => { + expect(mockHandleQueryDelete).toHaveBeenCalledWith({ + id: '1', + attributes: { + description: 'Description for Query 1', + query: { + language: 'sql', + query: 'SELECT * FROM table1', + }, + title: 'Saved Query 1', + }, + }); + }); + expect(mockHandleQueryDelete).toHaveBeenCalledTimes(1); + }); + + it('should handle pagination controls correctly', async () => { + render( + + ); + + await waitFor(() => screen.getByText('Saved Query 1')); + + const pageSizeButton = await screen.findByText(/10/); + fireEvent.click(pageSizeButton); + + expect(mockSavedQueryService.getAllSavedQueries).toHaveBeenCalled(); + }); +});