Data products explore tab (#13578)

* fix: add data products in explore tab

* fix: add data products to explore

* fix: update icon positioning

* fix: make dataProducts as default tab on explore

* fix: multiple api calls

* fix: data product feedback

* fix: filtering ui screen for data product

* fix: sonar tests

---------

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
karanh37 2023-10-17 15:10:28 +05:30 committed by GitHub
parent c65518ac04
commit 10ef83779e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 248 additions and 116 deletions

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 4.66667C2 4.29848 2.29848 4 2.66667 4H13.3333C13.7015 4 14 4.29848 14 4.66667C14 5.03485 13.7015 5.33333 13.3333 5.33333H2.66667C2.29848 5.33333 2 5.03485 2 4.66667ZM4 8C4 7.6318 4.29848 7.33333 4.66667 7.33333H11.3333C11.7015 7.33333 12 7.6318 12 8C12 8.3682 11.7015 8.66667 11.3333 8.66667H4.66667C4.29848 8.66667 4 8.3682 4 8ZM6 11.3333C6 10.9651 6.29848 10.6667 6.66667 10.6667H9.33333C9.70153 10.6667 10 10.9651 10 11.3333C10 11.7015 9.70153 12 9.33333 12H6.66667C6.29848 12 6 11.7015 6 11.3333Z" fill="#292929"/>
</svg>

After

Width:  |  Height:  |  Size: 651 B

View File

@ -240,6 +240,9 @@ const Appbar: React.FC = (): JSX.Element => {
tab: defaultTab,
search: value,
isPersistFilters: false,
extraParameters: {
sort: '_score',
},
})
);
}

View File

@ -38,6 +38,7 @@ export interface AssetSelectionModalProps {
type?: AssetsOfEntity;
onCancel: () => void;
onSave?: () => void;
queryFilter?: Record<string, unknown>;
}
export type AssetsUnion =

View File

@ -38,7 +38,6 @@ import {
getAssetsSearchIndex,
getEntityAPIfromSource,
} from '../../../utils/Assets/AssetsUtils';
import { getQueryFilterToExcludeTerm } from '../../../utils/GlossaryUtils';
import Searchbar from '../../common/searchbar/Searchbar';
import TableDataCardV2 from '../../common/table-data-card-v2/TableDataCardV2';
import { AssetsOfEntity } from '../../Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
@ -53,6 +52,7 @@ export const AssetSelectionModal = ({
onSave,
open,
type = AssetsOfEntity.GLOSSARY,
queryFilter = {},
}: AssetSelectionModalProps) => {
const { t } = useTranslation();
const [search, setSearch] = useState('');
@ -67,14 +67,6 @@ export const AssetSelectionModal = ({
const [pageNumber, setPageNumber] = useState(1);
const [totalCount, setTotalCount] = useState(0);
const queryFilter = useMemo(() => {
if (type === AssetsOfEntity.GLOSSARY) {
return getQueryFilterToExcludeTerm(entityFqn);
} else {
return {};
}
}, [entityFqn, type]);
const fetchEntities = useCallback(
async ({ searchText = '', page = 1, index = activeFilter }) => {
try {
@ -106,7 +98,7 @@ export const AssetSelectionModal = ({
} else if (type === AssetsOfEntity.DATA_PRODUCT) {
const data = await getDataProductByName(
encodeURIComponent(entityFqn),
''
'domain'
);
setActiveEntity(data);
}

View File

@ -44,10 +44,7 @@ import { EntityDetailsObjectInterface } from '../../../components/Explore/explor
import AssetsTabs, {
AssetsTabRef,
} from '../../../components/Glossary/GlossaryTerms/tabs/AssetsTabs.component';
import {
AssetsOfEntity,
AssetsViewType,
} from '../../../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
import { AssetsOfEntity } from '../../../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface';
import EntityDeleteModal from '../../../components/Modals/EntityDeleteModal/EntityDeleteModal';
import EntityNameModal from '../../../components/Modals/EntityNameModal/EntityNameModal.component';
import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider';
@ -56,7 +53,6 @@ import {
ResourceEntity,
} from '../../../components/PermissionProvider/PermissionProvider.interface';
import TabsLabel from '../../../components/TabsLabel/TabsLabel.component';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { DE_ACTIVE_COLOR } from '../../../constants/constants';
import { EntityField } from '../../../constants/Feeds.constants';
import { myDataSearchIndex } from '../../../constants/Mydata.constants';
@ -70,8 +66,9 @@ import { Operation } from '../../../generated/entity/policies/policy';
import { Style } from '../../../generated/type/tagLabel';
import { searchData } from '../../../rest/miscAPI';
import { getEntityDeleteMessage } from '../../../utils/CommonUtils';
import { getQueryFilterToIncludeDomain } from '../../../utils/DomainUtils';
import { getEntityName } from '../../../utils/EntityUtils';
import { getEntityVersionByField } from '../../../utils/EntityVersionUtils';
import Fqn from '../../../utils/Fqn.js';
import {
checkPermission,
DEFAULT_ENTITY_PERMISSION,
@ -117,25 +114,18 @@ const DataProductsDetailsPage = ({
const [assetCount, setAssetCount] = useState<number>(0);
const breadcrumbs = useMemo(() => {
if (!dataProductFqn) {
if (!dataProduct.domain) {
return [];
}
const arr = Fqn.split(dataProductFqn);
const dataFQN: Array<string> = [];
return [
...arr.slice(0, -1).map((d) => {
dataFQN.push(d);
return {
name: d,
url: getDomainPath(dataFQN.join(FQN_SEPARATOR_CHAR)),
activeTitle: false,
};
}),
{
name: getEntityName(dataProduct.domain),
url: getDomainPath(dataProduct.domain.fullyQualifiedName),
activeTitle: false,
},
];
}, [dataProductFqn]);
}, [dataProduct.domain]);
const [name, displayName] = useMemo(() => {
const defaultName = dataProduct.name;
@ -425,7 +415,6 @@ const DataProductsDetailsPage = ({
permissions={dataProductPermission}
ref={assetTabRef}
type={AssetsOfEntity.DATA_PRODUCT}
viewType={AssetsViewType.TABS}
onAddAsset={() => setAssetModelVisible(true)}
onAssetClick={handleAssetClick}
/>
@ -575,6 +564,9 @@ const DataProductsDetailsPage = ({
<AssetSelectionModal
entityFqn={dataProductFqn}
open={assetModalVisible}
queryFilter={getQueryFilterToIncludeDomain(
dataProduct.domain?.fullyQualifiedName ?? ''
)}
type={AssetsOfEntity.DATA_PRODUCT}
onCancel={() => setAssetModelVisible(false)}
onSave={handleAssetSave}

View File

@ -40,6 +40,5 @@
}
.summary-panel-container {
height: @domain-page-height;
border-left: 1px solid @border-color;
}
}

View File

@ -101,7 +101,7 @@ const DataProductsPage = () => {
try {
const data = await getDataProductByName(
encodeURIComponent(fqn),
'owner,experts'
'domain,owner,experts'
);
setDataProduct(data);

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { isNil, isString } from 'lodash';
import { cloneDeep, isNil, isString } from 'lodash';
import Qs from 'qs';
import React, {
useCallback,
@ -22,9 +22,11 @@ import React, {
} from 'react';
import {
Config,
FieldGroup,
ImmutableTree,
JsonTree,
Utils as QbUtils,
ValueSource,
} from 'react-awesome-query-builder';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import {
@ -33,7 +35,9 @@ import {
} from '../../../constants/AdvancedSearch.constants';
import { tabsInfo } from '../../../constants/explore.constants';
import { SearchIndex } from '../../../enums/search.enum';
import { getTypeByFQN } from '../../../rest/metadataTypeAPI';
import { elasticSearchFormat } from '../../../utils/QueryBuilderElasticsearchFormatUtils';
import { getEntityTypeFromSearchIndex } from '../../../utils/SearchUtils';
import Loader from '../../Loader/Loader';
import { AdvancedSearchModal } from '../AdvanceSearchModal.component';
import { ExploreSearchIndex, UrlParams } from '../explore.interface';
@ -165,15 +169,55 @@ export const AdvanceSearchProvider = ({
});
}, [history, location.pathname]);
useEffect(() => {
if (jsonTree) {
const tree = QbUtils.checkTree(QbUtils.loadTree(jsonTree), config);
async function getCustomAttributesSubfields() {
const updatedConfig = cloneDeep(config);
try {
const entityType = getEntityTypeFromSearchIndex(searchIndex);
if (!entityType) {
return;
}
const res = await getTypeByFQN(entityType);
const customAttributes = res.customProperties;
const subfields: Record<
string,
{ type: string; valueSources: ValueSource[] }
> = {};
if (customAttributes) {
customAttributes.forEach((attr) => {
subfields[attr.name] = {
type: 'text',
valueSources: ['value'],
};
});
}
(updatedConfig.fields.extension as FieldGroup).subfields = subfields;
return updatedConfig;
} catch (error) {
// Error
return updatedConfig;
}
}
const loadTree = useCallback(
async (treeObj: JsonTree) => {
const updatedConfig = (await getCustomAttributesSubfields()) ?? config;
const tree = QbUtils.checkTree(QbUtils.loadTree(treeObj), updatedConfig);
setTreeInternal(tree);
const qFilter = {
query: elasticSearchFormat(tree, config),
query: elasticSearchFormat(tree, updatedConfig),
};
setQueryFilter(qFilter);
setSQLQuery(QbUtils.sqlFormat(tree, config) ?? '');
setSQLQuery(QbUtils.sqlFormat(tree, updatedConfig) ?? '');
},
[config]
);
useEffect(() => {
if (jsonTree) {
loadTree(jsonTree);
} else {
handleReset();
}

View File

@ -16,6 +16,7 @@ import { useTranslation } from 'react-i18next';
import { DataProduct } from '../../../../generated/entity/domains/dataProduct';
import { getEntityName } from '../../../../utils/EntityUtils';
import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
import RichTextEditorPreviewer from '../../../common/rich-text-editor/RichTextEditorPreviewer';
import SummaryPanelSkeleton from '../../../Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
interface DataProductSummaryProps {
@ -50,6 +51,32 @@ const DataProductSummary = ({
</Row>
<Divider className="m-y-xs" />
<Row className="m-md" gutter={[0, 8]}>
<Col span={24}>
<Typography.Text
className="summary-panel-section-title"
data-testid="description-header">
{t('label.description')}
</Typography.Text>
</Col>
<Col span={24}>
<div>
{entityDetails.description?.trim() ? (
<RichTextEditorPreviewer
markdown={entityDetails.description}
maxLength={80}
/>
) : (
<Typography className="text-grey-body">
{t('label.no-data-found')}
</Typography>
)}
</div>
</Col>
</Row>
<Divider className="m-y-xs" />
<Row className="m-md m-t-0" gutter={[0, 8]}>
<Col span={24}>
<Typography.Text

View File

@ -268,6 +268,11 @@ export const MOCK_EXPLORE_SEARCH_RESULTS: SearchResponse<ExploreSearchIndex> = {
};
export const MOCK_EXPLORE_TAB_ITEMS = [
{
key: 'data_product_search_index',
label: 'data_product_search_index',
count: 0,
},
{
key: 'table_search_index',
label: 'table_search_index',

View File

@ -46,6 +46,7 @@ export type UrlParams = {
};
export type ExploreSearchIndex =
| SearchIndex.DATA_PRODUCT
| SearchIndex.TABLE
| SearchIndex.PIPELINE
| SearchIndex.DASHBOARD

View File

@ -101,7 +101,8 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
if (
source.entityType !== EntityType.GLOSSARY_TERM &&
source.entityType !== EntityType.TAG
source.entityType !== EntityType.TAG &&
source.entityType !== EntityType.DATA_PRODUCT
) {
_otherDetails.push({
key: 'Tier',

View File

@ -76,7 +76,6 @@ const ExploreV1: React.FC<ExploreProps> = ({
quickFilters,
}) => {
const { t } = useTranslation();
// const { tab } = useParams<{ tab: string }>();
const [selectedQuickFilters, setSelectedQuickFilters] = useState<
ExploreQuickFilterField[]
>([] as ExploreQuickFilterField[]);

View File

@ -67,6 +67,7 @@ const props = {
tabItems: MOCK_EXPLORE_TAB_ITEMS,
activeTabKey: SearchIndex.TABLE,
tabCounts: {
data_product_search_index: 0,
table_search_index: 20,
topic_search_index: 10,
dashboard_search_index: 14,

View File

@ -101,10 +101,15 @@ export const FeedEditor = forwardRef<editorRef, FeedEditorProp>(
renderLoading: () => `${t('label.loading')}...`,
renderItem: (item: Record<string, any>) => {
if (!item.type) {
return `<div class="d-flex gap-2">
const userResult = `<div class="d-flex gap-2">
${item.avatarEle}
<span class="d-flex items-center truncate w-56">${item.name}</span>
</div>`;
const userWrapper = document.createElement('div');
userWrapper.innerHTML = userResult;
return userWrapper;
}
const breadcrumbsData = item.breadcrumbs
@ -127,7 +132,7 @@ export const FeedEditor = forwardRef<editorRef, FeedEditorProp>(
? `<span class="text-grey-muted text-xs">${item.type}</span>`
: '';
return `<div class="d-flex items-center gap-2">
const result = `<div class="d-flex items-center gap-2">
<div class="flex-center mention-icon-image">${icon}</div>
<div>
${breadcrumbEle}
@ -137,6 +142,11 @@ export const FeedEditor = forwardRef<editorRef, FeedEditorProp>(
</div>
</div>
</div>`;
const wrapper = document.createElement('div');
wrapper.innerHTML = result;
return wrapper;
},
},
markdownOptions: {},

View File

@ -25,6 +25,7 @@ import { ChangeDescription } from '../../../generated/entity/type';
import { searchData } from '../../../rest/miscAPI';
import { getCountBadge, getFeedCounts } from '../../../utils/CommonUtils';
import { getEntityVersionByField } from '../../../utils/EntityVersionUtils';
import { getQueryFilterToExcludeTerm } from '../../../utils/GlossaryUtils';
import { getGlossaryTermsVersionsPath } from '../../../utils/RouterUtils';
import { getEncodedFqn } from '../../../utils/StringsUtils';
import { ActivityFeedTab } from '../../ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
@ -321,6 +322,9 @@ const GlossaryTermsV1 = ({
<AssetSelectionModal
entityFqn={glossaryTerm.fullyQualifiedName}
open={assetModalVisible}
queryFilter={getQueryFilterToExcludeTerm(
glossaryTerm.fullyQualifiedName
)}
type={AssetsOfEntity.GLOSSARY}
onCancel={() => setAssetModelVisible(false)}
onSave={handleAssetSave}

View File

@ -12,8 +12,6 @@
*/
import { render, screen } from '@testing-library/react';
import React from 'react';
import AppState from '../../AppState';
import { User } from '../../generated/entity/teams/user';
import {
getEntityPermissionByFqn,
getEntityPermissionById,
@ -37,13 +35,25 @@ jest.mock('../../rest/permissionAPI', () => ({
.mockImplementation(() => Promise.resolve({})),
}));
jest.mock('react-router-dom', () => ({
useHistory: jest.fn().mockReturnValue({ push: jest.fn(), listen: jest.fn() }),
}));
let currentUser: { id: string; name: string } | null = {
id: '123',
name: 'Test User',
};
jest.mock('../authentication/auth-provider/AuthProvider', () => {
return {
useAuthContext: jest.fn().mockImplementation(() => ({
currentUser,
})),
};
});
describe('PermissionProvider', () => {
it('Should render children and call apis when current user is present', async () => {
const currentUser = { id: '123', name: 'Test User' };
const getUserDetailsSpy = jest
.spyOn(AppState, 'getCurrentUserDetails')
.mockReturnValue(currentUser as User);
render(
<PermissionProvider>
<div data-testid="children">Children</div>
@ -57,15 +67,10 @@ describe('PermissionProvider', () => {
expect(getResourcePermission).not.toHaveBeenCalled();
expect(await screen.findByTestId('children')).toBeInTheDocument();
getUserDetailsSpy.mockRestore();
});
it('Should not call apis when current user is undefined', async () => {
const getUserDetailsSpy = jest
.spyOn(AppState, 'getCurrentUserDetails')
.mockReturnValue(undefined);
currentUser = null;
render(
<PermissionProvider>
<div data-testid="children">Children</div>
@ -77,7 +82,5 @@ describe('PermissionProvider', () => {
expect(getEntityPermissionById).not.toHaveBeenCalled();
expect(getEntityPermissionByFqn).not.toHaveBeenCalled();
expect(getResourcePermission).not.toHaveBeenCalled();
getUserDetailsSpy.mockRestore();
});
});

View File

@ -24,7 +24,6 @@ import React, {
useState,
} from 'react';
import { useHistory } from 'react-router-dom';
import AppState from '../../AppState';
import Loader from '../../components/Loader/Loader';
import { REDIRECT_PATHNAME } from '../../constants/constants';
import {
@ -41,6 +40,7 @@ import {
getOperationPermissions,
getUIPermission,
} from '../../utils/PermissionsUtils';
import { useAuthContext } from '../authentication/auth-provider/AuthProvider';
import {
EntityPermissionMap,
PermissionContextType,
@ -67,6 +67,7 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
const [permissions, setPermissions] = useState<UIPermission>(
{} as UIPermission
);
const { currentUser } = useAuthContext();
const cookieStorage = new CookieStorage();
const history = useHistory();
const [loading, setLoading] = useState(false);
@ -78,11 +79,6 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
{} as UIPermission
);
// Update current user details of AppState change
const currentUser = useMemo(() => {
return AppState.getCurrentUserDetails();
}, [AppState.userDetails, AppState.nonSecureUserDetails]);
const redirectToStoredPath = useCallback(() => {
const urlPathname = cookieStorage.getItem(REDIRECT_PATHNAME);
if (urlPathname) {

View File

@ -29,7 +29,7 @@
}
.ant-tabs-nav {
padding: 8px 16px 0 28px;
margin-bottom: 0;
margin-bottom: 0 !important;
}
.widget-manage-dropdown .ant-dropdown-trigger {

View File

@ -84,25 +84,21 @@ const EntitySummaryDetails = ({
/>
);
const {
isEntityDetails,
userDetails,
isTier,
isOwner,
const { isEntityDetails, userDetails, isTier, isOwner, isTeamOwner } =
useMemo(() => {
const userDetails = getTeamsUser(data);
isTeamOwner,
} = useMemo(() => {
const userDetails = getTeamsUser(data);
return {
isEntityCard: data?.isEntityCard,
isEntityDetails: data?.isEntityDetails,
userDetails,
isTier: data.key === 'Tier',
isOwner: data.key === 'Owner',
isTeamOwner: isString(data.value) ? data.value.includes('teams/') : false,
};
}, [data]);
return {
isEntityCard: data?.isEntityCard,
isEntityDetails: data?.isEntityDetails,
userDetails,
isTier: data.key === 'Tier',
isOwner: data.key === 'Owner',
isTeamOwner: isString(data.value)
? data.value.includes('teams/')
: false,
};
}, [data]);
switch (data.key) {
case 'Owner':
@ -129,7 +125,11 @@ const EntitySummaryDetails = ({
</>
)}
{isTeamOwner ? (
<IconTeamsGrey height={18} width={18} />
<IconTeamsGrey
className="align-middle"
height={18}
width={18}
/>
) : (
<ProfilePicture
displayName={displayVal}

View File

@ -10,11 +10,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FilterOutlined } from '@ant-design/icons';
import { Button, Checkbox, List, Popover, Space, Typography } from 'antd';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import AppState from '../../../AppState';
import { ReactComponent as FilterIcon } from '../../../assets/svg/ic-feeds-filter.svg';
import { FeedFilter } from '../../../enums/mydata.enum';
import './feeds-filter-popover.less';
import { FeedsFilterPopoverProps } from './FeedsFilterPopover.interface';
@ -126,7 +126,7 @@ const FeedsFilterPopover = ({
showArrow={false}
trigger="click"
onOpenChange={setPopupVisible}>
<Button icon={<FilterOutlined />}>{t('label.filter-plural')}</Button>
<Button className="flex-center" icon={<FilterIcon height={16} />} />
</Popover>
);
};

View File

@ -162,6 +162,17 @@ export const TAG_DROPDOWN_ITEMS = [
},
];
export const DATA_PRODUCT_DROPDOWN_ITEMS = [
{
label: t('label.domain'),
key: 'domain.displayName.keyword',
},
{
label: t('label.owner'),
key: 'owner.displayName.keyword',
},
];
export const ALL_DROPDOWN_ITEMS = [
...COMMON_DROPDOWN_ITEMS,
...TABLE_DROPDOWN_ITEMS,

View File

@ -26,7 +26,7 @@ export const SIDEBAR_LIST = [
{
key: ROUTES.EXPLORE,
label: i18next.t('label.explore'),
redirect_url: '/explore/tables',
redirect_url: '/explore/dataProducts',
icon: ExploreIcon,
dataTestId: 'app-bar-item-explore',
},

View File

@ -56,6 +56,12 @@ export interface ExploreTabInfo {
}
export const tabsInfo: { [K in ExploreSearchIndex]: ExploreTabInfo } = {
[SearchIndex.DATA_PRODUCT]: {
label: i18n.t('label.data-product-plural'),
sortingFields: tableSortingFields,
sortField: INITIAL_SORT_FIELD,
path: 'dataProducts',
},
[SearchIndex.TABLE]: {
label: i18n.t('label.table-plural'),
sortingFields: tableSortingFields,

View File

@ -1917,6 +1917,7 @@ export const mockSearchData = {
};
export const MOCK_EXPLORE_PAGE_COUNT = {
[SearchIndex.DATA_PRODUCT]: 0,
[SearchIndex.TABLE]: mockSearchData.hits.total.value,
[SearchIndex.TOPIC]: 0,
[SearchIndex.DASHBOARD]: 0,

View File

@ -77,8 +77,6 @@ const ExplorePageV1: FunctionComponent = () => {
const [advancesSearchQuickFilters, setAdvancedSearchQuickFilters] =
useState<QueryFilterInterface>();
const [sortValue, setSortValue] = useState<string>(INITIAL_SORT_FIELD);
const [sortOrder, setSortOrder] = useState<SORT_ORDER>(SORT_ORDER.DESC);
const [searchHitCounts, setSearchHitCounts] = useState<SearchHitCounts>();
@ -87,20 +85,23 @@ const ExplorePageV1: FunctionComponent = () => {
const { queryFilter } = useAdvanceSearch();
const parsedSearch = useMemo(
() =>
Qs.parse(
location.search.startsWith('?')
? location.search.substring(1)
: location.search
),
[location.search]
);
const [parsedSearch, searchQueryParam, sortValue] = useMemo(() => {
const parsedSearch = Qs.parse(
location.search.startsWith('?')
? location.search.substring(1)
: location.search
);
const searchQueryParam = useMemo(
() => (isString(parsedSearch.search) ? parsedSearch.search : ''),
[location.search]
);
const searchQueryParam = isString(parsedSearch.search)
? parsedSearch.search
: '';
const sortValue = isString(parsedSearch.sort)
? parsedSearch.sort
: INITIAL_SORT_FIELD;
return [parsedSearch, searchQueryParam, sortValue];
}, [location.search]);
const handlePageChange: ExploreProps['onChangePage'] = (page, size) => {
history.push({
@ -108,6 +109,17 @@ const ExplorePageV1: FunctionComponent = () => {
});
};
const handleSortValueChange = (page: number, sortVal: string) => {
history.push({
search: Qs.stringify({
...parsedSearch,
page,
size: size ?? PAGE_SIZE,
sort: sortVal,
}),
});
};
// Filters that can be common for all the Entities Ex. Tables, Topics, etc.
const commonQuickFilters = useMemo(() => {
const mustField: QueryFieldInterface[] = get(
@ -152,6 +164,7 @@ const ExplorePageV1: FunctionComponent = () => {
getExplorePath({
tab: tabsInfo[nSearchIndex].path,
extraParameters: {
sort: searchQueryParam ? '_score' : INITIAL_SORT_FIELD,
page: '1',
quickFilter: commonQuickFilters
? JSON.stringify(commonQuickFilters)
@ -161,7 +174,7 @@ const ExplorePageV1: FunctionComponent = () => {
})
);
},
[commonQuickFilters]
[commonQuickFilters, searchQueryParam]
);
const handleQuickFilterChange = useCallback(
@ -193,13 +206,13 @@ const ExplorePageV1: FunctionComponent = () => {
if (isNil(tabInfo)) {
const activeKey = findActiveSearchIndex(searchHitCounts);
return activeKey ? activeKey : SearchIndex.TABLE;
return activeKey ? activeKey : SearchIndex.DATA_PRODUCT;
}
return tabInfo[0] as ExploreSearchIndex;
}
return SearchIndex.TABLE;
return SearchIndex.DATA_PRODUCT;
}, [tab, searchHitCounts]);
const tabItems = useMemo(() => {
@ -297,19 +310,13 @@ const ExplorePageV1: FunctionComponent = () => {
queryFilter as unknown as QueryFilterInterface
);
let newSortValue = sortValue;
if (searchQueryParam !== '') {
newSortValue = '_score';
setSortValue(newSortValue);
}
setIsLoading(true);
Promise.all([
searchQuery({
query: escapeESReservedCharacters(searchQueryParam),
searchIndex,
queryFilter: combinedQueryFilter,
sortField: newSortValue,
sortField: sortValue,
sortOrder,
pageNumber: page,
pageSize: size,
@ -322,6 +329,7 @@ const ExplorePageV1: FunctionComponent = () => {
}),
Promise.all(
[
SearchIndex.DATA_PRODUCT,
SearchIndex.TABLE,
SearchIndex.TOPIC,
SearchIndex.DASHBOARD,
@ -348,6 +356,7 @@ const ExplorePageV1: FunctionComponent = () => {
)
).then(
([
dataProductResponse,
tableResponse,
topicResponse,
dashboardResponse,
@ -361,6 +370,7 @@ const ExplorePageV1: FunctionComponent = () => {
searchIndexResponse,
]) => {
setSearchHitCounts({
[SearchIndex.DATA_PRODUCT]: dataProductResponse.hits.total.value,
[SearchIndex.TABLE]: tableResponse.hits.total.value,
[SearchIndex.TOPIC]: topicResponse.hits.total.value,
[SearchIndex.DASHBOARD]: dashboardResponse.hits.total.value,
@ -440,9 +450,8 @@ const ExplorePageV1: FunctionComponent = () => {
handlePageChange(1);
setSortOrder(sort);
}}
onChangeSortValue={(sort) => {
handlePageChange(1);
setSortValue(sort);
onChangeSortValue={(sortVal) => {
handleSortValueChange(1, sortVal);
}}
/>
);

View File

@ -23,6 +23,7 @@ import { User } from '../generated/entity/teams/user';
import { SearchResponse } from '../interface/search.interface';
export type SearchEntityHits = SearchResponse<
| SearchIndex.DATA_PRODUCT
| SearchIndex.PIPELINE
| SearchIndex.DASHBOARD
| SearchIndex.TABLE
@ -56,7 +57,7 @@ export const formatDataResponse = (
newData.owner = get(hit, '_source.owner');
newData.highlight = hit.highlight;
newData.entityType = hit._source.entityType;
newData.deleted = hit._source.deleted;
newData.deleted = get(hit, '_source.deleted');
if ('tableType' in source) {
newData.tableType = source.tableType ?? '';

View File

@ -24,6 +24,7 @@ import {
CONTAINER_DROPDOWN_ITEMS,
DASHBOARD_DATA_MODEL_TYPE,
DASHBOARD_DROPDOWN_ITEMS,
DATA_PRODUCT_DROPDOWN_ITEMS,
GLOSSARY_DROPDOWN_ITEMS,
PIPELINE_DROPDOWN_ITEMS,
SEARCH_INDEX_DROPDOWN_ITEMS,
@ -79,6 +80,8 @@ export const getDropDownItems = (index: string) => {
return [...GLOSSARY_DROPDOWN_ITEMS];
case SearchIndex.TAG:
return [...TAG_DROPDOWN_ITEMS];
case SearchIndex.DATA_PRODUCT:
return [...DATA_PRODUCT_DROPDOWN_ITEMS];
default:
return [];

View File

@ -109,3 +109,23 @@ export const getUserNames = (
return getOwner(hasPermission, getEntityName(entity.owner), entity.owner);
};
export const getQueryFilterToIncludeDomain = (fqn: string) => ({
query: {
bool: {
must: [
{
bool: {
must: [
{
term: {
'domain.fullyQualifiedName': fqn,
},
},
],
},
},
],
},
},
});