Move search to URL query string parameter rather than path (#10088)

* [WiP] Fix encoding of '%' in search query

* Use search query parameter for search instead of path

* Fix tests

---------

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
mosiac1 2023-02-21 14:35:07 +00:00 committed by GitHub
parent 18a519571e
commit 0b54d316ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 124 additions and 103 deletions

View File

@ -55,7 +55,7 @@ export const LEFT_PANEL_DETAILS = {
export const NAVBAR_DETAILS = {
explore: {
testid: '[data-testid="appbar-item-explore"]',
url: `${BASE_URL}/explore/tables/?page=1`,
url: `${BASE_URL}/explore/tables?page=1`,
},
quality: {
testid: '[data-testid="appbar-item-data-quality"]',

View File

@ -13,6 +13,7 @@
import { Modal } from 'antd';
import { debounce } from 'lodash';
import Qs from 'qs';
import { BaseSelectRef } from 'rc-select';
import React, {
FC,
@ -23,8 +24,7 @@ import React, {
useState,
} from 'react';
import { useHistory } from 'react-router-dom';
import AppState from '../../AppState';
import { getExplorePathWithSearch, ROUTES } from '../../constants/constants';
import { getExplorePath, ROUTES } from '../../constants/constants';
import { addToRecentSearched } from '../../utils/CommonUtils';
import { isCommandKeyPress, Keys } from '../../utils/KeyboardUtil';
import GlobalSearchSuggestions from './GlobalSearchSuggestions/GlobalSearchSuggestions';
@ -77,15 +77,20 @@ const GlobalSearchProvider: FC<Props> = ({ children }: Props) => {
const searchHandler = (value: string) => {
addToRecentSearched(value);
history.push({
pathname: getExplorePathWithSearch(
value,
location.pathname.startsWith(ROUTES.EXPLORE)
? AppState.explorePageTab
: 'tables'
),
search: location.search,
});
if (location.pathname.startsWith(ROUTES.EXPLORE)) {
// Already on explore page, only push search change
const paramsObject: Record<string, unknown> = Qs.parse(
location.search.startsWith('?')
? location.search.substr(1)
: location.search
);
history.push({
search: Qs.stringify({ ...paramsObject, search: value }),
});
} else {
// Outside Explore page
history.push(getExplorePath({ search: value }));
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {

View File

@ -16,7 +16,7 @@ import { isNil } from 'lodash';
import React, { FunctionComponent, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { getExplorePathWithSearch, ROUTES } from '../../constants/constants';
import { getExplorePath, ROUTES } from '../../constants/constants';
import {
GlobalSettingOptions,
GlobalSettingsMenuCategory,
@ -41,35 +41,35 @@ const MyAssetStats: FunctionComponent<MyAssetStatsProps> = ({
icon: Icons.TABLE_GREY,
data: t('label.table-plural'),
count: entityCounts.tableCount,
link: getExplorePathWithSearch(undefined, 'tables'),
link: getExplorePath({ tab: 'tables' }),
dataTestId: 'tables',
},
topics: {
icon: Icons.TOPIC_GREY,
data: t('label.topic-plural'),
count: entityCounts.topicCount,
link: getExplorePathWithSearch(undefined, 'topics'),
link: getExplorePath({ tab: 'topics' }),
dataTestId: 'topics',
},
dashboards: {
icon: Icons.DASHBOARD_GREY,
data: t('label.dashboard-plural'),
count: entityCounts.dashboardCount,
link: getExplorePathWithSearch(undefined, 'dashboards'),
link: getExplorePath({ tab: 'dashboards' }),
dataTestId: 'dashboards',
},
pipelines: {
icon: Icons.PIPELINE_GREY,
data: t('label.pipeline-plural'),
count: entityCounts.pipelineCount,
link: getExplorePathWithSearch(undefined, 'pipelines'),
link: getExplorePath({ tab: 'pipelines' }),
dataTestId: 'pipelines',
},
mlModal: {
icon: Icons.MLMODAL,
data: t('label.ml-model-plural'),
count: entityCounts.mlmodelCount,
link: getExplorePathWithSearch(undefined, 'mlmodels'),
link: getExplorePath({ tab: 'mlmodels' }),
dataTestId: 'mlmodels',
},
testSuite: {

View File

@ -68,11 +68,11 @@ describe('Test MyDataHeader Component', () => {
const user = getByTestId(container, 'user');
const terms = getByTestId(container, 'terms');
expect(tables).toHaveAttribute('href', '/explore/tables/');
expect(topics).toHaveAttribute('href', '/explore/topics/');
expect(dashboards).toHaveAttribute('href', '/explore/dashboards/');
expect(pipelines).toHaveAttribute('href', '/explore/pipelines/');
expect(mlmodel).toHaveAttribute('href', '/explore/mlmodels/');
expect(tables).toHaveAttribute('href', '/explore/tables');
expect(topics).toHaveAttribute('href', '/explore/topics');
expect(dashboards).toHaveAttribute('href', '/explore/dashboards');
expect(pipelines).toHaveAttribute('href', '/explore/pipelines');
expect(mlmodel).toHaveAttribute('href', '/explore/mlmodels');
expect(service).toHaveAttribute('href', '/settings/services/databases');
expect(user).toHaveAttribute('href', '/settings/members/users');
expect(terms).toHaveAttribute(

View File

@ -16,7 +16,7 @@ import { RecentlySearchedData } from 'Models';
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { getExplorePathWithSearch } from '../../constants/constants';
import { getExplorePath } from '../../constants/constants';
import {
getRecentlySearchedData,
removeRecentSearchTerm,
@ -74,7 +74,7 @@ const RecentSearchedTermsAntd: FunctionComponent = () => {
<div className="tw-flex tw-justify-between">
<Link
className="tw-font-medium"
to={getExplorePathWithSearch(item.term)}>
to={getExplorePath({ search: item.term })}>
<Button
className="tw-text-grey-body hover:tw-text-primary-hover hover:tw-underline"
data-testid={`search-term-${item.term}`}

View File

@ -14,11 +14,12 @@
import { Button, Typography } from 'antd';
import { AxiosError } from 'axios';
import { CookieStorage } from 'cookie-storage';
import { isEmpty } from 'lodash';
import { isEmpty, isString } from 'lodash';
import { observer } from 'mobx-react';
import Qs from 'qs';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useHistory, useLocation, useRouteMatch } from 'react-router-dom';
import { Link, useHistory, useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import { getVersion } from 'rest/miscAPI';
import { extractDetailsFromToken } from 'utils/AuthProvider.util';
@ -28,7 +29,7 @@ import { ReactComponent as IconDoc } from '../../assets/svg/doc.svg';
import { ReactComponent as IconSlackGrey } from '../../assets/svg/slack-grey.svg';
import { ReactComponent as IconVersionBlack } from '../../assets/svg/version-black.svg';
import {
getExplorePathWithSearch,
getExplorePath,
getTeamAndUserDetailsPath,
getUserPath,
ROUTES,
@ -67,11 +68,19 @@ const Appbar: React.FC = (): JSX.Element => {
isTourRoute,
onLogoutHandler,
} = useAuthContext();
const match = useRouteMatch<{ searchQuery: string }>({
path: ROUTES.EXPLORE_WITH_SEARCH,
});
const searchQuery = match?.params?.searchQuery;
const parsedQueryString = Qs.parse(
location.search.startsWith('?')
? location.search.substr(1)
: location.search
);
const searchQuery = isString(parsedQueryString.search)
? parsedQueryString.search
: '';
const [searchValue, setSearchValue] = useState(searchQuery);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [isFeatureModalOpen, setIsFeatureModalOpen] = useState<boolean>(false);
const [version, setVersion] = useState<string>('');
@ -81,6 +90,8 @@ const Appbar: React.FC = (): JSX.Element => {
};
const handleSearchChange = (value: string) => {
console.debug(`handleSearchChange value=${value}`);
setSearchValue(value);
value ? setIsOpen(true) : setIsOpen(false);
};
@ -287,16 +298,15 @@ const Appbar: React.FC = (): JSX.Element => {
const searchHandler = (value: string) => {
setIsOpen(false);
addToRecentSearched(value);
history.push({
pathname: getExplorePathWithSearch(
encodeURIComponent(value),
// this is for if user is searching from another page
location.pathname.startsWith(ROUTES.EXPLORE)
? appState.explorePageTab
: 'tables'
),
search: location.search,
});
if (location.pathname.startsWith(ROUTES.EXPLORE)) {
// Already on explore page, only push search change
history.push({
search: Qs.stringify({ ...parsedQueryString, search: value }),
});
} else {
// Outside Explore page
history.push(getExplorePath({ search: value }));
}
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
@ -307,7 +317,7 @@ const Appbar: React.FC = (): JSX.Element => {
};
const handleOnclick = () => {
searchHandler(searchValue ?? '');
searchHandler(searchValue);
};
const fetchOMVersion = () => {
@ -324,7 +334,7 @@ const Appbar: React.FC = (): JSX.Element => {
};
useEffect(() => {
setSearchValue(decodeURIComponent(searchQuery || ''));
setSearchValue(searchQuery);
}, [searchQuery]);
useEffect(() => {

View File

@ -14,7 +14,7 @@
import Tags from 'components/Tag/Tags/tags';
import React, { FunctionComponent, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import { getExplorePathWithSearch } from '../../constants/constants';
import { getExplorePath } from '../../constants/constants';
type SearchOptionsProp = {
searchText: string;
@ -63,7 +63,7 @@ const SearchOptions: FunctionComponent<SearchOptionsProp> = ({
className="link-text tw-flex tw-justify-between tw-px-4 tw-py-2 tw-text-sm
hover:tw-bg-body-hover"
data-testid="InOpenMetadata"
to={getExplorePathWithSearch(searchText)}
to={getExplorePath({ search: searchText })}
onClick={() => setIsOpen(false)}>
{searchText}
<Tags

View File

@ -257,7 +257,6 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
<Route exact component={MyDataPage} path={ROUTES.MY_DATA} />
<Route exact component={TourPageComponent} path={ROUTES.TOUR} />
<Route exact component={ExplorePage} path={ROUTES.EXPLORE} />
<Route component={ExplorePage} path={ROUTES.EXPLORE_WITH_SEARCH} />
<Route component={ExplorePage} path={ROUTES.EXPLORE_WITH_TAB} />
<Route
exact

View File

@ -13,6 +13,8 @@
import { COOKIE_VERSION } from 'components/Modals/WhatsNewModal/whatsNewData';
import { t } from 'i18next';
import { isUndefined } from 'lodash';
import Qs from 'qs';
import { getSettingPath } from '../utils/RouterUtils';
import { getEncodedFqn } from '../utils/StringsUtils';
import { FQN_SEPARATOR_CHAR } from './char.constants';
@ -91,7 +93,6 @@ export const PLACEHOLDER_ROUTE_SERVICE_FQN = ':serviceFQN';
export const PLACEHOLDER_ROUTE_INGESTION_TYPE = ':ingestionType';
export const PLACEHOLDER_ROUTE_INGESTION_FQN = ':ingestionFQN';
export const PLACEHOLDER_ROUTE_SERVICE_CAT = ':serviceCategory';
export const PLACEHOLDER_ROUTE_SEARCHQUERY = ':searchQuery';
export const PLACEHOLDER_ROUTE_TAB = ':tab';
export const PLACEHOLDER_ROUTE_FQN = ':fqn';
export const PLACEHOLDER_ROUTE_TEAM_AND_USER = ':teamAndUser';
@ -160,7 +161,6 @@ export const ROUTES = {
TOUR: '/tour',
REPORTS: '/reports',
EXPLORE: '/explore',
EXPLORE_WITH_SEARCH: `/explore/${PLACEHOLDER_ROUTE_TAB}/${PLACEHOLDER_ROUTE_SEARCHQUERY}`,
EXPLORE_WITH_TAB: `/explore/${PLACEHOLDER_ROUTE_TAB}`,
WORKFLOWS: '/workflows',
SQL_BUILDER: '/sql-builder',
@ -315,13 +315,35 @@ export const getServiceDetailsPath = (
return path;
};
export const getExplorePathWithSearch = (searchQuery = '', tab = 'tables') => {
let path = ROUTES.EXPLORE_WITH_SEARCH;
path = path
.replace(PLACEHOLDER_ROUTE_SEARCHQUERY, searchQuery)
.replace(PLACEHOLDER_ROUTE_TAB, tab);
export const getExplorePath: (args: {
tab?: string;
search?: string;
extraParameters?: Record<string, unknown>;
}) => string = ({ tab, search, extraParameters }) => {
const pathname = ROUTES.EXPLORE_WITH_TAB.replace(
PLACEHOLDER_ROUTE_TAB,
tab ?? ''
);
let paramsObject: Record<string, unknown> = Qs.parse(
location.search.startsWith('?')
? location.search.substr(1)
: location.search
);
if (!isUndefined(search)) {
paramsObject = {
...paramsObject,
search,
};
}
if (!isUndefined(extraParameters)) {
paramsObject = {
...paramsObject,
...extraParameters,
};
}
const query = Qs.stringify(paramsObject);
return path;
return `${pathname}?${query}`;
};
export const getDatabaseDetailsPath = (databaseFQN: string, tab?: string) => {

View File

@ -63,6 +63,7 @@ import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import {
getDatabaseDetailsPath,
getDatabaseSchemaDetailsPath,
getExplorePath,
getServiceDetailsPath,
getTeamAndUserDetailsPath,
PAGE_SIZE,
@ -96,10 +97,7 @@ import {
updateThreadData,
} from '../../utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
import {
getExplorePathWithInitFilters,
getSettingPath,
} from '../../utils/RouterUtils';
import { getSettingPath } from '../../utils/RouterUtils';
import {
getServiceRouteFromServiceType,
serviceTypeLogo,
@ -577,11 +575,15 @@ const DatabaseDetails: FunctionComponent = () => {
useEffect(() => {
if (!isMounting.current && appState.inPageSearchText) {
history.push(
getExplorePathWithInitFilters(
appState.inPageSearchText,
undefined,
`postFilter[serviceType][0]=${serviceType}&postFilter[database.name.keyword][0]=${databaseName}`
)
getExplorePath({
search: appState.inPageSearchText,
extraParameters: {
postFilter: {
serviceType: [serviceType],
'database.name.keyword': [databaseName],
},
},
})
);
}
}, [appState.inPageSearchText]);

View File

@ -27,7 +27,7 @@ import { useHistory, useLocation, useParams } from 'react-router-dom';
import { searchQuery } from 'rest/searchAPI';
import useDeepCompareEffect from 'use-deep-compare-effect';
import AppState from '../../AppState';
import { PAGE_SIZE } from '../../constants/constants';
import { getExplorePath, PAGE_SIZE } from '../../constants/constants';
import {
INITIAL_SORT_FIELD,
INITIAL_SORT_ORDER,
@ -47,7 +47,7 @@ const ExplorePage: FunctionComponent = () => {
const location = useLocation();
const history = useHistory();
const { searchQuery: searchQueryParam = '', tab } = useParams<UrlParams>();
const { tab } = useParams<UrlParams>();
const [searchResults, setSearchResults] =
useState<SearchResponse<ExploreSearchIndex>>();
@ -73,6 +73,11 @@ const ExplorePage: FunctionComponent = () => {
[location.search]
);
const searchQueryParam = useMemo(
() => (isString(parsedSearch.search) ? parsedSearch.search : ''),
[location.search]
);
const postFilter = useMemo(
() =>
isFilterObject(parsedSearch.postFilter)
@ -93,17 +98,18 @@ const ExplorePage: FunctionComponent = () => {
const handleSearchIndexChange: (nSearchIndex: ExploreSearchIndex) => void = (
nSearchIndex
) => {
history.push({
pathname: `/explore/${tabsInfo[nSearchIndex].path}/${searchQueryParam}`,
search: Qs.stringify({ page: 1 }),
});
history.push(
getExplorePath({
tab: tabsInfo[nSearchIndex].path,
extraParameters: { page: '1' },
})
);
setAdvancedSearchQueryFilter(undefined);
};
const handleQueryFilterChange: ExploreProps['onChangeAdvancedSearchJsonTree'] =
(queryFilter) => {
history.push({
pathname: history.location.pathname,
search: Qs.stringify({
...parsedSearch,
queryFilter: queryFilter ? JSON.stringify(queryFilter) : undefined,
@ -116,7 +122,6 @@ const ExplorePage: FunctionComponent = () => {
postFilter
) => {
history.push({
pathname: history.location.pathname,
search: Qs.stringify({ ...parsedSearch, postFilter, page: 1 }),
});
};
@ -125,7 +130,6 @@ const ExplorePage: FunctionComponent = () => {
showDeleted
) => {
history.push({
pathname: history.location.pathname,
search: Qs.stringify({ ...parsedSearch, showDeleted, page: 1 }),
});
};

View File

@ -62,6 +62,7 @@ import {
} from 'rest/tagAPI';
import { ReactComponent as PlusIcon } from '../../assets/svg/plus-primary.svg';
import {
getExplorePath,
INITIAL_PAGING_VALUE,
PAGE_SIZE,
TIER_CATEGORY,
@ -85,10 +86,7 @@ import {
checkPermission,
DEFAULT_ENTITY_PERMISSION,
} from '../../utils/PermissionsUtils';
import {
getExplorePathWithInitFilters,
getTagPath,
} from '../../utils/RouterUtils';
import { getTagPath } from '../../utils/RouterUtils';
import { getErrorText } from '../../utils/StringsUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showErrorToast } from '../../utils/ToastUtils';
@ -545,11 +543,13 @@ const TagsPage = () => {
const getUsageCountLink = (tagFQN: string) => {
const type = tagFQN.startsWith('Tier') ? 'tier' : 'tags';
return getExplorePathWithInitFilters(
'',
undefined,
`postFilter[${type}.tagFQN][0]=${tagFQN}`
);
return getExplorePath({
extraParameters: {
postFilter: {
[`${type}.tagFQN`]: [tagFQN],
},
},
});
};
const handleActionDeleteTag = (record: Tag) => {

View File

@ -32,7 +32,6 @@ import {
PLACEHOLDER_ROUTE_FQN,
PLACEHOLDER_ROUTE_INGESTION_FQN,
PLACEHOLDER_ROUTE_INGESTION_TYPE,
PLACEHOLDER_ROUTE_SEARCHQUERY,
PLACEHOLDER_ROUTE_SERVICE_CAT,
PLACEHOLDER_ROUTE_SERVICE_FQN,
PLACEHOLDER_ROUTE_TAB,
@ -139,26 +138,6 @@ export const getEditIngestionPath = (
return path;
};
/**
*
* @param searchQuery search text
* @param tab selected explore result tab
* @param filter selected facet filters
* @returns
*/
export const getExplorePathWithInitFilters = (
searchQuery = '',
tab = 'tables',
filter = ''
) => {
let path = ROUTES.EXPLORE_WITH_SEARCH;
path = path
.replace(PLACEHOLDER_ROUTE_SEARCHQUERY, searchQuery)
.replace(PLACEHOLDER_ROUTE_TAB, tab);
return filter ? `${path}?${filter}` : path;
};
export const getGlossaryPath = (fqn?: string) => {
let path = ROUTES.GLOSSARY;
if (fqn) {