mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-27 08:44:49 +00:00
Adding dashboard tab on explore page (#398)
* Adding dashboard tab on explore page * minor fix
This commit is contained in:
parent
072c2034ad
commit
1fa033798a
@ -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<AxiosResponse> => {
|
||||
const url = `${getURLWithQueryFields(
|
||||
`/dashboards`,
|
||||
arrQueryFields
|
||||
)}&service=${serviceName}${paging ? paging : ''}`;
|
||||
|
||||
return APIClient.get(url);
|
||||
};
|
||||
|
||||
export const getDashboardByFqn: Function = (
|
||||
fqn: string,
|
||||
arrQueryFields: string
|
||||
): Promise<AxiosResponse> => {
|
||||
const url = getURLWithQueryFields(`/dashboards/name/${fqn}`, arrQueryFields);
|
||||
|
||||
return APIClient.get(url);
|
||||
};
|
||||
|
||||
export const addFollower: Function = (
|
||||
dashboardID: string,
|
||||
userId: string
|
||||
): Promise<AxiosResponse> => {
|
||||
const configOptions = {
|
||||
headers: { 'Content-type': 'application/json' },
|
||||
};
|
||||
|
||||
return APIClient.put(
|
||||
`/dashboards/${dashboardID}/followers`,
|
||||
userId,
|
||||
configOptions
|
||||
);
|
||||
};
|
||||
|
||||
export const removeFollower: Function = (
|
||||
dashboardID: string,
|
||||
userId: string
|
||||
): Promise<AxiosResponse> => {
|
||||
const configOptions = {
|
||||
headers: { 'Content-type': 'application/json' },
|
||||
};
|
||||
|
||||
return APIClient.delete(
|
||||
`/dashboards/${dashboardID}/followers/${userId}`,
|
||||
configOptions
|
||||
);
|
||||
};
|
||||
|
||||
export const patchDashboardDetails: Function = (
|
||||
id: string,
|
||||
data: Dashboard
|
||||
): Promise<AxiosResponse> => {
|
||||
const configOptions = {
|
||||
headers: { 'Content-type': 'application/json-patch+json' },
|
||||
};
|
||||
|
||||
return APIClient.patch(`/dashboards/${id}`, data, configOptions);
|
||||
};
|
||||
@ -44,7 +44,9 @@ const ErrorPlaceHolderES = ({ type }: Props) => {
|
||||
</div>
|
||||
<div className="tw-flex tw-flex-col tw-items-center tw-mt-10 tw-text-base tw-font-normal">
|
||||
<p className="tw-text-lg tw-font-bold tw-mb-1 tw-text-primary">
|
||||
{`Hi, ${AppState.userDetails.displayName}!`}
|
||||
{`Hi, ${
|
||||
AppState.userDetails.displayName || AppState.users[0].displayName
|
||||
}!`}
|
||||
</p>
|
||||
{type === 'noData' && noRecordForES()}
|
||||
{type === 'error' && (
|
||||
|
||||
@ -33,7 +33,6 @@ const FacetFilter: FunctionComponent<FacetProp> = ({
|
||||
}: FacetProp) => {
|
||||
const [showAllTags, setShowAllTags] = useState<boolean>(false);
|
||||
const [showAllServices, setShowAllServices] = useState<boolean>(false);
|
||||
const [showAllClusters, setShowAllClusters] = useState<boolean>(false);
|
||||
const [showAllTier, setShowAllTier] = useState<boolean>(false);
|
||||
const sortAggregations = () => {
|
||||
return aggregations.sort((a, b) =>
|
||||
@ -74,8 +73,6 @@ const FacetFilter: FunctionComponent<FacetProp> = ({
|
||||
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<FacetProp> = ({
|
||||
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,
|
||||
};
|
||||
|
||||
@ -121,7 +121,7 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
|
||||
};
|
||||
|
||||
const getTagsContainer = (tag: ColumnTags, index: number) => {
|
||||
return (
|
||||
return tag.tagFQN ? (
|
||||
<Tags
|
||||
className="tw-bg-gray-200"
|
||||
editable={editable}
|
||||
@ -132,7 +132,7 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
|
||||
}}
|
||||
tag={`#${tag.tagFQN}`}
|
||||
/>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
const getTagsElement = (tag: ColumnTags, index: number) => {
|
||||
|
||||
@ -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'];
|
||||
|
||||
|
||||
@ -24,4 +24,5 @@ export enum FilterType {
|
||||
export enum SearchIndex {
|
||||
TABLE = 'table_search_index',
|
||||
TOPIC = 'topic_search_index',
|
||||
DASHBOARD = 'dashboard_search_index',
|
||||
}
|
||||
|
||||
@ -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<Array<string>>([]);
|
||||
const { dashboardFQN } = useParams() as Record<string, string>;
|
||||
const [dashboardDetails, setDashboardDetails] = useState<Dashboard>(
|
||||
{} as Dashboard
|
||||
);
|
||||
const [dashboardId, setDashboardId] = useState<string>('');
|
||||
const [isLoading, setLoading] = useState<boolean>(false);
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const [followers, setFollowers] = useState<number>(0);
|
||||
const [isFollowing, setIsFollowing] = useState(false);
|
||||
const [owner, setOwner] = useState<TableDetail['owner']>();
|
||||
const [tier, setTier] = useState<string>();
|
||||
const [tags, setTags] = useState<Array<ColumnTags>>([]);
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
const [isEdit, setIsEdit] = useState<boolean>(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<void> => {
|
||||
return new Promise<void>((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<string>) => {
|
||||
if (selectedTags) {
|
||||
const prevTags = dashboardDetails?.tags?.filter((tag) =>
|
||||
selectedTags.includes(tag?.tagFQN as string)
|
||||
);
|
||||
const newTags: Array<ColumnTags> = 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 (
|
||||
<PageContainer>
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<div className="tw-px-4 w-full">
|
||||
<EntityPageInfo
|
||||
isTagEditable
|
||||
extraInfo={extraInfo}
|
||||
followers={followers}
|
||||
followHandler={followDashboard}
|
||||
isFollowing={isFollowing}
|
||||
tagList={tagList}
|
||||
tags={tags}
|
||||
tagsHandler={onTagUpdate}
|
||||
tier={tier || ''}
|
||||
titleLinks={slashedDashboardName}
|
||||
/>
|
||||
<div className="tw-block tw-mt-1">
|
||||
<TabsPane
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
tabs={tabs}
|
||||
/>
|
||||
|
||||
<div className="tw-bg-white tw--mx-4 tw-p-4">
|
||||
{activeTab === 1 && (
|
||||
<>
|
||||
<div className="tw-grid tw-grid-cols-4 tw-gap-4 w-full">
|
||||
<div className="tw-col-span-full">
|
||||
<Description
|
||||
description={description}
|
||||
hasEditAccess={hasEditAccess()}
|
||||
isEdit={isEdit}
|
||||
owner={owner}
|
||||
onCancel={onCancel}
|
||||
onDescriptionEdit={onDescriptionEdit}
|
||||
onDescriptionUpdate={onDescriptionUpdate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeTab === 2 && (
|
||||
<ManageTab
|
||||
currentTier={tier}
|
||||
currentUser={owner?.id}
|
||||
hasEditAccess={hasEditAccess()}
|
||||
onSave={onSettingsUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyDashBoardPage;
|
||||
@ -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<Bucket>) => {
|
||||
let bucketList: Array<Bucket> = [...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,
|
||||
},
|
||||
];
|
||||
|
||||
@ -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<number>(1);
|
||||
const [tableCount, setTableCount] = useState<number>(0);
|
||||
const [topicCount, setTopicCount] = useState<number>(0);
|
||||
const [dashboardCount, setDashboardCount] = useState<number>(0);
|
||||
const [fieldList, setFieldList] =
|
||||
useState<Array<{ name: string; value: string }>>(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<SearchResponse>) => {
|
||||
const dashboardCount = searchData(
|
||||
searchText,
|
||||
0,
|
||||
0,
|
||||
getFilterString(filters),
|
||||
emptyValue,
|
||||
emptyValue,
|
||||
SearchIndex.DASHBOARD
|
||||
);
|
||||
Promise.all([tableCount, topicCount, dashboardCount])
|
||||
.then(([table, topic, dashboard]: Array<SearchResponse>) => {
|
||||
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 (
|
||||
<div className="tw-mb-3 tw--mt-4">
|
||||
<nav className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-4 tw-justify-between">
|
||||
<div>
|
||||
<button
|
||||
className={`tw-pb-2 tw-px-4 tw-gh-tabs ${getActiveTabClass(1)}`}
|
||||
onClick={() => {
|
||||
setCurrentTab(1);
|
||||
setSearchIndex(SearchIndex.TABLE);
|
||||
setFieldList(tableSortingFields);
|
||||
setSortField(tableSortingFields[0].value);
|
||||
setCurrentPage(1);
|
||||
resetFilters();
|
||||
}}>
|
||||
Tables
|
||||
{getCountBadge(tableCount)}
|
||||
</button>
|
||||
<button
|
||||
className={`tw-pb-2 tw-px-4 tw-gh-tabs ${getActiveTabClass(2)}`}
|
||||
onClick={() => {
|
||||
setCurrentTab(2);
|
||||
setSearchIndex(SearchIndex.TOPIC);
|
||||
setFieldList(topicSortingFields);
|
||||
setSortField(topicSortingFields[0].value);
|
||||
setCurrentPage(1);
|
||||
resetFilters();
|
||||
}}>
|
||||
Topics
|
||||
{getCountBadge(topicCount)}
|
||||
</button>
|
||||
{tabsInfo.map((tab, index) => (
|
||||
<button
|
||||
className={`tw-pb-2 tw-px-4 tw-gh-tabs ${getActiveTabClass(
|
||||
tab.tab
|
||||
)}`}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setCurrentTab(tab.tab);
|
||||
setSearchIndex(tab.index);
|
||||
setFieldList(tab.sortingFields);
|
||||
setSortField(tab.sortField);
|
||||
setCurrentPage(1);
|
||||
resetFilters();
|
||||
}}>
|
||||
{tab.label}
|
||||
{getTabCount(tab.index)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{getSortingElements()}
|
||||
</nav>
|
||||
|
||||
@ -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` },
|
||||
];
|
||||
|
||||
@ -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 = () => {
|
||||
<Route component={DatabaseDetails} path={ROUTES.DATABASE_DETAILS} />
|
||||
<Route component={MyDataDetailsPage} path={ROUTES.DATASET_DETAILS} />
|
||||
<Route component={MyTopicDetailPage} path={ROUTES.TOPIC_DETAILS} />
|
||||
<Route component={MyDashBoardPage} path={ROUTES.DASHBOARD_DETAILS} />
|
||||
<Route component={Onboarding} path={ROUTES.ONBOARDING} />
|
||||
<Redirect to={ROUTES.NOT_FOUND} />
|
||||
</Switch>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user