diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/AddNewService/postgres.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/AddNewService/postgres.spec.js index beafc7175bc..e703819eebf 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/AddNewService/postgres.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/AddNewService/postgres.spec.js @@ -161,11 +161,7 @@ describe('Postgres Ingestion', () => { ); visitEntityDetailsPage(tableName, serviceName, 'tables'); verifyResponseStatusCode('@entityDetailsPage', 200); - interceptURL( - 'GET', - '/api/v1/search/query?q=&index=query_search_index*', - 'queriesTab' - ); + interceptURL('GET', '/api/v1/queries?*', 'queriesTab'); cy.get('[data-testid="Queries"]').should('be.visible').trigger('click'); verifyResponseStatusCode('@queriesTab', 200); // Validate that the triggered query is visible in the queries container diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryFilters/QueryFilters.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryFilters/QueryFilters.component.tsx index 9de4d98c662..c7d0170d5a4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryFilters/QueryFilters.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/QueryFilters/QueryFilters.component.tsx @@ -15,6 +15,7 @@ import { Space, Typography } from 'antd'; import SearchDropdown from 'components/SearchDropdown/SearchDropdown'; import { SearchDropdownOption } from 'components/SearchDropdown/SearchDropdown.interface'; import { WILD_CARD_CHAR } from 'constants/char.constants'; +import { INITIAL_PAGING_VALUE } from 'constants/constants'; import { QUERY_PAGE_FILTER } from 'constants/Query.constant'; import { PROMISE_STATE } from 'enums/common.enum'; import { debounce, isEmpty } from 'lodash'; @@ -59,7 +60,6 @@ const QueryFilters = ({ onFilterChange }: QueryFiltersProps) => { ) => { setSelectedFilter((pre) => { const updatedFilter = { ...pre, [searchKey]: value }; - onFilterChange(updatedFilter); return updatedFilter; @@ -111,13 +111,13 @@ const QueryFilters = ({ onFilterChange }: QueryFiltersProps) => { } }; - const debounceOnUserSearch = debounce(onUserSearch, 400); - const debounceOnTeamSearch = debounce(onTeamSearch, 400); + const debounceOnUserSearch = debounce(onUserSearch, 500); + const debounceOnTeamSearch = debounce(onTeamSearch, 500); const getInitialUserAndTeam = () => { const promise = [ - getSearchedUsers(WILD_CARD_CHAR, 1), - getSearchedTeams(WILD_CARD_CHAR, 1), + getSearchedUsers(WILD_CARD_CHAR, INITIAL_PAGING_VALUE), + getSearchedTeams(WILD_CARD_CHAR, INITIAL_PAGING_VALUE), ]; Promise.allSettled(promise) .then((res) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.interface.ts index e6633edda2a..607dbf5cf3e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.interface.ts @@ -62,10 +62,13 @@ export interface QueryFiltersProps { onFilterChange: (value: QueryFiltersType) => void; } -export type QuerySearchParams = QueryFiltersType & { - page: string; - tableId: string; - query: string; +export type QuerySearchParams = { + queryFrom?: number; + after?: string; + tableId?: string; + query?: string; + user?: SearchDropdownOption[]; + team?: SearchDropdownOption[]; }; export type QuerySearchShouldFilterType = { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.test.tsx index 317a132ef5f..2a345598f61 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.test.tsx @@ -19,8 +19,8 @@ import { } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { searchQuery } from 'rest/searchAPI'; -import { MOCK_QUERIES_ES_DATA } from '../../mocks/Queries.mock'; +import { getQueriesList } from 'rest/queryAPI'; +import { MOCK_QUERIES, MOCK_QUERIES_ES_DATA } from '../../mocks/Queries.mock'; import TableQueries from './TableQueries'; import { TableQueriesProp } from './TableQueries.interface'; @@ -49,6 +49,14 @@ jest.mock('rest/searchAPI', () => ({ }) ), })); +jest.mock('rest/queryAPI', () => ({ + ...jest.requireActual('rest/queryAPI'), + getQueriesList: jest + .fn() + .mockImplementation(() => + Promise.resolve({ data: MOCK_QUERIES, paging: { total: 2 } }) + ), +})); jest.mock('components/PermissionProvider/PermissionProvider', () => ({ usePermissionProvider: jest .fn() @@ -89,9 +97,10 @@ describe('Test TableQueries Component', () => { }); it('Error placeholder should display if there is no data', async () => { - (searchQuery as jest.Mock).mockImplementationOnce(() => + (getQueriesList as jest.Mock).mockImplementationOnce(() => Promise.resolve({ - hits: { hits: [], total: { value: 0 } }, + data: [], + paging: { total: 0 }, }) ); render(, { @@ -105,9 +114,10 @@ describe('Test TableQueries Component', () => { }); it('If paging count is more than 10, pagination should be visible', async () => { - (searchQuery as jest.Mock).mockImplementationOnce(() => + (getQueriesList as jest.Mock).mockImplementationOnce(() => Promise.resolve({ - hits: { hits: [], total: { value: 11 } }, + data: MOCK_QUERIES, + paging: { total: 11 }, }) ); render(, { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.tsx index e3105b426ae..e9a61e2f0ae 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueries.tsx @@ -38,10 +38,20 @@ import Qs from 'qs'; import React, { FC, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory, useLocation } from 'react-router-dom'; -import { getQueryById, patchQueries, updateQueryVote } from 'rest/queryAPI'; +import { + getQueriesList, + getQueryById, + ListQueriesParams, + patchQueries, + updateQueryVote, +} from 'rest/queryAPI'; import { searchQuery } from 'rest/searchAPI'; import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils'; -import { createQueryFilter, parseSearchParams } from 'utils/Query/QueryUtils'; +import { + createQueryFilter, + parseSearchParams, + stringifySearchParams, +} from 'utils/Query/QueryUtils'; import { showErrorToast } from '../../utils/ToastUtils'; import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; import Loader from '../Loader/Loader'; @@ -65,10 +75,13 @@ const TableQueries: FC = ({ const { searchParams, selectedFilters } = useMemo(() => { const searchData = parseSearchParams(location.search); - const selectedFilters = { - user: searchData.user || [], - team: searchData.team || [], - }; + const selectedFilters = + searchData.user || searchData.team + ? { + user: searchData.user || [], + team: searchData.team || [], + } + : undefined; return { searchParams: searchData, @@ -87,13 +100,19 @@ const TableQueries: FC = ({ DEFAULT_ENTITY_PERMISSION ); const [currentPage, setCurrentPage] = useState( - Number(searchParams.page) || INITIAL_PAGING_VALUE + Number(searchParams.queryFrom) || INITIAL_PAGING_VALUE ); - const [appliedFilter, setAppliedFilter] = - useState(selectedFilters); + const [appliedFilter, setAppliedFilter] = useState< + QueryFiltersType | undefined + >(selectedFilters); const { getEntityPermission } = usePermissionProvider(); + const isNumberBasedPaging = useMemo( + () => Boolean(appliedFilter?.team.length || appliedFilter?.user.length), + [appliedFilter] + ); + const fetchResourcePermission = async () => { if (isUndefined(selectedQuery)) { return; @@ -165,6 +184,45 @@ const TableQueries: FC = ({ showErrorToast(error as AxiosError); } }; + const fetchTableQuery = async ( + params?: ListQueriesParams, + activePage?: number + ) => { + setIsLoading((pre) => ({ ...pre, query: true })); + try { + const queries = await getQueriesList({ + ...params, + limit: PAGE_SIZE, + entityId: tableId, + fields: 'owner,votes,tags,queryUsedIn', + }); + if (queries.data.length === 0) { + setIsError((pre) => ({ ...pre, page: true })); + } else { + setTableQueries(queries); + const selectedQueryData = searchParams.query + ? queries.data.find((query) => query.id === searchParams.query) || + queries.data[0] + : queries.data[0]; + setSelectedQuery(selectedQueryData); + + history.push({ + search: stringifySearchParams({ + tableId, + after: params?.after, + query: selectedQueryData.id, + queryFrom: activePage, + }), + }); + } + } catch (error) { + showErrorToast(error as AxiosError); + setIsError((pre) => ({ ...pre, page: true })); + } finally { + setIsLoading((pre) => ({ ...pre, query: false })); + } + }; + const fetchFilterData = async (value?: QueryFiltersType, page?: number) => { setIsLoading((pre) => ({ ...pre, query: true })); const allFilter = flatMap(value); @@ -196,13 +254,13 @@ const TableQueries: FC = ({ : queries[0]; setSelectedQuery(selectedQueryData); + history.push({ - search: Qs.stringify({ - ...searchParams, + search: stringifySearchParams({ ...value, - page: pageNumber, tableId, query: selectedQueryData.id, + queryFrom: pageNumber, }), }); if (queries.length === 0) { @@ -215,10 +273,14 @@ const TableQueries: FC = ({ } }; - const pagingHandler = (cursorType: string | number) => { + const pagingHandler = (cursorType: string | number, activePage?: number) => { + const { paging } = tableQueries; if (isNumber(cursorType)) { setCurrentPage(cursorType); fetchFilterData(appliedFilter, cursorType); + } else { + fetchTableQuery({ [cursorType]: paging[cursorType] }, activePage); + activePage && setCurrentPage(activePage); } }; @@ -238,20 +300,27 @@ const TableQueries: FC = ({ useEffect(() => { setIsLoading((pre) => ({ ...pre, page: true })); if (tableId && !isTableDeleted) { - fetchFilterData(selectedFilters, Number(searchParams.page)).finally( - () => { - setIsLoading((pre) => ({ ...pre, page: false })); - } - ); + const initialFetch = selectedFilters + ? fetchFilterData(selectedFilters, Number(searchParams.queryFrom)) + : fetchTableQuery({ after: searchParams?.after }); + initialFetch.finally(() => { + setIsLoading((pre) => ({ ...pre, page: false })); + }); } else { setIsLoading((pre) => ({ ...pre, page: false })); } }, [tableId]); const onOwnerFilterChange = (value: QueryFiltersType) => { + const { team, user } = value; + setIsError((pre) => ({ ...pre, search: false })); setAppliedFilter(value); - fetchFilterData(value, INITIAL_PAGING_VALUE); + if (team.length || user.length) { + fetchFilterData(value, INITIAL_PAGING_VALUE); + } else { + fetchTableQuery(); + } }; if (isLoading.page) { @@ -265,6 +334,30 @@ const TableQueries: FC = ({ ); } + const queryTabBody = isError.search ? ( + + + + ) : ( + tableQueries.data.map((query) => ( + + + + )) + ); + return ( @@ -276,39 +369,13 @@ const TableQueries: FC = ({ - {isLoading.query ? ( - - ) : isError.search ? ( - - - - ) : ( - tableQueries.data.map((query) => ( - - - - )) - )} + {isLoading.query ? : queryTabBody} + {tableQueries.paging.total > PAGE_SIZE && ( { // need to typecast into QuerySearchParams as Qs.parse returns as "Qs.ParsedQs" Type object ) as unknown as QuerySearchParams; }; +export const stringifySearchParams = (param: QuerySearchParams) => { + return Qs.stringify(param); +};