From 1fa033798abb704c8cd770e492e6e5449f853556 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Mon, 6 Sep 2021 11:23:27 +0530 Subject: [PATCH] Adding dashboard tab on explore page (#398) * Adding dashboard tab on explore page * minor fix --- .../ui/src/axiosAPIs/dashboardAPI.ts | 83 +++++ .../ErrorPlaceHolderES.tsx | 4 +- .../common/facetfilter/FacetFilter.tsx | 7 +- .../tags-container/tags-container.tsx | 4 +- .../resources/ui/src/constants/constants.ts | 12 +- .../resources/ui/src/enums/search.enum.ts | 1 + .../ui/src/pages/dashboard-details/index.tsx | 322 ++++++++++++++++++ .../ui/src/pages/explore/explore.constants.ts | 31 +- .../resources/ui/src/pages/explore/index.tsx | 83 +++-- .../ui/src/pages/my-data-details/index.tsx | 1 + .../ui/src/router/AuthenticatedAppRouter.tsx | 2 + .../main/resources/ui/src/utils/APIUtils.js | 8 +- .../resources/ui/src/utils/FilterUtils.js | 3 +- .../resources/ui/src/utils/TableUtils.tsx | 4 + 14 files changed, 516 insertions(+), 49 deletions(-) create mode 100644 catalog-rest-service/src/main/resources/ui/src/axiosAPIs/dashboardAPI.ts create mode 100644 catalog-rest-service/src/main/resources/ui/src/pages/dashboard-details/index.tsx diff --git a/catalog-rest-service/src/main/resources/ui/src/axiosAPIs/dashboardAPI.ts b/catalog-rest-service/src/main/resources/ui/src/axiosAPIs/dashboardAPI.ts new file mode 100644 index 00000000000..57d63e51b0d --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/axiosAPIs/dashboardAPI.ts @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +import { AxiosResponse } from 'axios'; +import { Dashboard } from '../generated/entity/data/dashboard'; +import { getURLWithQueryFields } from '../utils/APIUtils'; +import APIClient from './index'; + +export const getDashboards: Function = ( + serviceName: string, + paging: string, + arrQueryFields: string +): Promise => { + const url = `${getURLWithQueryFields( + `/dashboards`, + arrQueryFields + )}&service=${serviceName}${paging ? paging : ''}`; + + return APIClient.get(url); +}; + +export const getDashboardByFqn: Function = ( + fqn: string, + arrQueryFields: string +): Promise => { + const url = getURLWithQueryFields(`/dashboards/name/${fqn}`, arrQueryFields); + + return APIClient.get(url); +}; + +export const addFollower: Function = ( + dashboardID: string, + userId: string +): Promise => { + const configOptions = { + headers: { 'Content-type': 'application/json' }, + }; + + return APIClient.put( + `/dashboards/${dashboardID}/followers`, + userId, + configOptions + ); +}; + +export const removeFollower: Function = ( + dashboardID: string, + userId: string +): Promise => { + const configOptions = { + headers: { 'Content-type': 'application/json' }, + }; + + return APIClient.delete( + `/dashboards/${dashboardID}/followers/${userId}`, + configOptions + ); +}; + +export const patchDashboardDetails: Function = ( + id: string, + data: Dashboard +): Promise => { + const configOptions = { + headers: { 'Content-type': 'application/json-patch+json' }, + }; + + return APIClient.patch(`/dashboards/${id}`, data, configOptions); +}; diff --git a/catalog-rest-service/src/main/resources/ui/src/components/common/error-with-placeholder/ErrorPlaceHolderES.tsx b/catalog-rest-service/src/main/resources/ui/src/components/common/error-with-placeholder/ErrorPlaceHolderES.tsx index 14d7c097f36..01f60848da0 100644 --- a/catalog-rest-service/src/main/resources/ui/src/components/common/error-with-placeholder/ErrorPlaceHolderES.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/components/common/error-with-placeholder/ErrorPlaceHolderES.tsx @@ -44,7 +44,9 @@ const ErrorPlaceHolderES = ({ type }: Props) => {

- {`Hi, ${AppState.userDetails.displayName}!`} + {`Hi, ${ + AppState.userDetails.displayName || AppState.users[0].displayName + }!`}

{type === 'noData' && noRecordForES()} {type === 'error' && ( diff --git a/catalog-rest-service/src/main/resources/ui/src/components/common/facetfilter/FacetFilter.tsx b/catalog-rest-service/src/main/resources/ui/src/components/common/facetfilter/FacetFilter.tsx index 0d40f9c81b0..9b411d5691b 100644 --- a/catalog-rest-service/src/main/resources/ui/src/components/common/facetfilter/FacetFilter.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/components/common/facetfilter/FacetFilter.tsx @@ -33,7 +33,6 @@ const FacetFilter: FunctionComponent = ({ }: FacetProp) => { const [showAllTags, setShowAllTags] = useState(false); const [showAllServices, setShowAllServices] = useState(false); - const [showAllClusters, setShowAllClusters] = useState(false); const [showAllTier, setShowAllTier] = useState(false); const sortAggregations = () => { return aggregations.sort((a, b) => @@ -74,8 +73,6 @@ const FacetFilter: FunctionComponent = ({ return getLinkText(bucketLength, showAllServices, setShowAllServices); case 'Tags': return getLinkText(bucketLength, showAllTags, setShowAllTags); - case 'Service Type': - return getLinkText(bucketLength, showAllClusters, setShowAllClusters); case 'Tier': return getLinkText(bucketLength, showAllTier, setShowAllTier); default: @@ -89,8 +86,6 @@ const FacetFilter: FunctionComponent = ({ return getBuckets(buckets, showAllServices); case 'Tags': return getBuckets(buckets, showAllTags); - case 'Service Type': - return getBuckets(buckets, showAllClusters); case 'Tier': return getBuckets(buckets, showAllTier); default: @@ -176,7 +171,7 @@ FacetFilter.propTypes = { onSelectHandler: PropTypes.func.isRequired, filters: PropTypes.shape({ tags: PropTypes.array.isRequired, - 'service type': PropTypes.array.isRequired, + service: PropTypes.array.isRequired, tier: PropTypes.array.isRequired, }).isRequired, }; diff --git a/catalog-rest-service/src/main/resources/ui/src/components/tags-container/tags-container.tsx b/catalog-rest-service/src/main/resources/ui/src/components/tags-container/tags-container.tsx index 202cb388c13..c35b074906d 100644 --- a/catalog-rest-service/src/main/resources/ui/src/components/tags-container/tags-container.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/components/tags-container/tags-container.tsx @@ -121,7 +121,7 @@ const TagsContainer: FunctionComponent = ({ }; const getTagsContainer = (tag: ColumnTags, index: number) => { - return ( + return tag.tagFQN ? ( = ({ }} tag={`#${tag.tagFQN}`} /> - ); + ) : null; }; const getTagsElement = (tag: ColumnTags, index: number) => { diff --git a/catalog-rest-service/src/main/resources/ui/src/constants/constants.ts b/catalog-rest-service/src/main/resources/ui/src/constants/constants.ts index 8376acbd90d..e44ed5bc975 100644 --- a/catalog-rest-service/src/main/resources/ui/src/constants/constants.ts +++ b/catalog-rest-service/src/main/resources/ui/src/constants/constants.ts @@ -35,6 +35,7 @@ export const ERROR404 = 'No data found'; export const ERROR500 = 'Something went wrong'; const PLACEHOLDER_ROUTE_DATASET_FQN = ':datasetFQN'; const PLACEHOLDER_ROUTE_TOPIC_FQN = ':topicFQN'; +const PLACEHOLDER_ROUTE_DASHBOARD_FQN = ':dashboardFQN'; const PLACEHOLDER_ROUTE_DATABASE_FQN = ':databaseFQN'; const PLACEHOLDER_ROUTE_SERVICE_FQN = ':serviceFQN'; const PLACEHOLDER_ROUTE_SERVICE_TYPE = ':serviceType'; @@ -63,7 +64,7 @@ export const tableSortingFields = [ export const topicSortingFields = [ { - name: 'Last Updated Timestamp', + name: 'Last Updated', value: 'last_updated_timestamp', }, ]; @@ -75,7 +76,7 @@ export const sortingOrder = [ export const facetFilterPlaceholder = [ { - name: 'Service Type', + name: 'Service', value: 'Service', }, { @@ -113,6 +114,7 @@ export const ROUTES = { SIGNIN: '/signin', DATASET_DETAILS: `/dataset/${PLACEHOLDER_ROUTE_DATASET_FQN}`, TOPIC_DETAILS: `/topic/${PLACEHOLDER_ROUTE_TOPIC_FQN}`, + DASHBOARD_DETAILS: `/dashboard/${PLACEHOLDER_ROUTE_DASHBOARD_FQN}`, DATABASE_DETAILS: `/database/${PLACEHOLDER_ROUTE_DATABASE_FQN}`, ONBOARDING: '/onboarding', }; @@ -163,6 +165,12 @@ export const getTopicDetailsPath = (topicFQN: string) => { return path; }; +export const getDashboardDetailsPath = (dashboardFQN: string) => { + let path = ROUTES.DASHBOARD_DETAILS; + path = path.replace(PLACEHOLDER_ROUTE_DASHBOARD_FQN, dashboardFQN); + + return path; +}; export const LIST_TYPES = ['numbered-list', 'bulleted-list']; diff --git a/catalog-rest-service/src/main/resources/ui/src/enums/search.enum.ts b/catalog-rest-service/src/main/resources/ui/src/enums/search.enum.ts index baf56a83fe1..528d8bafc87 100644 --- a/catalog-rest-service/src/main/resources/ui/src/enums/search.enum.ts +++ b/catalog-rest-service/src/main/resources/ui/src/enums/search.enum.ts @@ -24,4 +24,5 @@ export enum FilterType { export enum SearchIndex { TABLE = 'table_search_index', TOPIC = 'topic_search_index', + DASHBOARD = 'dashboard_search_index', } diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/dashboard-details/index.tsx b/catalog-rest-service/src/main/resources/ui/src/pages/dashboard-details/index.tsx new file mode 100644 index 00000000000..6e2e82455e5 --- /dev/null +++ b/catalog-rest-service/src/main/resources/ui/src/pages/dashboard-details/index.tsx @@ -0,0 +1,322 @@ +import { AxiosResponse } from 'axios'; +import { compare } from 'fast-json-patch'; +import { isNil } from 'lodash'; +import { ColumnTags, TableDetail } from 'Models'; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { + addFollower, + getDashboardByFqn, + patchDashboardDetails, + removeFollower, +} from '../../axiosAPIs/dashboardAPI'; +import { getServiceById } from '../../axiosAPIs/serviceAPI'; +import Description from '../../components/common/description/Description'; +import EntityPageInfo from '../../components/common/entityPageInfo/EntityPageInfo'; +import TabsPane from '../../components/common/TabsPane/TabsPane'; +import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface'; +import PageContainer from '../../components/containers/PageContainer'; +import Loader from '../../components/Loader/Loader'; +import ManageTab from '../../components/my-data-details/ManageTab'; +import { getServiceDetailsPath } from '../../constants/constants'; +import { Dashboard, TagLabel } from '../../generated/entity/data/dashboard'; +import { getCurrentUserId, getUserTeams } from '../../utils/CommonUtils'; +import { serviceTypeLogo } from '../../utils/ServiceUtils'; +import { + getOwnerFromId, + getTagsWithoutTier, + getTierFromTableTags, + getUsagePercentile, +} from '../../utils/TableUtils'; +import { getTagCategories, getTaglist } from '../../utils/TagsUtils'; + +const MyDashBoardPage = () => { + const USERId = getCurrentUserId(); + const [tagList, setTagList] = useState>([]); + const { dashboardFQN } = useParams() as Record; + const [dashboardDetails, setDashboardDetails] = useState( + {} as Dashboard + ); + const [dashboardId, setDashboardId] = useState(''); + const [isLoading, setLoading] = useState(false); + const [description, setDescription] = useState(''); + const [followers, setFollowers] = useState(0); + const [isFollowing, setIsFollowing] = useState(false); + const [owner, setOwner] = useState(); + const [tier, setTier] = useState(); + const [tags, setTags] = useState>([]); + const [activeTab, setActiveTab] = useState(1); + const [isEdit, setIsEdit] = useState(false); + const [usage, setUsage] = useState(''); + const [weeklyUsageCount, setWeeklyUsageCount] = useState(''); + const [slashedDashboardName, setSlashedDashboardName] = useState< + TitleBreadcrumbProps['titleLinks'] + >([]); + + const hasEditAccess = () => { + if (owner?.type === 'user') { + return owner.id === getCurrentUserId(); + } else { + return getUserTeams().some((team) => team.id === owner?.id); + } + }; + const tabs = [ + { + name: 'Details', + icon: { + alt: 'schema', + name: 'icon-schema', + title: 'Details', + }, + isProtected: false, + position: 1, + }, + { + name: 'Manage', + icon: { + alt: 'manage', + name: 'icon-manage', + title: 'Manage', + }, + isProtected: true, + protectedState: !owner || hasEditAccess(), + position: 2, + }, + ]; + + const extraInfo = [ + { key: 'Owner', value: owner?.name || '' }, + { key: 'Tier', value: tier ? tier.split('.')[1] : '' }, + { key: 'Usage', value: usage }, + { key: 'Queries', value: `${weeklyUsageCount} past week` }, + ]; + const fetchTags = () => { + getTagCategories().then((res) => { + if (res.data) { + setTagList(getTaglist(res.data)); + } + }); + }; + const fetchDashboardDetail = (dashboardFQN: string) => { + setLoading(true); + getDashboardByFqn(dashboardFQN, [ + 'owner', + 'service', + 'followers', + 'tags', + 'usageSummary', + ]).then((res: AxiosResponse) => { + const { + id, + description, + followers, + service, + tags, + owner, + usageSummary, + displayName, + } = res.data; + setDashboardDetails(res.data); + setDashboardId(id); + setDescription(description ?? ''); + setFollowers(followers?.length); + setOwner(getOwnerFromId(owner?.id)); + setTier(getTierFromTableTags(tags)); + setTags(getTagsWithoutTier(tags)); + setIsFollowing(followers.some(({ id }: { id: string }) => id === USERId)); + if (!isNil(usageSummary?.weeklyStats.percentileRank)) { + const percentile = getUsagePercentile( + usageSummary.weeklyStats.percentileRank + ); + setUsage(percentile); + } else { + setUsage('--'); + } + setWeeklyUsageCount( + usageSummary?.weeklyStats.count.toLocaleString() || '--' + ); + getServiceById('dashboardServices', service?.id).then( + (serviceRes: AxiosResponse) => { + setSlashedDashboardName([ + { + name: serviceRes.data.name, + url: serviceRes.data.name + ? getServiceDetailsPath( + serviceRes.data.name, + serviceRes.data.serviceType + ) + : '', + imgSrc: serviceRes.data.serviceType + ? serviceTypeLogo(serviceRes.data.serviceType) + : undefined, + }, + { + name: displayName, + url: '', + activeTitle: true, + }, + ]); + } + ); + setLoading(false); + }); + }; + + const followDashboard = (): void => { + if (isFollowing) { + removeFollower(dashboardId, USERId).then(() => { + setFollowers((preValu) => preValu - 1); + setIsFollowing(false); + }); + } else { + addFollower(dashboardId, USERId).then(() => { + setFollowers((preValu) => preValu + 1); + setIsFollowing(true); + }); + } + }; + + const onDescriptionUpdate = (updatedHTML: string) => { + const updatedDashboard = { ...dashboardDetails, description: updatedHTML }; + + const jsonPatch = compare(dashboardDetails, updatedDashboard); + patchDashboardDetails(dashboardId, jsonPatch).then((res: AxiosResponse) => { + setDescription(res.data.description); + }); + setIsEdit(false); + }; + const onDescriptionEdit = (): void => { + setIsEdit(true); + }; + const onCancel = () => { + setIsEdit(false); + }; + + const onSettingsUpdate = ( + newOwner?: TableDetail['owner'], + newTier?: TableDetail['tier'] + ): Promise => { + return new Promise((resolve, reject) => { + if (newOwner || newTier) { + const tierTag: TableDetail['tags'] = newTier + ? [ + ...getTagsWithoutTier(dashboardDetails.tags as ColumnTags[]), + { tagFQN: newTier, labelType: 'Manual', state: 'Confirmed' }, + ] + : (dashboardDetails.tags as ColumnTags[]); + const updatedDashboard = { + ...dashboardDetails, + owner: newOwner + ? { ...dashboardDetails.owner, ...newOwner } + : dashboardDetails.owner, + tags: tierTag, + }; + const jsonPatch = compare(dashboardDetails, updatedDashboard); + patchDashboardDetails(dashboardId, jsonPatch) + .then((res: AxiosResponse) => { + setDashboardDetails(res.data); + setOwner(getOwnerFromId(res.data.owner?.id)); + setTier(getTierFromTableTags(res.data.tags)); + resolve(); + }) + .catch(() => reject()); + } else { + reject(); + } + }); + }; + + const onTagUpdate = (selectedTags?: Array) => { + if (selectedTags) { + const prevTags = dashboardDetails?.tags?.filter((tag) => + selectedTags.includes(tag?.tagFQN as string) + ); + const newTags: Array = selectedTags + .filter((tag) => { + return !prevTags?.map((prevTag) => prevTag.tagFQN).includes(tag); + }) + .map((tag) => ({ + labelType: 'Manual', + state: 'Confirmed', + tagFQN: tag, + })); + const updatedTags = [...(prevTags as TagLabel[]), ...newTags]; + const updatedDashboard = { ...dashboardDetails, tags: updatedTags }; + const jsonPatch = compare(dashboardDetails, updatedDashboard); + patchDashboardDetails(dashboardId, jsonPatch).then( + (res: AxiosResponse) => { + setTier(getTierFromTableTags(res.data.tags)); + setTags(getTagsWithoutTier(res.data.tags)); + } + ); + } + }; + + useEffect(() => { + fetchDashboardDetail(dashboardFQN); + }, [dashboardFQN]); + + useEffect(() => { + fetchTags(); + }, []); + + return ( + + {isLoading ? ( + + ) : ( +
+ +
+ + +
+ {activeTab === 1 && ( + <> +
+
+ +
+
+ + )} + {activeTab === 2 && ( + + )} +
+
+
+ )} +
+ ); +}; + +export default MyDashBoardPage; diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/explore/explore.constants.ts b/catalog-rest-service/src/main/resources/ui/src/pages/explore/explore.constants.ts index 9344736715f..3af9afe8d0a 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/explore/explore.constants.ts +++ b/catalog-rest-service/src/main/resources/ui/src/pages/explore/explore.constants.ts @@ -17,7 +17,12 @@ import { lowerCase } from 'lodash'; import { AggregationType, Bucket } from 'Models'; -import { tiers } from '../../constants/constants'; +import { + tableSortingFields, + tiers, + topicSortingFields, +} from '../../constants/constants'; +import { SearchIndex } from '../../enums/search.enum'; export const getBucketList = (buckets: Array) => { let bucketList: Array = [...tiers]; @@ -53,3 +58,27 @@ export const getAggrWithDefaultValue = ( ? aggregations : aggregations.filter((item) => allowedAgg.includes(lowerCase(item.title))); }; + +export const tabsInfo = [ + { + label: 'Tables', + index: SearchIndex.TABLE, + sortingFields: tableSortingFields, + sortField: tableSortingFields[0].value, + tab: 1, + }, + { + label: 'Topics', + index: SearchIndex.TOPIC, + sortingFields: topicSortingFields, + sortField: topicSortingFields[0].value, + tab: 2, + }, + { + label: 'Dashboards', + index: SearchIndex.DASHBOARD, + sortingFields: topicSortingFields, + sortField: topicSortingFields[0].value, + tab: 3, + }, +]; diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/explore/index.tsx b/catalog-rest-service/src/main/resources/ui/src/pages/explore/index.tsx index f743b7551f8..250fdc2579d 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/explore/index.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/pages/explore/index.tsx @@ -37,7 +37,6 @@ import { ERROR500, PAGE_SIZE, tableSortingFields, - topicSortingFields, } from '../../constants/constants'; import { SearchIndex } from '../../enums/search.enum'; import { usePrevious } from '../../hooks/usePrevious'; @@ -47,10 +46,10 @@ import { formatDataResponse } from '../../utils/APIUtils'; import { getCountBadge } from '../../utils/CommonUtils'; import { getFilterString } from '../../utils/FilterUtils'; import { dropdownIcon as DropDownIcon } from '../../utils/svgconstant'; -import { getAggrWithDefaultValue } from './explore.constants'; +import { getAggrWithDefaultValue, tabsInfo } from './explore.constants'; import { Params } from './explore.interface'; -const visibleFilters = ['tags', 'service type', 'tier']; +const visibleFilters = ['tags', 'service', 'tier']; const getQueryParam = (urlSearchQuery = ''): FilterObject => { const arrSearchQuery = urlSearchQuery @@ -74,7 +73,7 @@ const ExplorePage: React.FC = (): React.ReactElement => { const location = useLocation(); const filterObject: FilterObject = { - ...{ tags: [], 'service type': [], tier: [] }, + ...{ tags: [], service: [], tier: [] }, ...getQueryParam(location.search), }; const showToast = useToastContext(); @@ -95,6 +94,7 @@ const ExplorePage: React.FC = (): React.ReactElement => { const [currentTab, setCurrentTab] = useState(1); const [tableCount, setTableCount] = useState(0); const [topicCount, setTopicCount] = useState(0); + const [dashboardCount, setDashboardCount] = useState(0); const [fieldList, setFieldList] = useState>(tableSortingFields); const isMounting = useRef(true); @@ -198,10 +198,20 @@ const ExplorePage: React.FC = (): React.ReactElement => { emptyValue, SearchIndex.TOPIC ); - Promise.all([tableCount, topicCount]) - .then(([table, topic]: Array) => { + const dashboardCount = searchData( + searchText, + 0, + 0, + getFilterString(filters), + emptyValue, + emptyValue, + SearchIndex.DASHBOARD + ); + Promise.all([tableCount, topicCount, dashboardCount]) + .then(([table, topic, dashboard]: Array) => { setTableCount(table.data.hits.total.value); setTopicCount(topic.data.hits.total.value); + setDashboardCount(dashboard.data.hits.total.value); }) .catch((err: AxiosError) => { setError(ERROR404); @@ -228,7 +238,7 @@ const ExplorePage: React.FC = (): React.ReactElement => { searchText, currentPage, 0, - getFilterString(filters, ['service type']), + getFilterString(filters, ['service']), sortField, sortOrder, searchIndex @@ -270,7 +280,7 @@ const ExplorePage: React.FC = (): React.ReactElement => { } else { const aggServiceType = getAggregationList( resAggServiceType.data.aggregations, - 'service type' + 'service' ); const aggTier = getAggregationList( resAggTier.data.aggregations, @@ -380,37 +390,42 @@ const ExplorePage: React.FC = (): React.ReactElement => { }); }; + const getTabCount = (index: string) => { + switch (index) { + case SearchIndex.TABLE: + return getCountBadge(tableCount); + case SearchIndex.TOPIC: + return getCountBadge(topicCount); + case SearchIndex.DASHBOARD: + return getCountBadge(dashboardCount); + default: + return getCountBadge(); + } + }; + const getTabs = () => { return (
diff --git a/catalog-rest-service/src/main/resources/ui/src/pages/my-data-details/index.tsx b/catalog-rest-service/src/main/resources/ui/src/pages/my-data-details/index.tsx index 264e3f320fa..888d68ee78d 100644 --- a/catalog-rest-service/src/main/resources/ui/src/pages/my-data-details/index.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/pages/my-data-details/index.tsx @@ -139,6 +139,7 @@ const MyDataDetailsPage = () => { const extraInfo = [ { key: 'Owner', value: owner?.name || '' }, + { key: 'Tier', value: tier ? tier.split('.')[1] : '' }, { key: 'Usage', value: usage }, { key: 'Queries', value: `${weeklyUsageCount} past week` }, ]; diff --git a/catalog-rest-service/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx b/catalog-rest-service/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx index 6688a7f11e0..6b40b74420b 100644 --- a/catalog-rest-service/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx @@ -21,6 +21,7 @@ import { Redirect, Route, Switch } from 'react-router-dom'; import AppState from '../AppState'; import Onboarding from '../components/onboarding/Onboarding'; // Remove this route once Onboarding is added to my-data import { ROUTES } from '../constants/constants'; +import MyDashBoardPage from '../pages/dashboard-details'; import DatabaseDetails from '../pages/database-details/index'; import ExplorePage from '../pages/explore'; import MyDataPage from '../pages/my-data'; @@ -64,6 +65,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { + diff --git a/catalog-rest-service/src/main/resources/ui/src/utils/APIUtils.js b/catalog-rest-service/src/main/resources/ui/src/utils/APIUtils.js index 8d04094f5c0..87d2cb020e8 100644 --- a/catalog-rest-service/src/main/resources/ui/src/utils/APIUtils.js +++ b/catalog-rest-service/src/main/resources/ui/src/utils/APIUtils.js @@ -6,8 +6,12 @@ import { getRelativeTime } from './TimeUtils'; export const formatDataResponse = (hits) => { const formattedData = hits.map((hit) => { const newData = {}; - newData.id = hit._source.table_id || hit._source.topic_id; - newData.name = hit._source.table_name || hit._source.topic_name; + newData.id = + hit._source.table_id || hit._source.topic_id || hit._source.dashboard_id; + newData.name = + hit._source.table_name || + hit._source.topic_name || + hit._source.dashboard_name; newData.description = hit._source.description; newData.fullyQualifiedName = hit._source.fqdn; newData.tableType = hit._source.table_type; diff --git a/catalog-rest-service/src/main/resources/ui/src/utils/FilterUtils.js b/catalog-rest-service/src/main/resources/ui/src/utils/FilterUtils.js index 95e2aca627f..bd8debbbc6f 100644 --- a/catalog-rest-service/src/main/resources/ui/src/utils/FilterUtils.js +++ b/catalog-rest-service/src/main/resources/ui/src/utils/FilterUtils.js @@ -7,7 +7,8 @@ export const getFilterString = (filters, excludeFilters = []) => { const modifiedFilter = []; const filter = filters[key]; filter.forEach((value) => { - modifiedFilter.push(`${key.split(' ').join('_')}:${value}`); + const modifiedKey = key === 'service' ? 'service type' : key; + modifiedFilter.push(`${modifiedKey.split(' ').join('_')}:${value}`); }); modifiedFilters[key] = modifiedFilter; } diff --git a/catalog-rest-service/src/main/resources/ui/src/utils/TableUtils.tsx b/catalog-rest-service/src/main/resources/ui/src/utils/TableUtils.tsx index 1927d003083..dc572264549 100644 --- a/catalog-rest-service/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/catalog-rest-service/src/main/resources/ui/src/utils/TableUtils.tsx @@ -3,6 +3,7 @@ import React from 'react'; import AppState from '../AppState'; import PopOver from '../components/common/popover/PopOver'; import { + getDashboardDetailsPath, getDatasetDetailsPath, getTopicDetailsPath, } from '../constants/constants'; @@ -156,6 +157,9 @@ export const getEntityLink = ( case SearchIndex.TOPIC: return getTopicDetailsPath(fullyQualifiedName); + case SearchIndex.DASHBOARD: + return getDashboardDetailsPath(fullyQualifiedName); + case SearchIndex.TABLE: default: return getDatasetDetailsPath(fullyQualifiedName);