UI - feedback bug fixes and improvements (#12592)

* fixed loading issue in KPI widget on landing page

* fixed redirection links for the notification box tabs

* Fixed redirection issue after soft and hard deleting data assets

* fixed version page layout issue

* Fixed data model restore issue

* Added loader to summary panel while fetching permissions

* Fixed alerts settings page error placeholder alignment issue

* changed the dashboard and pipeline url display method

* fixed cypress test

* updated the logic to perform the after delete action in DeleteWidgetModal

* changed the logic to update the history object through util functions

* added `experimentalMemoryManagement` to the cypress config

* reverted the cypress config changes

* fixed flaky test

* worked on comments

* removed unnecessary code
This commit is contained in:
Aniket Katkar 2023-07-28 18:20:55 +05:30 committed by GitHub
parent cb9e5d8b6f
commit fe3766e106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 347 additions and 188 deletions

View File

@ -706,10 +706,12 @@ export const deleteSoftDeletedUser = (username) => {
cy.get('[data-testid="search-error-placeholder"]').should('be.visible'); cy.get('[data-testid="search-error-placeholder"]').should('be.visible');
}; };
export const toastNotification = (msg) => { export const toastNotification = (msg, closeToast = true) => {
cy.get('.Toastify__toast-body').should('be.visible').contains(msg); cy.get('.Toastify__toast-body').should('be.visible').contains(msg);
cy.wait(200); cy.wait(200);
cy.get('.Toastify__close-button').should('be.visible').click(); if (closeToast) {
cy.get('.Toastify__close-button').should('be.visible').click();
}
}; };
export const addCustomPropertiesForEntity = ( export const addCustomPropertiesForEntity = (

View File

@ -66,7 +66,7 @@ describe('Restore entity functionality should work properly', () => {
cy.get('[data-testid="confirm-button"]').click(); cy.get('[data-testid="confirm-button"]').click();
verifyResponseStatusCode('@softDeleteTable', 200); verifyResponseStatusCode('@softDeleteTable', 200);
toastNotification('Table deleted successfully!'); toastNotification('Table deleted successfully!', false);
}); });
it('Check Soft Deleted entity table', () => { it('Check Soft Deleted entity table', () => {

View File

@ -25,16 +25,17 @@ import { TOAST_OPTIONS } from 'constants/Toasts.constants';
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from 'react';
import { HelmetProvider } from 'react-helmet-async'; import { HelmetProvider } from 'react-helmet-async';
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import { BrowserRouter as Router } from 'react-router-dom'; import { Router } from 'react-router-dom';
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.min.css'; import 'react-toastify/dist/ReactToastify.min.css';
import { history } from 'utils/HistoryUtils';
import i18n from 'utils/i18next/LocalUtil'; import i18n from 'utils/i18next/LocalUtil';
const App: FunctionComponent = () => { const App: FunctionComponent = () => {
return ( return (
<div className="main-container"> <div className="main-container">
<div className="content-wrapper" data-testid="content-wrapper"> <div className="content-wrapper" data-testid="content-wrapper">
<Router> <Router history={history}>
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
<ErrorBoundary> <ErrorBoundary>
<ApplicationConfigProvider> <ApplicationConfigProvider>

View File

@ -67,6 +67,7 @@ import {
import { withActivityFeed } from 'components/router/withActivityFeed'; import { withActivityFeed } from 'components/router/withActivityFeed';
import TableDescription from 'components/TableDescription/TableDescription.component'; import TableDescription from 'components/TableDescription/TableDescription.component';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface'; import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
const DashboardDetails = ({ const DashboardDetails = ({
charts, charts,
@ -723,6 +724,7 @@ const DashboardDetails = ({
<Row gutter={[0, 12]}> <Row gutter={[0, 12]}>
<Col className="p-x-lg" span={24}> <Col className="p-x-lg" span={24}>
<DataAssetsHeader <DataAssetsHeader
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={dashboardDetails} dataAsset={dashboardDetails}
entityType={EntityType.DASHBOARD} entityType={EntityType.DASHBOARD}
permissions={dashboardPermissions} permissions={dashboardPermissions}

View File

@ -104,6 +104,7 @@ export const ExtraInfoLink = ({
export const DataAssetsHeader = ({ export const DataAssetsHeader = ({
allowSoftDelete = true, allowSoftDelete = true,
afterDeleteAction,
dataAsset, dataAsset,
onOwnerUpdate, onOwnerUpdate,
onTierUpdate, onTierUpdate,
@ -403,6 +404,7 @@ export const DataAssetsHeader = ({
/> />
</Tooltip> </Tooltip>
<ManageButton <ManageButton
afterDeleteAction={afterDeleteAction}
allowSoftDelete={!dataAsset.deleted && allowSoftDelete} allowSoftDelete={!dataAsset.deleted && allowSoftDelete}
canDelete={permissions.Delete} canDelete={permissions.Delete}
deleted={dataAsset.deleted} deleted={dataAsset.deleted}

View File

@ -75,6 +75,7 @@ export type DataAssetsHeaderProps = {
permissions: OperationPermission; permissions: OperationPermission;
allowSoftDelete?: boolean; allowSoftDelete?: boolean;
isRecursiveDelete?: boolean; isRecursiveDelete?: boolean;
afterDeleteAction?: (isSoftDelete?: boolean) => void;
onTierUpdate: (tier?: string) => Promise<void>; onTierUpdate: (tier?: string) => Promise<void>;
onOwnerUpdate: (owner?: EntityReference) => Promise<void>; onOwnerUpdate: (owner?: EntityReference) => Promise<void>;
onVersionClick?: () => void; onVersionClick?: () => void;

View File

@ -37,7 +37,7 @@ function DataAssetsVersionHeader({
return ( return (
<Row className="p-x-lg" gutter={[8, 12]} justify="space-between"> <Row className="p-x-lg" gutter={[8, 12]} justify="space-between">
<Col className="self-center"> <Col className="self-center" span={21}>
<Row gutter={[16, 12]}> <Row gutter={[16, 12]}>
<Col span={24}> <Col span={24}>
<TitleBreadcrumb titleLinks={breadcrumbLinks} /> <TitleBreadcrumb titleLinks={breadcrumbLinks} />
@ -83,14 +83,18 @@ function DataAssetsVersionHeader({
</Col> </Col>
</Row> </Row>
</Col> </Col>
<Col> <Col span={3}>
<Button <Row justify="end">
className="w-16 p-0" <Col>
data-testid="version-button" <Button
icon={<Icon component={VersionIcon} />} className="w-16 p-0"
onClick={onVersionClick}> data-testid="version-button"
<Typography.Text>{version}</Typography.Text> icon={<Icon component={VersionIcon} />}
</Button> onClick={onVersionClick}>
<Typography.Text>{version}</Typography.Text>
</Button>
</Col>
</Row>
</Col> </Col>
</Row> </Row>
); );

View File

@ -43,6 +43,7 @@ import KPILatestResultsV1 from './KPILatestResultsV1';
interface Props { interface Props {
kpiList: Array<Kpi>; kpiList: Array<Kpi>;
selectedDays: number; selectedDays: number;
isKPIListLoading: boolean;
} }
const EmptyPlaceholder = () => { const EmptyPlaceholder = () => {
@ -78,13 +79,13 @@ const EmptyPlaceholder = () => {
); );
}; };
const KPIChartV1: FC<Props> = ({ kpiList, selectedDays }) => { const KPIChartV1: FC<Props> = ({ isKPIListLoading, kpiList, selectedDays }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [kpiResults, setKpiResults] = useState<KpiResult[]>([]); const [kpiResults, setKpiResults] = useState<KpiResult[]>([]);
const [kpiLatestResults, setKpiLatestResults] = const [kpiLatestResults, setKpiLatestResults] =
useState<Record<string, UIKpiResult>>(); useState<Record<string, UIKpiResult>>();
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(false);
const fetchKpiResults = useCallback(async () => { const fetchKpiResults = useCallback(async () => {
setIsLoading(true); setIsLoading(true);
@ -180,7 +181,7 @@ const KPIChartV1: FC<Props> = ({ kpiList, selectedDays }) => {
className="kpi-widget-card h-full" className="kpi-widget-card h-full"
data-testid="kpi-card" data-testid="kpi-card"
id="kpi-charts" id="kpi-charts"
loading={isLoading}> loading={isKPIListLoading || isLoading}>
<Row> <Row>
<Col span={24}> <Col span={24}>
<Typography.Text className="font-medium"> <Typography.Text className="font-medium">

View File

@ -12,6 +12,7 @@
*/ */
import { Card, Col, Row, Space, Tabs } from 'antd'; import { Card, Col, Row, Space, Tabs } from 'antd';
import { AxiosError } from 'axios';
import { useActivityFeedProvider } from 'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider'; import { useActivityFeedProvider } from 'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import { ActivityFeedTab } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component'; import { ActivityFeedTab } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel'; import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
@ -37,10 +38,13 @@ import { EntityTags } from 'Models';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { getFeedCounts } from 'utils/CommonUtils'; import { restoreDataModel } from 'rest/dataModelsAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getFeedCounts, refreshPage } from 'utils/CommonUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils'; import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getEntityFieldThreadCounts } from 'utils/FeedUtils'; import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
import { getTagsWithoutTier } from 'utils/TableUtils'; import { getTagsWithoutTier } from 'utils/TableUtils';
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
import { DataModelDetailsProps } from './DataModelDetails.interface'; import { DataModelDetailsProps } from './DataModelDetails.interface';
import ModelTab from './ModelTab/ModelTab.component'; import ModelTab from './ModelTab/ModelTab.component';
@ -159,6 +163,26 @@ const DataModelDetails = ({
handleUpdateTags(updatedTags); handleUpdateTags(updatedTags);
}; };
const handleRestoreDataModel = async () => {
try {
await restoreDataModel(dataModelData.id ?? '');
showSuccessToast(
t('message.restore-entities-success', {
entity: t('label.data-model'),
}),
2000
);
refreshPage();
} catch (error) {
showErrorToast(
error as AxiosError,
t('message.restore-entities-error', {
entity: t('label.data-model'),
})
);
}
};
const modelComponent = useMemo(() => { const modelComponent = useMemo(() => {
return ( return (
<Row gutter={[0, 16]} wrap={false}> <Row gutter={[0, 16]} wrap={false}>
@ -335,13 +359,14 @@ const DataModelDetails = ({
<Row gutter={[0, 12]}> <Row gutter={[0, 12]}>
<Col className="p-x-lg" span={24}> <Col className="p-x-lg" span={24}>
<DataAssetsHeader <DataAssetsHeader
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={dataModelData} dataAsset={dataModelData}
entityType={EntityType.DASHBOARD_DATA_MODEL} entityType={EntityType.DASHBOARD_DATA_MODEL}
permissions={dataModelPermissions} permissions={dataModelPermissions}
onDisplayNameUpdate={handleUpdateDisplayName} onDisplayNameUpdate={handleUpdateDisplayName}
onFollowClick={handleFollowDataModel} onFollowClick={handleFollowDataModel}
onOwnerUpdate={handleUpdateOwner} onOwnerUpdate={handleUpdateOwner}
onRestoreDataAsset={() => Promise.resolve()} onRestoreDataAsset={handleRestoreDataModel}
onTierUpdate={handleUpdateTier} onTierUpdate={handleUpdateTier}
onVersionClick={versionHandler} onVersionClick={versionHandler}
/> />

View File

@ -13,6 +13,7 @@
import { Drawer, Typography } from 'antd'; import { Drawer, Typography } from 'antd';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import Loader from 'components/Loader/Loader';
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider'; import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import { import {
OperationPermission, OperationPermission,
@ -51,18 +52,22 @@ export default function EntitySummaryPanel({
const { tab } = useParams<{ tab: string }>(); const { tab } = useParams<{ tab: string }>();
const { getEntityPermission } = usePermissionProvider(); const { getEntityPermission } = usePermissionProvider();
const [isPermissionLoading, setIsPermissionLoading] =
useState<boolean>(false);
const [entityPermissions, setEntityPermissions] = const [entityPermissions, setEntityPermissions] =
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION); useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
const fetchResourcePermission = async (entityFqn: string) => { const fetchResourcePermission = async (entityFqn: string) => {
try { try {
setIsPermissionLoading(true);
const type = const type =
get(entityDetails, 'details.entityType') ?? ResourceEntity.TABLE; get(entityDetails, 'details.entityType') ?? ResourceEntity.TABLE;
const permissions = await getEntityPermission(type, entityFqn); const permissions = await getEntityPermission(type, entityFqn);
setEntityPermissions(permissions); setEntityPermissions(permissions);
} catch (error) { } catch (error) {
// Error // Error
} finally {
setIsPermissionLoading(false);
} }
}; };
@ -78,6 +83,9 @@ export default function EntitySummaryPanel({
); );
const summaryComponent = useMemo(() => { const summaryComponent = useMemo(() => {
if (isPermissionLoading) {
return <Loader />;
}
if (!viewPermission) { if (!viewPermission) {
return ( return (
<ErrorPlaceHolder <ErrorPlaceHolder
@ -117,7 +125,7 @@ export default function EntitySummaryPanel({
default: default:
return null; return null;
} }
}, [tab, entityDetails, viewPermission]); }, [tab, entityDetails, viewPermission, isPermissionLoading]);
const entityLink = useMemo( const entityLink = useMemo(
() => () =>

View File

@ -22,14 +22,18 @@ import './kpi-widget.less';
const KPIWidget = () => { const KPIWidget = () => {
const [kpiList, setKpiList] = useState<Array<Kpi>>([]); const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
const [isKPIListLoading, setIsKPIListLoading] = useState<boolean>(false);
const fetchKpiList = async () => { const fetchKpiList = async () => {
try { try {
setIsKPIListLoading(true);
const response = await getListKPIs({ fields: 'dataInsightChart' }); const response = await getListKPIs({ fields: 'dataInsightChart' });
setKpiList(response.data); setKpiList(response.data);
} catch (_err) { } catch (_err) {
setKpiList([]); setKpiList([]);
showErrorToast(_err as AxiosError); showErrorToast(_err as AxiosError);
} finally {
setIsKPIListLoading(false);
} }
}; };
@ -41,7 +45,11 @@ const KPIWidget = () => {
return ( return (
<div className="kpi-widget-container h-full"> <div className="kpi-widget-container h-full">
<KPIChartV1 kpiList={kpiList} selectedDays={CHART_WIDGET_DAYS_DURATION} /> <KPIChartV1
isKPIListLoading={isKPIListLoading}
kpiList={kpiList}
selectedDays={CHART_WIDGET_DAYS_DURATION}
/>
</div> </div>
); );
}; };

View File

@ -34,6 +34,7 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { restoreMlmodel } from 'rest/mlModelAPI'; import { restoreMlmodel } from 'rest/mlModelAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils'; import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { getMlModelDetailsPath } from '../../constants/constants'; import { getMlModelDetailsPath } from '../../constants/constants';
@ -555,6 +556,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
<Row gutter={[0, 12]}> <Row gutter={[0, 12]}>
<Col className="p-x-lg" span={24}> <Col className="p-x-lg" span={24}>
<DataAssetsHeader <DataAssetsHeader
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={mlModelDetail} dataAsset={mlModelDetail}
entityType={EntityType.MLMODEL} entityType={EntityType.MLMODEL}
permissions={mlModelPermissions} permissions={mlModelPermissions}

View File

@ -15,7 +15,6 @@ import { Badge, Button, List, Tabs, Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { ActivityFeedTabs } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface'; import { ActivityFeedTabs } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
import { EntityTabs } from 'enums/entity.enum'; import { EntityTabs } from 'enums/entity.enum';
import { UserProfileTab } from 'enums/user.enum';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -62,7 +61,7 @@ const NotificationBox = ({
); );
const notificationDropDownList = useMemo(() => { const notificationDropDownList = useMemo(() => {
return notifications.slice(0, 5).map((feed, idx) => { return notifications.slice(0, 5).map((feed) => {
const mainFeed = { const mainFeed = {
message: feed.message, message: feed.message,
postTs: feed.threadTs, postTs: feed.threadTs,
@ -79,7 +78,7 @@ const NotificationBox = ({
entityFQN={entityFQN as string} entityFQN={entityFQN as string}
entityType={entityType as string} entityType={entityType as string}
feedType={feed.type || ThreadType.Conversation} feedType={feed.type || ThreadType.Conversation}
key={`${mainFeed.from} ${idx}`} key={`${mainFeed.from} ${mainFeed.id}`}
task={feed} task={feed}
timestamp={mainFeed.postTs} timestamp={mainFeed.postTs}
/> />
@ -117,11 +116,13 @@ const NotificationBox = ({
getNotificationData(threadType, feedFilter); getNotificationData(threadType, feedFilter);
setViewAllPath( setViewAllPath(
`${getUserPath(currentUser?.name as string)}/${(threadType === getUserPath(
ThreadType.Conversation currentUser?.name as string,
? UserProfileTab.ACTIVITY EntityTabs.ACTIVITY_FEED,
: threadType key === NotificationTabsKey.TASK
).toLowerCase()}?feedFilter=${feedFilter}` ? ActivityFeedTabs.TASKS
: ActivityFeedTabs.MENTIONS
)
); );
if (hasTaskNotification || hasMentionNotification) { if (hasTaskNotification || hasMentionNotification) {
@ -132,7 +133,7 @@ const NotificationBox = ({
}, NOTIFICATION_READ_TIMER); }, NOTIFICATION_READ_TIMER);
} }
}, },
[currentUser, hasTaskNotification, hasMentionNotification] [onTabChange, currentUser, hasTaskNotification, hasMentionNotification]
); );
useEffect(() => { useEffect(() => {
@ -187,7 +188,7 @@ const NotificationBox = ({
size="small" size="small"
/> />
), ),
[notifications] [notifications, notificationDropDownList, viewAllPath]
); );
return ( return (

View File

@ -43,6 +43,7 @@ import { useTranslation } from 'react-i18next';
import { Link, useHistory, useParams } from 'react-router-dom'; import { Link, useHistory, useParams } from 'react-router-dom';
import { postThread } from 'rest/feedsAPI'; import { postThread } from 'rest/feedsAPI';
import { restorePipeline } from 'rest/pipelineAPI'; import { restorePipeline } from 'rest/pipelineAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { ReactComponent as ExternalLinkIcon } from '../../assets/svg/external-links.svg'; import { ReactComponent as ExternalLinkIcon } from '../../assets/svg/external-links.svg';
import { import {
getPipelineDetailsPath, getPipelineDetailsPath,
@ -746,6 +747,7 @@ const PipelineDetails = ({
<Row gutter={[0, 12]}> <Row gutter={[0, 12]}>
<Col className="p-x-lg" span={24}> <Col className="p-x-lg" span={24}>
<DataAssetsHeader <DataAssetsHeader
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={pipelineDetails} dataAsset={pipelineDetails}
entityType={EntityType.PIPELINE} entityType={EntityType.PIPELINE}
permissions={pipelinePermissions} permissions={pipelinePermissions}

View File

@ -36,6 +36,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { restoreTopic } from 'rest/topicsAPI'; import { restoreTopic } from 'rest/topicsAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils'; import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { EntityField } from '../../constants/Feeds.constants'; import { EntityField } from '../../constants/Feeds.constants';
import { EntityTabs, EntityType } from '../../enums/entity.enum'; import { EntityTabs, EntityType } from '../../enums/entity.enum';
@ -450,6 +451,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
<Row gutter={[0, 12]}> <Row gutter={[0, 12]}>
<Col className="p-x-lg" span={24}> <Col className="p-x-lg" span={24}>
<DataAssetsHeader <DataAssetsHeader
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={topicDetails} dataAsset={topicDetails}
entityType={EntityType.TOPIC} entityType={EntityType.TOPIC}
permissions={topicPermissions} permissions={topicPermissions}

View File

@ -24,7 +24,7 @@ export interface DeleteWidgetModalProps {
entityId?: string; entityId?: string;
prepareType?: boolean; prepareType?: boolean;
isRecursiveDelete?: boolean; isRecursiveDelete?: boolean;
afterDeleteAction?: () => void; afterDeleteAction?: (isSoftDelete?: boolean) => void;
} }
export interface DeleteSectionProps { export interface DeleteSectionProps {

View File

@ -22,15 +22,13 @@ import React, {
useState, useState,
} from 'react'; } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { deleteEntity } from 'rest/miscAPI'; import { deleteEntity } from 'rest/miscAPI';
import { ENTITY_DELETE_STATE } from '../../../constants/entity.constants';
import { EntityType } from '../../../enums/entity.enum';
import { import {
getEntityDeleteMessage, getDeleteMessage,
Transi18next, prepareEntityType,
} from '../../../utils/CommonUtils'; } from 'utils/DeleteWidgetModalUtils';
import { getTitleCase } from '../../../utils/EntityUtils'; import { ENTITY_DELETE_STATE } from '../../../constants/entity.constants';
import { Transi18next } from '../../../utils/CommonUtils';
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
import { DeleteType, DeleteWidgetModalProps } from './DeleteWidget.interface'; import { DeleteType, DeleteWidgetModalProps } from './DeleteWidget.interface';
@ -49,7 +47,6 @@ const DeleteWidgetModal = ({
afterDeleteAction, afterDeleteAction,
}: DeleteWidgetModalProps) => { }: DeleteWidgetModalProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory();
const [entityDeleteState, setEntityDeleteState] = const [entityDeleteState, setEntityDeleteState] =
useState<typeof ENTITY_DELETE_STATE>(ENTITY_DELETE_STATE); useState<typeof ENTITY_DELETE_STATE>(ENTITY_DELETE_STATE);
const [name, setName] = useState<string>(''); const [name, setName] = useState<string>('');
@ -58,123 +55,95 @@ const DeleteWidgetModal = ({
); );
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const prepareDeleteMessage = (softDelete = false) => { const DELETE_OPTION = useMemo(
const softDeleteText = t('message.soft-delete-message-for-entity', { () => [
entity: entityName, {
}); title: `${t('label.delete')} ${entityType}${entityName}`,
const hardDeleteText = getEntityDeleteMessage(getTitleCase(entityType), ''); description: `${getDeleteMessage(
entityName,
entityType,
true
)} ${softDeleteMessagePostFix}`,
type: DeleteType.SOFT_DELETE,
isAllowed: allowSoftDelete,
},
{
title: `${t('label.permanently-delete')} ${entityType}${entityName}`,
description: `${
deleteMessage || getDeleteMessage(entityName, entityType)
} ${hardDeleteMessagePostFix}`,
type: DeleteType.HARD_DELETE,
isAllowed: true,
},
],
[
entityType,
entityName,
softDeleteMessagePostFix,
allowSoftDelete,
deleteMessage,
hardDeleteMessagePostFix,
]
);
return softDelete ? softDeleteText : hardDeleteText; const handleOnChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
};
const DELETE_OPTION = [
{
title: `${t('label.delete')} ${entityType}${entityName}`,
description: `${prepareDeleteMessage(true)} ${softDeleteMessagePostFix}`,
type: DeleteType.SOFT_DELETE,
isAllowd: allowSoftDelete,
},
{
title: `${t('label.permanently-delete')} ${entityType}${entityName}`,
description: `${
deleteMessage || prepareDeleteMessage()
} ${hardDeleteMessagePostFix}`,
type: DeleteType.HARD_DELETE,
isAllowd: true,
},
];
const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
setName(e.target.value); setName(e.target.value);
}; }, []);
const handleOnEntityDelete = (softDelete = true) => { const handleOnEntityDelete = useCallback((softDelete = true) => {
setEntityDeleteState((prev) => ({ ...prev, state: true, softDelete })); setEntityDeleteState((prev) => ({ ...prev, state: true, softDelete }));
}; }, []);
const handleOnEntityDeleteCancel = () => { const handleOnEntityDeleteCancel = useCallback(() => {
setEntityDeleteState(ENTITY_DELETE_STATE); setEntityDeleteState(ENTITY_DELETE_STATE);
setName(''); setName('');
setValue(DeleteType.SOFT_DELETE); setValue(DeleteType.SOFT_DELETE);
onCancel(); onCancel();
}; }, [onCancel]);
const prepareEntityType = () => { const handleOnEntityDeleteConfirm = useCallback(async () => {
const services = [ try {
EntityType.DASHBOARD_SERVICE, setIsLoading(false);
EntityType.DATABASE_SERVICE, setEntityDeleteState((prev) => ({ ...prev, loading: 'waiting' }));
EntityType.MESSAGING_SERVICE, const response = await deleteEntity(
EntityType.PIPELINE_SERVICE, prepareType ? prepareEntityType(entityType) : entityType,
EntityType.METADATA_SERVICE, entityId ?? '',
EntityType.STORAGE_SERVICE, Boolean(isRecursiveDelete),
EntityType.MLMODEL_SERVICE, !entityDeleteState.softDelete
]; );
const dataQuality = [EntityType.TEST_SUITE, EntityType.TEST_CASE]; if (response.status === 200) {
showSuccessToast(
if (services.includes((entityType || '') as EntityType)) { t('server.entity-deleted-successfully', {
return `services/${entityType}s`; entity: startCase(entityType),
} else if (entityType === EntityType.GLOSSARY) {
return `glossaries`;
} else if (entityType === EntityType.POLICY) {
return 'policies';
} else if (entityType === EntityType.KPI) {
return entityType;
} else if (entityType === EntityType.DASHBOARD_DATA_MODEL) {
return `dashboard/datamodels`;
} else if (dataQuality.includes(entityType as EntityType)) {
return `dataQuality/${entityType}s`;
} else if (entityType === EntityType.SUBSCRIPTION) {
return `events/${entityType}s`;
} else {
return `${entityType}s`;
}
};
const handleOnEntityDeleteConfirm = () => {
setIsLoading(false);
setEntityDeleteState((prev) => ({ ...prev, loading: 'waiting' }));
deleteEntity(
prepareType ? prepareEntityType() : entityType,
entityId ?? '',
Boolean(isRecursiveDelete),
!entityDeleteState.softDelete
)
.then((res) => {
if (res.status === 200) {
setTimeout(() => {
handleOnEntityDeleteCancel();
showSuccessToast(
t('server.entity-deleted-successfully', {
entity: startCase(entityType),
})
);
if (afterDeleteAction) {
afterDeleteAction();
} else {
setTimeout(() => {
history.push('/');
}, 500);
}
}, 1000);
} else {
showErrorToast(t('server.unexpected-response'));
}
})
.catch((error: AxiosError) => {
showErrorToast(
error,
t('server.delete-entity-error', {
entity: entityName,
}) })
); );
}) if (afterDeleteAction) {
.finally(() => { afterDeleteAction(entityDeleteState.softDelete);
handleOnEntityDeleteCancel(); }
setIsLoading(false); } else {
}); showErrorToast(t('server.unexpected-response'));
}; }
} catch (error) {
showErrorToast(
error as AxiosError,
t('server.delete-entity-error', {
entity: entityName,
})
);
} finally {
handleOnEntityDeleteCancel();
setIsLoading(false);
}
}, [
entityType,
entityId,
isRecursiveDelete,
entityDeleteState,
afterDeleteAction,
entityName,
handleOnEntityDeleteCancel,
]);
const isNameMatching = useCallback(() => { const isNameMatching = useCallback(() => {
return ( return (
@ -183,11 +152,14 @@ const DeleteWidgetModal = ({
); );
}, [name]); }, [name]);
const onChange = (e: RadioChangeEvent) => { const onChange = useCallback(
const value = e.target.value; (e: RadioChangeEvent) => {
setValue(value); const value = e.target.value;
handleOnEntityDelete(value === DeleteType.SOFT_DELETE); setValue(value);
}; handleOnEntityDelete(value === DeleteType.SOFT_DELETE);
},
[handleOnEntityDelete]
);
useEffect(() => { useEffect(() => {
setValue(allowSoftDelete ? DeleteType.SOFT_DELETE : DeleteType.HARD_DELETE); setValue(allowSoftDelete ? DeleteType.SOFT_DELETE : DeleteType.HARD_DELETE);
@ -217,7 +189,12 @@ const DeleteWidgetModal = ({
</Button> </Button>
</Space> </Space>
); );
}, [entityDeleteState, isNameMatching]); }, [
entityDeleteState,
handleOnEntityDeleteCancel,
handleOnEntityDeleteConfirm,
isNameMatching,
]);
return ( return (
<Modal <Modal
@ -233,7 +210,7 @@ const DeleteWidgetModal = ({
<Radio.Group value={value} onChange={onChange}> <Radio.Group value={value} onChange={onChange}>
{DELETE_OPTION.map( {DELETE_OPTION.map(
(option) => (option) =>
option.isAllowd && ( option.isAllowed && (
<Radio <Radio
data-testid={option.type} data-testid={option.type}
key={option.type} key={option.type}

View File

@ -33,7 +33,7 @@ import './ManageButton.less';
interface Props { interface Props {
allowSoftDelete?: boolean; allowSoftDelete?: boolean;
afterDeleteAction?: () => void; afterDeleteAction?: (isSoftDelete?: boolean) => void;
buttonClassName?: string; buttonClassName?: string;
entityName: string; entityName: string;
entityId?: string; entityId?: string;

View File

@ -167,29 +167,6 @@ const AlertsPage = () => {
[] []
); );
if (loading) {
return <Loader />;
}
if (isEmpty(alerts)) {
return (
<ErrorPlaceHolder
permission
doc={ALERTS_DOCS}
heading={t('label.alert')}
type={ERROR_PLACEHOLDER_TYPE.CREATE}
onClick={() =>
history.push(
getSettingPath(
GlobalSettingsMenuCategory.NOTIFICATIONS,
GlobalSettingOptions.ADD_ALERTS
)
)
}
/>
);
}
return ( return (
<> <>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
@ -212,6 +189,29 @@ const AlertsPage = () => {
bordered bordered
columns={columns} columns={columns}
dataSource={alerts} dataSource={alerts}
loading={{
spinning: loading,
indicator: <Loader size="small" />,
}}
locale={{
emptyText: !loading && (
<ErrorPlaceHolder
permission
className="p-y-md"
doc={ALERTS_DOCS}
heading={t('label.alert')}
type={ERROR_PLACEHOLDER_TYPE.CREATE}
onClick={() =>
history.push(
getSettingPath(
GlobalSettingsMenuCategory.NOTIFICATIONS,
GlobalSettingOptions.ADD_ALERTS
)
)
}
/>
),
}}
pagination={false} pagination={false}
rowKey="id" rowKey="id"
size="middle" size="middle"

View File

@ -60,6 +60,7 @@ import {
removeContainerFollower, removeContainerFollower,
restoreContainer, restoreContainer,
} from 'rest/storageAPI'; } from 'rest/storageAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { import {
addToRecentViewed, addToRecentViewed,
getCurrentUserId, getCurrentUserId,
@ -690,6 +691,7 @@ const ContainerPage = () => {
<Row gutter={[0, 12]}> <Row gutter={[0, 12]}>
<Col className="p-x-lg" span={24}> <Col className="p-x-lg" span={24}>
<DataAssetsHeader <DataAssetsHeader
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={containerData} dataAsset={containerData}
entityType={EntityType.CONTAINER} entityType={EntityType.CONTAINER}
permissions={containerPermissions} permissions={containerPermissions}

View File

@ -22,7 +22,7 @@ import { EmptyGraphPlaceholder } from 'components/DataInsightDetail/EmptyGraphPl
import Loader from 'components/Loader/Loader'; import Loader from 'components/Loader/Loader';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum'; import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { getListKPIs } from 'rest/KpiAPI'; import { getListKPIs } from 'rest/KpiAPI';
@ -184,6 +184,10 @@ const KPIList = ({ viewKPIPermission }: { viewKPIPermission: boolean }) => {
fetchKpiList(); fetchKpiList();
}, []); }, []);
const handleAfterDeleteAction = useCallback(() => {
fetchKpiList();
}, [fetchKpiList]);
const noDataPlaceHolder = useMemo( const noDataPlaceHolder = useMemo(
() => () =>
viewKPIPermission ? ( viewKPIPermission ? (
@ -225,7 +229,7 @@ const KPIList = ({ viewKPIPermission }: { viewKPIPermission: boolean }) => {
{selectedKpi && ( {selectedKpi && (
<DeleteWidgetModal <DeleteWidgetModal
afterDeleteAction={fetchKpiList} afterDeleteAction={handleAfterDeleteAction}
allowSoftDelete={false} allowSoftDelete={false}
deleteMessage={`Are you sure you want to delete ${getEntityName( deleteMessage={`Are you sure you want to delete ${getEntityName(
selectedKpi selectedKpi

View File

@ -60,6 +60,7 @@ import {
restoreDatabase, restoreDatabase,
} from 'rest/databaseAPI'; } from 'rest/databaseAPI';
import { getFeedCount, postThread } from 'rest/feedsAPI'; import { getFeedCount, postThread } from 'rest/feedsAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { default as appState } from '../../AppState'; import { default as appState } from '../../AppState';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { import {
@ -748,8 +749,8 @@ const DatabaseDetails: FunctionComponent = () => {
<Row gutter={[0, 12]}> <Row gutter={[0, 12]}>
<Col className="p-x-lg" span={24}> <Col className="p-x-lg" span={24}>
<DataAssetsHeader <DataAssetsHeader
allowSoftDelete
isRecursiveDelete isRecursiveDelete
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={database} dataAsset={database}
entityType={EntityType.DATABASE} entityType={EntityType.DATABASE}
permissions={databasePermission} permissions={databasePermission}

View File

@ -55,6 +55,7 @@ import {
} from 'rest/databaseAPI'; } from 'rest/databaseAPI';
import { getFeedCount, postThread } from 'rest/feedsAPI'; import { getFeedCount, postThread } from 'rest/feedsAPI';
import { getTableList, TableListParams } from 'rest/tableAPI'; import { getTableList, TableListParams } from 'rest/tableAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { default as appState } from '../../AppState'; import { default as appState } from '../../AppState';
import { getDatabaseSchemaDetailsPath } from '../../constants/constants'; import { getDatabaseSchemaDetailsPath } from '../../constants/constants';
import { EntityTabs, EntityType } from '../../enums/entity.enum'; import { EntityTabs, EntityType } from '../../enums/entity.enum';
@ -557,8 +558,8 @@ const DatabaseSchemaPage: FunctionComponent = () => {
/> />
) : ( ) : (
<DataAssetsHeader <DataAssetsHeader
allowSoftDelete
isRecursiveDelete isRecursiveDelete
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={databaseSchema} dataAsset={databaseSchema}
entityType={EntityType.DATABASE_SCHEMA} entityType={EntityType.DATABASE_SCHEMA}
permissions={databaseSchemaPermission} permissions={databaseSchemaPermission}

View File

@ -18,7 +18,7 @@ import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichText
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider'; import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface'; import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
import { isEmpty, isUndefined, uniqueId } from 'lodash'; import { isEmpty, isUndefined, uniqueId } from 'lodash';
import React, { FC, useMemo, useState } from 'react'; import React, { FC, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { getEntityName } from 'utils/EntityUtils'; import { getEntityName } from 'utils/EntityUtils';
@ -181,6 +181,10 @@ const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => {
]; ];
}, []); }, []);
const handleAfterDeleteAction = useCallback(() => {
fetchPolicies();
}, [fetchPolicies]);
return ( return (
<> <>
<Table <Table
@ -195,7 +199,7 @@ const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => {
/> />
{selectedPolicy && deletePolicyPermission && ( {selectedPolicy && deletePolicyPermission && (
<DeleteWidgetModal <DeleteWidgetModal
afterDeleteAction={fetchPolicies} afterDeleteAction={handleAfterDeleteAction}
allowSoftDelete={false} allowSoftDelete={false}
deleteMessage={t('message.are-you-sure-delete-entity', { deleteMessage={t('message.are-you-sure-delete-entity', {
entity: getEntityName(selectedPolicy), entity: getEntityName(selectedPolicy),

View File

@ -18,7 +18,7 @@ import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichText
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider'; import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface'; import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
import { isEmpty, isUndefined, uniqueId } from 'lodash'; import { isEmpty, isUndefined, uniqueId } from 'lodash';
import React, { FC, useMemo, useState } from 'react'; import React, { FC, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { getEntityName } from 'utils/EntityUtils'; import { getEntityName } from 'utils/EntityUtils';
@ -178,6 +178,10 @@ const RolesList: FC<RolesListProps> = ({ roles, fetchRoles }) => {
]; ];
}, []); }, []);
const handleAfterDeleteAction = useCallback(() => {
fetchRoles();
}, [fetchRoles]);
return ( return (
<> <>
<Table <Table
@ -192,7 +196,7 @@ const RolesList: FC<RolesListProps> = ({ roles, fetchRoles }) => {
/> />
{selectedRole && ( {selectedRole && (
<DeleteWidgetModal <DeleteWidgetModal
afterDeleteAction={fetchRoles} afterDeleteAction={handleAfterDeleteAction}
allowSoftDelete={false} allowSoftDelete={false}
deleteMessage={t('message.are-you-sure-delete-entity', { deleteMessage={t('message.are-you-sure-delete-entity', {
entity: getEntityName(selectedRole), entity: getEntityName(selectedRole),

View File

@ -72,6 +72,7 @@ import {
removeFollower, removeFollower,
restoreTable, restoreTable,
} from 'rest/tableAPI'; } from 'rest/tableAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { import {
addToRecentViewed, addToRecentViewed,
getCurrentUserId, getCurrentUserId,
@ -887,6 +888,7 @@ const TableDetailsPageV1 = () => {
{/* Entity Heading */} {/* Entity Heading */}
<Col className="p-x-lg" data-testid="entity-page-header" span={24}> <Col className="p-x-lg" data-testid="entity-page-header" span={24}>
<DataAssetsHeader <DataAssetsHeader
afterDeleteAction={handleDataAssetAfterDeleteAction}
dataAsset={tableDetails} dataAsset={tableDetails}
entityType={EntityType.TABLE} entityType={EntityType.TABLE}
permissions={tablePermissions} permissions={tablePermissions}

View File

@ -16,6 +16,7 @@ import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { EntityHistory } from 'generated/type/entityHistory'; import { EntityHistory } from 'generated/type/entityHistory';
import { EntityReference } from 'generated/type/entityReference'; import { EntityReference } from 'generated/type/entityReference';
import { Include } from 'generated/type/include'; import { Include } from 'generated/type/include';
import { RestoreRequestType } from 'Models';
import { getURLWithQueryFields } from 'utils/APIUtils'; import { getURLWithQueryFields } from 'utils/APIUtils';
import APIClient from './index'; import APIClient from './index';
@ -117,3 +118,12 @@ export const getDataModelVersion = async (id: string, version: string) => {
return response.data; return response.data;
}; };
export const restoreDataModel = async (id: string) => {
const response = await APIClient.put<
RestoreRequestType,
AxiosResponse<DashboardDataModel>
>('/dashboard/datamodels/restore', { id });
return response.data;
};

View File

@ -22,6 +22,7 @@ import { getPipelineByFqn, patchPipelineDetails } from 'rest/pipelineAPI';
import { getContainerByName, patchContainerDetails } from 'rest/storageAPI'; import { getContainerByName, patchContainerDetails } from 'rest/storageAPI';
import { getTableDetailsByFQN, patchTableDetails } from 'rest/tableAPI'; import { getTableDetailsByFQN, patchTableDetails } from 'rest/tableAPI';
import { getTopicByFqn, patchTopicDetails } from 'rest/topicsAPI'; import { getTopicByFqn, patchTopicDetails } from 'rest/topicsAPI';
import { history } from 'utils/HistoryUtils';
export const getAPIfromSource = ( export const getAPIfromSource = (
source: AssetsUnion source: AssetsUnion
@ -66,3 +67,13 @@ export const getEntityAPIfromSource = (
return getContainerByName; return getContainerByName;
} }
}; };
export const handleDataAssetAfterDeleteAction = (isSoftDelete?: boolean) => {
if (isSoftDelete) {
setTimeout(() => {
history.go(0);
}, 1000);
} else {
history.push('/');
}
};

View File

@ -76,6 +76,7 @@ import { TagLabel } from '../generated/type/tagLabel';
import { EntityFieldThreadCount } from '../interface/feed.interface'; import { EntityFieldThreadCount } from '../interface/feed.interface';
import { getEntityFeedLink, getTitleCase } from './EntityUtils'; import { getEntityFeedLink, getTitleCase } from './EntityUtils';
import Fqn from './Fqn'; import Fqn from './Fqn';
import { history } from './HistoryUtils';
import { serviceTypeLogo } from './ServiceUtils'; import { serviceTypeLogo } from './ServiceUtils';
import { TASK_ENTITIES } from './TasksUtils'; import { TASK_ENTITIES } from './TasksUtils';
import { showErrorToast } from './ToastUtils'; import { showErrorToast } from './ToastUtils';
@ -701,7 +702,9 @@ export const getLoadingStatus = (
); );
}; };
export const refreshPage = () => window.location.reload(); export const refreshPage = () => {
history.go(0);
};
// return array of id as strings // return array of id as strings
export const getEntityIdArray = (entities: EntityReference[]): string[] => export const getEntityIdArray = (entities: EntityReference[]): string[] =>
entities.map((item) => item.id); entities.map((item) => item.id);

View File

@ -46,6 +46,7 @@ import {
getBreadcrumbForEntitiesWithServiceOnly, getBreadcrumbForEntitiesWithServiceOnly,
getBreadcrumbForTable, getBreadcrumbForTable,
getEntityBreadcrumbs, getEntityBreadcrumbs,
getEntityName,
} from './EntityUtils'; } from './EntityUtils';
import { bytesToSize } from './StringsUtils'; import { bytesToSize } from './StringsUtils';
import { getUsagePercentile } from './TableUtils'; import { getUsagePercentile } from './TableUtils';
@ -92,8 +93,8 @@ export const getDataAssetsHeaderInfo = (
{dashboardDetails.sourceUrl && ( {dashboardDetails.sourceUrl && (
<ExtraInfoLink <ExtraInfoLink
href={dashboardDetails.sourceUrl} href={dashboardDetails.sourceUrl}
label={entityName} label=""
value={dashboardDetails.sourceUrl} value={getEntityName(dashboardDetails)}
/> />
)} )}
{dashboardDetails.dashboardType && ( {dashboardDetails.dashboardType && (
@ -126,7 +127,7 @@ export const getDataAssetsHeaderInfo = (
<ExtraInfoLink <ExtraInfoLink
href={pipelineDetails.sourceUrl} href={pipelineDetails.sourceUrl}
label="" label=""
value={pipelineDetails.sourceUrl} value={getEntityName(pipelineDetails)}
/> />
)} )}
</> </>

View File

@ -0,0 +1,62 @@
/*
* Copyright 2023 Collate.
* Licensed 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 { EntityType } from 'enums/entity.enum';
import { t } from 'i18next';
import { getEntityDeleteMessage } from './CommonUtils';
import { getTitleCase } from './EntityUtils';
export const prepareEntityType = (entityType: string) => {
const services = [
EntityType.DASHBOARD_SERVICE,
EntityType.DATABASE_SERVICE,
EntityType.MESSAGING_SERVICE,
EntityType.PIPELINE_SERVICE,
EntityType.METADATA_SERVICE,
EntityType.STORAGE_SERVICE,
EntityType.MLMODEL_SERVICE,
];
const dataQuality = [EntityType.TEST_SUITE, EntityType.TEST_CASE];
if (services.includes((entityType || '') as EntityType)) {
return `services/${entityType}s`;
} else if (entityType === EntityType.GLOSSARY) {
return `glossaries`;
} else if (entityType === EntityType.POLICY) {
return 'policies';
} else if (entityType === EntityType.KPI) {
return entityType;
} else if (entityType === EntityType.DASHBOARD_DATA_MODEL) {
return `dashboard/datamodels`;
} else if (dataQuality.includes(entityType as EntityType)) {
return `dataQuality/${entityType}s`;
} else if (entityType === EntityType.SUBSCRIPTION) {
return `events/${entityType}s`;
} else {
return `${entityType}s`;
}
};
export const getDeleteMessage = (
entityName: string,
entityType: string,
softDelete = false
) => {
const softDeleteText = t('message.soft-delete-message-for-entity', {
entity: entityName,
});
const hardDeleteText = getEntityDeleteMessage(getTitleCase(entityType), '');
return softDelete ? softDeleteText : hardDeleteText;
};

View File

@ -0,0 +1,16 @@
/*
* Copyright 2023 Collate.
* Licensed 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 { createBrowserHistory } from 'history';
export const history = createBrowserHistory();