mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-18 14:39:44 +00:00
feat(ui): breadcrumb style updates (#10974)
* feat(ui): breadcrumb style updates entity title styling * fix localisation * update explore card & summary panel breadcrumbs * fix breadcrumbs for container, glossary & tags * update breadcrumb styling at all the places * fix service type missing from version components * fix unit tests * fix breadcrumb styling fix icons related changes fix link related issue * fixed assets related feedbacks * add external link for assets * fix cypress * fix breadcrumbs on the glossary * fix glossary breadcrumb issue * update databaseSchema page layout * fix code smells * fix code smells * fix unit tests failing * update tag icon and fix lineage cypress * fix restore entity * fix restore entity spec * fix test id issue
This commit is contained in:
parent
91b04ee9a3
commit
c89bcb51b4
@ -323,7 +323,7 @@ export const deleteCreatedService = (
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
cy.get(`[data-testid="inactive-link"]`)
|
||||
cy.get(`[data-testid="entity-header-name"]`)
|
||||
.should('exist')
|
||||
.should('be.visible')
|
||||
.invoke('text')
|
||||
|
@ -79,11 +79,12 @@ describe('Restore entity functionality should work properly', () => {
|
||||
cy.get('[data-testid="show-deleted"]').should('exist').click();
|
||||
verifyResponseStatusCode('@showDeletedTables', 200);
|
||||
|
||||
cy.get('[data-testid="sample_data-raw_product_catalog"]')
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.contains('raw_product_catalog')
|
||||
.should('exist')
|
||||
.click();
|
||||
|
||||
cy.get('[data-testid="inactive-link"]')
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.should('be.visible')
|
||||
.contains(ENTITY_TABLE.displayName);
|
||||
|
||||
@ -96,18 +97,20 @@ describe('Restore entity functionality should work properly', () => {
|
||||
cy.get('[data-testid="show-deleted"]').should('exist').click();
|
||||
verifyResponseStatusCode('@showDeletedTables', 200);
|
||||
|
||||
cy.get('[data-testid="sample_data-raw_product_catalog"]')
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.contains('raw_product_catalog')
|
||||
.should('exist')
|
||||
.click();
|
||||
|
||||
cy.get('[data-testid="inactive-link"]')
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.should('be.visible')
|
||||
.contains(ENTITY_TABLE.displayName)
|
||||
.click();
|
||||
|
||||
cy.get('[data-testid="deleted-badge"]').should('exist');
|
||||
|
||||
cy.get('[data-testid="breadcrumb-link"]')
|
||||
cy.get('[data-testid="breadcrumb"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.contains(ENTITY_TABLE.schemaName)
|
||||
.click();
|
||||
@ -142,11 +145,12 @@ describe('Restore entity functionality should work properly', () => {
|
||||
cy.get('[data-testid="show-deleted"]').should('exist').click();
|
||||
verifyResponseStatusCode('@showDeletedTables', 200);
|
||||
|
||||
cy.get('[data-testid="sample_data-raw_product_catalog"]')
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.contains('raw_product_catalog')
|
||||
.should('exist')
|
||||
.click();
|
||||
|
||||
cy.get('[data-testid="inactive-link"]')
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.should('be.visible')
|
||||
.contains(ENTITY_TABLE.displayName);
|
||||
|
||||
|
@ -582,7 +582,9 @@ describe('Data Quality and Profiler should work properly', () => {
|
||||
const { term, entity, serviceName, testCaseName } =
|
||||
DATA_QUALITY_SAMPLE_DATA_TABLE;
|
||||
visitEntityDetailsPage(term, serviceName, entity);
|
||||
cy.get('[data-testid="inactive-link"]').should('be.visible').contains(term);
|
||||
cy.get('[data-testid="entity-header-name"]')
|
||||
.should('be.visible')
|
||||
.contains(term);
|
||||
cy.get('[data-testid="Profiler & Data Quality"]')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
@ -633,7 +635,9 @@ describe('Data Quality and Profiler should work properly', () => {
|
||||
);
|
||||
visitEntityDetailsPage(term, serviceName, entity);
|
||||
verifyResponseStatusCode('@waitForPageLoad', 200);
|
||||
cy.get('[data-testid="inactive-link"]').should('be.visible').contains(term);
|
||||
cy.get('[data-testid="entity-header-name"]')
|
||||
.should('be.visible')
|
||||
.contains(term);
|
||||
cy.get('[data-testid="Profiler & Data Quality"]')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
@ -704,7 +704,7 @@ describe('Glossary page should work properly', () => {
|
||||
false
|
||||
);
|
||||
|
||||
cy.get(`[data-testid="${entity.serviceName}-${entity.term}"]`)
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.contains(entity.term)
|
||||
.should('be.visible');
|
||||
});
|
||||
@ -722,7 +722,8 @@ describe('Glossary page should work properly', () => {
|
||||
verifyResponseStatusCode('@assetTab', 200);
|
||||
|
||||
interceptURL('GET', '/api/v1/feed*', 'entityDetails');
|
||||
cy.get(`[data-testid="${entity.serviceName}-${entity.term}"]`)
|
||||
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.contains(entity.term)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
@ -88,7 +88,7 @@ describe('MyData page should work', () => {
|
||||
entity.entityObj.serviceName,
|
||||
entity.entityObj.entity
|
||||
);
|
||||
cy.get('[data-testid="inactive-link"]')
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.invoke('text')
|
||||
.then((newText) => {
|
||||
expect(newText).equal(text);
|
||||
@ -98,7 +98,7 @@ describe('MyData page should work', () => {
|
||||
.contains(text)
|
||||
.should('be.visible')
|
||||
.click();
|
||||
cy.get('[data-testid="inactive-link"]')
|
||||
cy.get('[data-testid="entity-header-display-name"]')
|
||||
.invoke('text')
|
||||
.then((newText) => {
|
||||
expect(newText).equal(text);
|
||||
|
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><g fill="#37352F" stroke="#37352F" stroke-width=".1" clip-path="url(#a)"><path d="M13.461 8.077a.461.461 0 0 0-.461.461v4.154a1.385 1.385 0 0 1-1.385 1.385H3.308a1.385 1.385 0 0 1-1.385-1.385V4.385A1.385 1.385 0 0 1 3.308 3h4.154a.462.462 0 1 0 0-.923H3.308A2.308 2.308 0 0 0 1 4.385v8.307A2.308 2.308 0 0 0 3.308 15h8.307a2.308 2.308 0 0 0 2.308-2.308V8.538a.461.461 0 0 0-.462-.461Z"/><path d="M10.385 2.154h2.648l-6.52 6.513a.577.577 0 0 0 .188.946.577.577 0 0 0 .632-.126l6.513-6.514v2.642a.577.577 0 0 0 1.154 0V1.577a.578.578 0 0 0-.355-.534.577.577 0 0 0-.222-.043h-4.038a.577.577 0 1 0 0 1.154Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none"><g fill="#37352F" stroke="#37352F" stroke-width=".1" clip-path="url(#a)"><path d="M13.461 8.077a.461.461 0 0 0-.461.461v4.154a1.385 1.385 0 0 1-1.385 1.385H3.308a1.385 1.385 0 0 1-1.385-1.385V4.385A1.385 1.385 0 0 1 3.308 3h4.154a.462.462 0 1 0 0-.923H3.308A2.308 2.308 0 0 0 1 4.385v8.307A2.308 2.308 0 0 0 3.308 15h8.307a2.308 2.308 0 0 0 2.308-2.308V8.538a.461.461 0 0 0-.462-.461Z"/><path d="M10.385 2.154h2.648l-6.52 6.513a.577.577 0 0 0 .188.946.577.577 0 0 0 .632-.126l6.513-6.514v2.642a.577.577 0 0 0 1.154 0V1.577a.578.578 0 0 0-.355-.534.577.577 0 0 0-.222-.043h-4.038a.577.577 0 1 0 0 1.154Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
Before Width: | Height: | Size: 768 B After Width: | Height: | Size: 765 B |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none"><path fill="#7147E8" d="M6.856 12c.376 0 .73-.147.995-.413l3.739-3.744a1.409 1.409 0 0 0 0-1.987L6.55.807A2.731 2.731 0 0 0 4.604 0H1.406C.631 0 0 .63 0 1.406v3.188c0 .735.286 1.425.806 1.945l5.056 5.05c.266.265.619.411.994.411ZM4.604.937a1.8 1.8 0 0 1 1.282.532l5.04 5.05a.47.47 0 0 1 0 .662l-3.739 3.744a.466.466 0 0 1-.33.137h-.001a.466.466 0 0 1-.331-.136l-5.056-5.05a1.8 1.8 0 0 1-.531-1.282V1.406a.47.47 0 0 1 .468-.468h3.198Zm-1.205 3.82c.775 0 1.406-.63 1.406-1.405 0-.776-.63-1.407-1.406-1.407a1.408 1.408 0 0 0 0 2.813Zm0-1.874a.47.47 0 1 1-.001.938.47.47 0 0 1 0-.938Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" fill="none"><path fill="#7147E8" d="M6.856 12c.376 0 .73-.147.995-.413l3.739-3.744a1.409 1.409 0 0 0 0-1.987L6.55.807A2.731 2.731 0 0 0 4.604 0H1.406C.631 0 0 .63 0 1.406v3.188c0 .735.286 1.425.806 1.945l5.056 5.05c.266.265.619.411.994.411ZM4.604.937a1.8 1.8 0 0 1 1.282.532l5.04 5.05a.47.47 0 0 1 0 .662l-3.739 3.744a.466.466 0 0 1-.33.137h-.001a.466.466 0 0 1-.331-.136l-5.056-5.05a1.8 1.8 0 0 1-.531-1.282V1.406a.47.47 0 0 1 .468-.468h3.198Zm-1.205 3.82c.775 0 1.406-.63 1.406-1.405 0-.776-.63-1.407-1.406-1.407a1.408 1.408 0 0 0 0 2.813Zm0-1.874a.47.47 0 1 1-.001.938.47.47 0 0 1 0-.938Z"/></svg>
|
Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 660 B |
@ -56,14 +56,6 @@ jest.mock('./ActivityThreadList', () => {
|
||||
return jest.fn().mockReturnValue(<p>ActivityThreadList</p>);
|
||||
});
|
||||
|
||||
const mockObserve = jest.fn();
|
||||
const mockunObserve = jest.fn();
|
||||
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockunObserve,
|
||||
}));
|
||||
|
||||
describe('Test ActivityThreadPanel Component', () => {
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@ -100,7 +92,5 @@ describe('Test ActivityThreadPanel Component', () => {
|
||||
const obServerElement = await screen.findByTestId('observer-element');
|
||||
|
||||
expect(obServerElement).toBeInTheDocument();
|
||||
|
||||
expect(mockObserve).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -51,14 +51,6 @@ jest.mock('./ActivityThreadList', () => {
|
||||
return jest.fn().mockReturnValue(<p>ActivityThreadList</p>);
|
||||
});
|
||||
|
||||
const mockObserve = jest.fn();
|
||||
const mockunObserve = jest.fn();
|
||||
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockunObserve,
|
||||
}));
|
||||
|
||||
describe('Test ActivityThreadPanelBodyBody Component', () => {
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@ -89,7 +81,5 @@ describe('Test ActivityThreadPanelBodyBody Component', () => {
|
||||
const obServerElement = await findByTestId(container, 'observer-element');
|
||||
|
||||
expect(obServerElement).toBeInTheDocument();
|
||||
|
||||
expect(mockObserve).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -55,6 +55,7 @@ export const AssetSelectionModal = ({
|
||||
SearchIndex.TABLE
|
||||
);
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
|
||||
const fetchEntities = useCallback(
|
||||
async ({ searchText = '', page = 1, index = activeFilter }) => {
|
||||
@ -68,7 +69,7 @@ export const AssetSelectionModal = ({
|
||||
queryFilter: getQueryFilterToExcludeTerm(glossaryFQN),
|
||||
});
|
||||
const hits = res.hits.hits as SearchedDataProps['data'];
|
||||
|
||||
setTotalCount(res.hits.total.value ?? 0);
|
||||
setItems(page === 1 ? hits : (prevItems) => [...prevItems, ...hits]);
|
||||
setPageNumber(page);
|
||||
} catch (error) {
|
||||
@ -174,7 +175,10 @@ export const AssetSelectionModal = ({
|
||||
|
||||
const onScroll: UIEventHandler<HTMLElement> = useCallback(
|
||||
(e) => {
|
||||
if (e.currentTarget.scrollHeight - e.currentTarget.scrollTop === 500) {
|
||||
if (
|
||||
e.currentTarget.scrollHeight - e.currentTarget.scrollTop === 500 &&
|
||||
items.length < totalCount
|
||||
) {
|
||||
!isLoading &&
|
||||
fetchEntities({
|
||||
searchText: search,
|
||||
@ -183,7 +187,7 @@ export const AssetSelectionModal = ({
|
||||
});
|
||||
}
|
||||
},
|
||||
[activeFilter, search]
|
||||
[activeFilter, search, totalCount, items]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -202,7 +206,8 @@ export const AssetSelectionModal = ({
|
||||
open={open}
|
||||
style={{ top: 40 }}
|
||||
title={t('label.add-entity', { entity: t('label.asset-plural') })}
|
||||
width={750}>
|
||||
width={750}
|
||||
onCancel={onCancel}>
|
||||
<Space className="w-full h-full" direction="vertical" size={16}>
|
||||
<Searchbar
|
||||
removeMargin
|
||||
@ -234,7 +239,7 @@ export const AssetSelectionModal = ({
|
||||
height={500}
|
||||
itemKey="id"
|
||||
onScroll={onScroll}>
|
||||
{({ _index: index, _source: item }) => (
|
||||
{({ _source: item }) => (
|
||||
<TableDataCardV2
|
||||
openEntityInNewPage
|
||||
showCheckboxes
|
||||
@ -243,8 +248,7 @@ export const AssetSelectionModal = ({
|
||||
handleSummaryPanelDisplay={handleCardClick}
|
||||
id={`tabledatacard-${item.id}`}
|
||||
key={item.id}
|
||||
searchIndex={index}
|
||||
source={item}
|
||||
source={{ ...item, tags: [] }}
|
||||
/>
|
||||
)}
|
||||
</VirtualList>
|
||||
|
@ -16,3 +16,20 @@
|
||||
.asset-selection-model-card.table-data-card-container:hover {
|
||||
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.13);
|
||||
}
|
||||
|
||||
.asset-selection-model-card {
|
||||
// CheckBox
|
||||
.ant-checkbox-inner {
|
||||
border-width: 2px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-color: #7147e8;
|
||||
}
|
||||
|
||||
.ant-checkbox-inner::after {
|
||||
top: 48%;
|
||||
left: 18.5%;
|
||||
width: 6.25px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
@ -399,6 +399,7 @@ const ContainerVersion: React.FC<ContainerVersionProp> = ({
|
||||
entityName={currentVersionData.name ?? ''}
|
||||
extraInfo={getExtraInfo()}
|
||||
followersList={[]}
|
||||
serviceType={currentVersionData.serviceType ?? ''}
|
||||
tags={getTags()}
|
||||
tier={undefined}
|
||||
titleLinks={breadCrumbList}
|
||||
|
@ -662,6 +662,7 @@ const DashboardDetails = ({
|
||||
? onRemoveTier
|
||||
: undefined
|
||||
}
|
||||
serviceType={dashboardDetails.serviceType ?? ''}
|
||||
tags={dashboardTags}
|
||||
tagsHandler={onTagUpdate}
|
||||
tier={tier}
|
||||
|
@ -112,14 +112,6 @@ const DashboardDetailsProps = {
|
||||
onExtensionUpdate: jest.fn(),
|
||||
};
|
||||
|
||||
const mockObserve = jest.fn();
|
||||
const mockunObserve = jest.fn();
|
||||
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockunObserve,
|
||||
}));
|
||||
|
||||
jest.mock('../common/description/Description', () => {
|
||||
return jest.fn().mockReturnValue(<p>Description Component</p>);
|
||||
});
|
||||
@ -285,8 +277,6 @@ describe('Test DashboardDetails component', () => {
|
||||
const obServerElement = await findByTestId(container, 'observer-element');
|
||||
|
||||
expect(obServerElement).toBeInTheDocument();
|
||||
|
||||
expect(mockObserve).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Check if tags and glossary-terms are present', async () => {
|
||||
|
@ -283,6 +283,7 @@ const DashboardVersion: FC<DashboardVersionProp> = ({
|
||||
}
|
||||
extraInfo={getExtraInfo()}
|
||||
followersList={[]}
|
||||
serviceType={currentVersionData.serviceType ?? ''}
|
||||
tags={getTags()}
|
||||
tier={{} as TagLabel}
|
||||
titleLinks={slashedDashboardName}
|
||||
|
@ -268,6 +268,7 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
|
||||
}
|
||||
extraInfo={getExtraInfo()}
|
||||
followersList={[]}
|
||||
serviceType={currentVersionData.serviceType ?? ''}
|
||||
tags={getTags()}
|
||||
tier={{} as TagLabel}
|
||||
titleLinks={slashedDataModelName}
|
||||
|
@ -44,7 +44,6 @@ import {
|
||||
} from 'utils/CommonUtils';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
|
||||
import { serviceTypeLogo } from 'utils/ServiceUtils';
|
||||
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
|
||||
import { DataModelDetailsProps } from './DataModelDetails.interface';
|
||||
import ModelTab from './ModelTab/ModelTab.component';
|
||||
@ -119,6 +118,7 @@ const DataModelDetails = ({
|
||||
followers,
|
||||
dataModelType,
|
||||
isUserFollowing,
|
||||
serviceType,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
deleted: dataModelData?.deleted,
|
||||
@ -134,11 +134,11 @@ const DataModelDetails = ({
|
||||
),
|
||||
followers: dataModelData?.followers ?? [],
|
||||
dataModelType: dataModelData?.dataModelType,
|
||||
serviceType: dataModelData?.serviceType,
|
||||
};
|
||||
}, [dataModelData]);
|
||||
|
||||
const breadcrumbTitles = useMemo(() => {
|
||||
const serviceType = dataModelData?.serviceType;
|
||||
const service = dataModelData?.service;
|
||||
const serviceName = service?.name;
|
||||
|
||||
@ -151,12 +151,6 @@ const DataModelDetails = ({
|
||||
ServiceCategory.DASHBOARD_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
{
|
||||
name: entityName,
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
];
|
||||
}, [dataModelData, dashboardDataModelFQN, entityName]);
|
||||
@ -247,6 +241,7 @@ const DataModelDetails = ({
|
||||
isFollowing={isUserFollowing}
|
||||
isTagEditable={hasEditTagsPermission}
|
||||
removeTier={hasEditTierPermission ? handleRemoveTier : undefined}
|
||||
serviceType={serviceType ?? ''}
|
||||
tags={tags}
|
||||
tagsHandler={handleUpdateTags}
|
||||
tier={tier}
|
||||
|
@ -14,6 +14,9 @@
|
||||
import { Card, Col, Row, Skeleton, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
// css
|
||||
import QueryCount from 'components/common/QueryCount/QueryCount.component';
|
||||
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
||||
import { isEqual, isNil, isUndefined } from 'lodash';
|
||||
import { EntityTags, ExtraInfo } from 'Models';
|
||||
import React, {
|
||||
@ -82,8 +85,6 @@ import TableProfilerGraph from '../TableProfiler/TableProfilerGraph.component';
|
||||
import TableProfilerV1 from '../TableProfiler/TableProfilerV1';
|
||||
import TableQueries from '../TableQueries/TableQueries';
|
||||
import { DatasetDetailsProps } from './DatasetDetails.interface';
|
||||
// css
|
||||
import QueryCount from 'components/common/QueryCount/QueryCount.component';
|
||||
import './datasetDetails.style.less';
|
||||
|
||||
const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
@ -606,7 +607,10 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
<Loader />
|
||||
) : (
|
||||
<PageContainerV1>
|
||||
<div className="entity-details-container">
|
||||
<PageLayoutV1
|
||||
pageTitle={t('label.entity-detail-plural', {
|
||||
entity: getEntityName(tableDetails),
|
||||
})}>
|
||||
<EntityPageInfo
|
||||
canDelete={tablePermissions.Delete}
|
||||
currentOwner={tableDetails.owner}
|
||||
@ -634,6 +638,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
? onRemoveTier
|
||||
: undefined
|
||||
}
|
||||
serviceType={tableDetails.serviceType ?? ''}
|
||||
tags={tableTags}
|
||||
tagsHandler={onTagUpdate}
|
||||
tier={tier}
|
||||
@ -654,7 +659,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
|
||||
<div className="tw-mt-4 tw-flex tw-flex-col tw-flex-grow">
|
||||
<div className="m-t-md h-inherit">
|
||||
<TabsPane
|
||||
activeTab={activeTab}
|
||||
className="tw-flex-initial"
|
||||
@ -838,7 +843,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</PageLayoutV1>
|
||||
</PageContainerV1>
|
||||
);
|
||||
};
|
||||
|
@ -224,14 +224,6 @@ jest.mock('../../utils/CommonUtils', () => ({
|
||||
getOwnerValue: jest.fn().mockReturnValue('Owner'),
|
||||
}));
|
||||
|
||||
const mockObserve = jest.fn();
|
||||
const mockunObserve = jest.fn();
|
||||
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockunObserve,
|
||||
}));
|
||||
|
||||
jest.mock('../PermissionProvider/PermissionProvider', () => ({
|
||||
usePermissionProvider: jest.fn().mockImplementation(() => ({
|
||||
permissions: {},
|
||||
@ -285,6 +277,10 @@ jest.mock('../../utils/PermissionsUtils', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('components/containers/PageLayoutV1', () => {
|
||||
return jest.fn().mockImplementation(({ children }) => children);
|
||||
});
|
||||
|
||||
describe('Test MyDataDetailsPage page', () => {
|
||||
it('Checks if the page has all the proper components rendered', async () => {
|
||||
const { container } = render(<DatasetDetails {...DatasetDetailsProps} />, {
|
||||
@ -417,7 +413,5 @@ describe('Test MyDataDetailsPage page', () => {
|
||||
const obServerElement = await findByTestId(container, 'observer-element');
|
||||
|
||||
expect(obServerElement).toBeInTheDocument();
|
||||
|
||||
expect(mockObserve).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -390,10 +390,11 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
||||
entityName={currentVersionData.name ?? ''}
|
||||
extraInfo={getExtraInfo()}
|
||||
followersList={[]}
|
||||
serviceType={currentVersionData.serviceType ?? ''}
|
||||
tags={getTags()}
|
||||
tier={tier}
|
||||
titleLinks={slashedTableName}
|
||||
version={version}
|
||||
version={Number(version)}
|
||||
versionHandler={backHandler}
|
||||
/>
|
||||
<div className="tw-mt-1 tw-flex tw-flex-col tw-flex-grow ">
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 { Col, Row } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component';
|
||||
import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { getEntityLinkFromType, getEntityName } from 'utils/EntityUtils';
|
||||
import EntityHeaderTitle from '../EntityHeaderTitle/EntityHeaderTitle.component';
|
||||
|
||||
interface Props {
|
||||
extra?: ReactNode;
|
||||
breadcrumb: TitleBreadcrumbProps['titleLinks'];
|
||||
entityData: {
|
||||
displayName?: string;
|
||||
name: string;
|
||||
fullyQualifiedName?: string;
|
||||
deleted?: boolean;
|
||||
};
|
||||
entityType?: EntityType;
|
||||
icon: ReactNode;
|
||||
titleIsLink?: boolean;
|
||||
openEntityInNewPage?: boolean;
|
||||
gutter?: 'default' | 'large';
|
||||
}
|
||||
|
||||
export const EntityHeader = ({
|
||||
breadcrumb,
|
||||
entityData,
|
||||
extra,
|
||||
icon,
|
||||
titleIsLink = false,
|
||||
entityType,
|
||||
openEntityInNewPage,
|
||||
gutter = 'default',
|
||||
}: Props) => {
|
||||
return (
|
||||
<Row className="w-full" gutter={0} justify="space-between">
|
||||
<Col>
|
||||
<div
|
||||
className={classNames(
|
||||
'tw-text-link tw-text-base glossary-breadcrumb',
|
||||
gutter === 'large' ? 'm-b-sm' : 'm-b-xss'
|
||||
)}
|
||||
data-testid="category-name">
|
||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||
</div>
|
||||
|
||||
<EntityHeaderTitle
|
||||
deleted={entityData.deleted}
|
||||
displayName={getEntityName(entityData)}
|
||||
icon={icon}
|
||||
link={
|
||||
titleIsLink && entityData.fullyQualifiedName && entityType
|
||||
? getEntityLinkFromType(entityData.fullyQualifiedName, entityType)
|
||||
: undefined
|
||||
}
|
||||
name={entityData.name}
|
||||
openEntityInNewPage={openEntityInNewPage}
|
||||
/>
|
||||
</Col>
|
||||
<Col>{extra}</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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 { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { Col, Row, Typography } from 'antd';
|
||||
import { ReactComponent as IconExternalLink } from 'assets/svg/external-link-grey.svg';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { EntityHeaderTitleProps } from './EntityHeaderTitle.interface';
|
||||
|
||||
const EntityHeaderTitle = ({
|
||||
icon,
|
||||
name,
|
||||
displayName,
|
||||
link,
|
||||
openEntityInNewPage,
|
||||
deleted = false,
|
||||
}: EntityHeaderTitleProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Row align="middle" gutter={8} wrap={false}>
|
||||
<Col>{icon}</Col>
|
||||
<Col>
|
||||
<div>
|
||||
<Typography.Text
|
||||
className="m-b-0 d-block tw-text-xs tw-text-grey-muted"
|
||||
data-testid="entity-header-name">
|
||||
{name}
|
||||
</Typography.Text>
|
||||
{link ? (
|
||||
<Link
|
||||
className="m-b-0 d-block entity-header-display-name text-lg font-bold"
|
||||
data-testid="entity-header-display-name"
|
||||
target={openEntityInNewPage ? '_blank' : '_self'}
|
||||
to={link}>
|
||||
<Typography.Text ellipsis={{ tooltip: true }}>
|
||||
{displayName ?? name}
|
||||
{openEntityInNewPage && (
|
||||
<IconExternalLink
|
||||
className="anticon vertical-baseline m-l-xss"
|
||||
height={14}
|
||||
width={14}
|
||||
/>
|
||||
)}
|
||||
</Typography.Text>
|
||||
</Link>
|
||||
) : (
|
||||
<Typography.Text
|
||||
className="m-b-0 d-block entity-header-display-name text-lg font-bold"
|
||||
data-testid="entity-header-display-name"
|
||||
ellipsis={{ tooltip: true }}>
|
||||
{displayName ?? name}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
{deleted && (
|
||||
<Col className="self-end text-xs">
|
||||
<div className="deleted-badge-button" data-testid="deleted-badge">
|
||||
<ExclamationCircleFilled className="m-r-xss font-medium text-xs" />
|
||||
{t('label.deleted')}
|
||||
</div>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntityHeaderTitle;
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 Collate.
|
||||
* 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
|
||||
@ -10,25 +10,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@import url('../../../styles/variables.less');
|
||||
@link-btn-color: #37352f;
|
||||
|
||||
.table-data-card-title-container {
|
||||
.ant-btn-link {
|
||||
color: @link-btn-color;
|
||||
font-weight: 600;
|
||||
padding: 0px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.ant-btn-link > span {
|
||||
color: @link-btn-color;
|
||||
}
|
||||
}
|
||||
|
||||
.button-hover {
|
||||
.ant-btn-link > span {
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
export interface EntityHeaderTitleProps {
|
||||
icon: React.ReactNode;
|
||||
name: string;
|
||||
displayName: string;
|
||||
link?: string;
|
||||
openEntityInNewPage?: boolean;
|
||||
deleted?: boolean;
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import EntityHeaderTitle from './EntityHeaderTitle.component';
|
||||
|
||||
describe('EntityHeaderTitle', () => {
|
||||
it('should render icon', () => {
|
||||
render(
|
||||
<EntityHeaderTitle
|
||||
displayName="Test DisplayName"
|
||||
icon="test-icon"
|
||||
name="test-name"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('test-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render name', () => {
|
||||
render(
|
||||
<EntityHeaderTitle
|
||||
displayName="Test DisplayName"
|
||||
icon="test-icon"
|
||||
name="test-name"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('test-name')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render displayName', () => {
|
||||
render(
|
||||
<EntityHeaderTitle
|
||||
displayName="Test DisplayName"
|
||||
icon="test-icon"
|
||||
name="test-name"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Test DisplayName')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render link if link is provided', () => {
|
||||
render(
|
||||
<EntityHeaderTitle
|
||||
displayName="Test DisplayName"
|
||||
icon="test-icon"
|
||||
link="test-link"
|
||||
name="test-name"
|
||||
/>,
|
||||
{ wrapper: MemoryRouter }
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('entity-header-display-name')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/test-link'
|
||||
);
|
||||
});
|
||||
});
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* 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 { Col, Row, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
interface props {
|
||||
icon: React.ReactNode;
|
||||
name: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
const EntityHeaderTitle = ({ icon, name, displayName }: props) => {
|
||||
return (
|
||||
<Row align="middle" gutter={8} wrap={false}>
|
||||
<Col>{icon}</Col>
|
||||
<Col>
|
||||
<div>
|
||||
<Typography.Text
|
||||
className="m-b-0 d-block tw-text-xs tw-text-grey-muted"
|
||||
data-testid="entity-header-name">
|
||||
{name}
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
className="m-b-0 d-block entity-header-display-name text-lg font-bold"
|
||||
data-testid="entity-header-display-name"
|
||||
ellipsis={{ tooltip: true }}>
|
||||
{displayName}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntityHeaderTitle;
|
@ -12,19 +12,21 @@
|
||||
*/
|
||||
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { Col, Drawer, Row } from 'antd';
|
||||
import TableDataCardTitle from 'components/common/table-data-card-v2/TableDataCardTitle.component';
|
||||
import { Drawer } from 'antd';
|
||||
import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { Tag } from 'generated/entity/classification/tag';
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { Dashboard } from 'generated/entity/data/dashboard';
|
||||
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
|
||||
import { Table } from 'generated/entity/data/table';
|
||||
import { get } from 'lodash';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Dashboard } from '../../../generated/entity/data/dashboard';
|
||||
import { getEntityBreadcrumbs } from 'utils/EntityUtils';
|
||||
import { getServiceIcon } from 'utils/TableUtils';
|
||||
import { Mlmodel } from '../../../generated/entity/data/mlmodel';
|
||||
import { Pipeline } from '../../../generated/entity/data/pipeline';
|
||||
import { Table } from '../../../generated/entity/data/table';
|
||||
import { Topic } from '../../../generated/entity/data/topic';
|
||||
import ContainerSummary from './ContainerSummary/ContainerSummary.component';
|
||||
import DashboardSummary from './DashboardSummary/DashboardSummary.component';
|
||||
@ -42,70 +44,44 @@ export default function EntitySummaryPanel({
|
||||
handleClosePanel,
|
||||
}: EntitySummaryPanelProps) {
|
||||
const { tab } = useParams<{ tab: string }>();
|
||||
const [currentSearchIndex, setCurrentSearchIndex] = useState<EntityType>();
|
||||
|
||||
const summaryComponent = useMemo(() => {
|
||||
const type = get(entityDetails, 'details.entityType') ?? EntityType.TABLE;
|
||||
const entity = entityDetails.details;
|
||||
switch (type) {
|
||||
case EntityType.TABLE:
|
||||
setCurrentSearchIndex(EntityType.TABLE);
|
||||
|
||||
return <TableSummary entityDetails={entityDetails.details as Table} />;
|
||||
return <TableSummary entityDetails={entity as Table} />;
|
||||
|
||||
case EntityType.TOPIC:
|
||||
setCurrentSearchIndex(EntityType.TOPIC);
|
||||
|
||||
return <TopicSummary entityDetails={entityDetails.details as Topic} />;
|
||||
return <TopicSummary entityDetails={entity as Topic} />;
|
||||
|
||||
case EntityType.DASHBOARD:
|
||||
setCurrentSearchIndex(EntityType.DASHBOARD);
|
||||
|
||||
return (
|
||||
<DashboardSummary
|
||||
entityDetails={entityDetails.details as Dashboard}
|
||||
/>
|
||||
);
|
||||
return <DashboardSummary entityDetails={entity as Dashboard} />;
|
||||
|
||||
case EntityType.PIPELINE:
|
||||
setCurrentSearchIndex(EntityType.PIPELINE);
|
||||
|
||||
return (
|
||||
<PipelineSummary entityDetails={entityDetails.details as Pipeline} />
|
||||
);
|
||||
return <PipelineSummary entityDetails={entity as Pipeline} />;
|
||||
|
||||
case EntityType.MLMODEL:
|
||||
setCurrentSearchIndex(EntityType.MLMODEL);
|
||||
|
||||
return (
|
||||
<MlModelSummary entityDetails={entityDetails.details as Mlmodel} />
|
||||
);
|
||||
return <MlModelSummary entityDetails={entity as Mlmodel} />;
|
||||
|
||||
case EntityType.CONTAINER:
|
||||
setCurrentSearchIndex(EntityType.CONTAINER);
|
||||
return <ContainerSummary entityDetails={entity as Container} />;
|
||||
|
||||
return (
|
||||
<ContainerSummary
|
||||
entityDetails={entityDetails.details as Container}
|
||||
/>
|
||||
);
|
||||
case EntityType.GLOSSARY_TERM:
|
||||
setCurrentSearchIndex(EntityType.GLOSSARY);
|
||||
return <GlossaryTermSummary entityDetails={entity as GlossaryTerm} />;
|
||||
|
||||
return (
|
||||
<GlossaryTermSummary
|
||||
entityDetails={entityDetails.details as GlossaryTerm}
|
||||
/>
|
||||
);
|
||||
case EntityType.TAG:
|
||||
setCurrentSearchIndex(EntityType.TAG);
|
||||
|
||||
return <TagsSummary entityDetails={entityDetails.details as Tag} />;
|
||||
return <TagsSummary entityDetails={entity as Tag} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [tab, entityDetails]);
|
||||
|
||||
const icon = useMemo(() => {
|
||||
return getServiceIcon(entityDetails.details);
|
||||
}, [entityDetails]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
destroyOnClose
|
||||
@ -122,16 +98,16 @@ export default function EntitySummaryPanel({
|
||||
headerStyle={{ padding: 16 }}
|
||||
mask={false}
|
||||
title={
|
||||
<Row gutter={[0, 6]}>
|
||||
<Col span={24}>
|
||||
<TableDataCardTitle
|
||||
isPanel
|
||||
dataTestId="summary-panel-title"
|
||||
searchIndex={currentSearchIndex as EntityType}
|
||||
source={entityDetails.details}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<EntityHeader
|
||||
titleIsLink
|
||||
breadcrumb={getEntityBreadcrumbs(
|
||||
entityDetails.details,
|
||||
entityDetails.details.entityType as EntityType
|
||||
)}
|
||||
entityData={entityDetails.details}
|
||||
entityType={entityDetails.details.entityType as EntityType}
|
||||
icon={icon}
|
||||
/>
|
||||
}
|
||||
width="100%">
|
||||
{summaryComponent}
|
||||
|
@ -64,36 +64,30 @@ jest.mock('./MlModelSummary/MlModelSummary.component', () =>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'components/common/table-data-card-v2/TableDataCardTitle.component',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(() => (
|
||||
<div data-testid="table-data-card-title">TableDataCardTitle</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useParams: jest.fn().mockImplementation(() => ({ tab: 'table' })),
|
||||
}));
|
||||
|
||||
jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({
|
||||
EntityHeader: jest.fn().mockImplementation(() => <p>EntityHeader</p>),
|
||||
}));
|
||||
|
||||
describe('EntitySummaryPanel component tests', () => {
|
||||
it('TableSummary should render for table data', async () => {
|
||||
render(
|
||||
<EntitySummaryPanel
|
||||
entityDetails={{
|
||||
details: mockTableEntityDetails,
|
||||
details: { ...mockTableEntityDetails, entityType: EntityType.TABLE },
|
||||
}}
|
||||
handleClosePanel={mockHandleClosePanel}
|
||||
/>
|
||||
);
|
||||
|
||||
const tableDataCardTitle = screen.getByText('TableDataCardTitle');
|
||||
const entityHeader = screen.getByText('EntityHeader');
|
||||
const tableSummary = screen.getByTestId('TableSummary');
|
||||
const closeIcon = screen.getByTestId('summary-panel-close-icon');
|
||||
|
||||
expect(tableDataCardTitle).toBeInTheDocument();
|
||||
expect(entityHeader).toBeInTheDocument();
|
||||
expect(tableSummary).toBeInTheDocument();
|
||||
expect(closeIcon).toBeInTheDocument();
|
||||
|
||||
|
@ -45,7 +45,7 @@ function SummaryListItem({
|
||||
{entityDetails.title}
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Row className="text-xs font-300" gutter={[4, 4]}>
|
||||
<Row className="text-xs font-thin" gutter={[4, 4]}>
|
||||
<Col>
|
||||
{entityDetails.type && (
|
||||
<Space size={4}>
|
||||
|
@ -18,7 +18,6 @@ import { EntityUnion } from 'components/Explore/explore.interface';
|
||||
import { SourceType } from 'components/searched-data/SearchedData.interface';
|
||||
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
|
||||
import { SearchIndex } from 'enums/search.enum';
|
||||
import { get } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { searchData } from 'rest/miscAPI';
|
||||
@ -50,14 +49,11 @@ function TagsSummary({ entityDetails, isLoading }: TagsSummaryProps) {
|
||||
|
||||
const usageItems = useMemo(() => {
|
||||
return selectedData.map((entity, index) => {
|
||||
const searchIndex = get(entity, 'entityType');
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<TableDataCardV2
|
||||
id={`tabledatacardtest${index}`}
|
||||
searchIndex={searchIndex}
|
||||
source={entity as SourceType}
|
||||
/>
|
||||
</div>
|
||||
|
@ -127,6 +127,13 @@ export type EntityUnion =
|
||||
| Tag
|
||||
| DashboardDataModel;
|
||||
|
||||
export type EntityWithServices =
|
||||
| Topic
|
||||
| Dashboard
|
||||
| Pipeline
|
||||
| Mlmodel
|
||||
| Container;
|
||||
|
||||
export interface EntityDetailsObjectInterface {
|
||||
details: SearchedDataProps['data'][number]['_source'];
|
||||
}
|
||||
|
@ -13,16 +13,15 @@
|
||||
import { Col, Row } from 'antd';
|
||||
import { ReactComponent as IconFolder } from 'assets/svg/folder.svg';
|
||||
import { ReactComponent as IconFlatDoc } from 'assets/svg/ic-flat-doc.svg';
|
||||
import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component';
|
||||
import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface';
|
||||
import EntityHeaderTitle from 'components/EntityHeaderTitle/EntityHeaderTitle.component';
|
||||
import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component';
|
||||
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
||||
import { DE_ACTIVE_COLOR } from 'constants/constants';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { Glossary } from 'generated/entity/data/glossary';
|
||||
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import { getGlossaryPath } from 'utils/RouterUtils';
|
||||
import GlossaryHeaderButtons from '../GlossaryHeaderButtons/GlossaryHeaderButtons.component';
|
||||
|
||||
@ -33,7 +32,7 @@ export interface GlossaryHeaderProps {
|
||||
isGlossary: boolean;
|
||||
onUpdate: (data: GlossaryTerm | Glossary) => void;
|
||||
onDelete: (id: string) => void;
|
||||
onAssetsUpdate?: () => void;
|
||||
onAssetAdd?: () => void;
|
||||
}
|
||||
|
||||
const GlossaryHeader = ({
|
||||
@ -42,7 +41,7 @@ const GlossaryHeader = ({
|
||||
onUpdate,
|
||||
onDelete,
|
||||
isGlossary,
|
||||
onAssetsUpdate,
|
||||
onAssetAdd,
|
||||
}: GlossaryHeaderProps) => {
|
||||
const [breadcrumb, setBreadcrumb] = useState<
|
||||
TitleBreadcrumbProps['titleLinks']
|
||||
@ -88,50 +87,42 @@ const GlossaryHeader = ({
|
||||
<>
|
||||
<Row gutter={[0, 16]}>
|
||||
<Col span={24}>
|
||||
<Row justify="space-between">
|
||||
<Col span={12}>
|
||||
<div
|
||||
className="tw-text-link tw-text-base glossary-breadcrumb m-b-sm"
|
||||
data-testid="category-name">
|
||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||
</div>
|
||||
|
||||
<EntityHeaderTitle
|
||||
displayName={getEntityName(selectedData)}
|
||||
icon={
|
||||
isGlossary ? (
|
||||
<IconFolder
|
||||
color={DE_ACTIVE_COLOR}
|
||||
height={36}
|
||||
name="folder"
|
||||
width={32}
|
||||
/>
|
||||
) : (
|
||||
<IconFlatDoc
|
||||
color={DE_ACTIVE_COLOR}
|
||||
height={36}
|
||||
name="doc"
|
||||
width={32}
|
||||
/>
|
||||
)
|
||||
}
|
||||
name={selectedData.name}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<EntityHeader
|
||||
breadcrumb={breadcrumb}
|
||||
entityData={selectedData}
|
||||
entityType={EntityType.GLOSSARY_TERM}
|
||||
extra={
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<GlossaryHeaderButtons
|
||||
deleteStatus="success"
|
||||
isGlossary={isGlossary}
|
||||
permission={permissions}
|
||||
selectedData={selectedData}
|
||||
onAssetsUpdate={onAssetsUpdate}
|
||||
onAssetAdd={onAssetAdd}
|
||||
onEntityDelete={onDelete}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
gutter="large"
|
||||
icon={
|
||||
isGlossary ? (
|
||||
<IconFolder
|
||||
color={DE_ACTIVE_COLOR}
|
||||
height={36}
|
||||
name="folder"
|
||||
width={32}
|
||||
/>
|
||||
) : (
|
||||
<IconFlatDoc
|
||||
color={DE_ACTIVE_COLOR}
|
||||
height={36}
|
||||
name="doc"
|
||||
width={32}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
|
@ -16,7 +16,6 @@ import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||
import { ReactComponent as ExportIcon } from 'assets/svg/ic-export.svg';
|
||||
import { ReactComponent as ImportIcon } from 'assets/svg/ic-import.svg';
|
||||
import { ReactComponent as IconDropdown } from 'assets/svg/menu.svg';
|
||||
import { AssetSelectionModal } from 'components/Assets/AssetsSelectionModal/AssetSelectionModal';
|
||||
import EntityDeleteModal from 'components/Modals/EntityDeleteModal/EntityDeleteModal';
|
||||
import EntityNameModal from 'components/Modals/EntityNameModal/EntityNameModal.component';
|
||||
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
@ -48,7 +47,7 @@ interface GlossaryHeaderButtonsProps {
|
||||
selectedData: Glossary | GlossaryTerm;
|
||||
permission: OperationPermission;
|
||||
onEntityDelete: (id: string) => void;
|
||||
onAssetsUpdate?: () => void;
|
||||
onAssetAdd?: () => void;
|
||||
onUpdate: (data: GlossaryTerm | Glossary) => void;
|
||||
}
|
||||
|
||||
@ -58,7 +57,7 @@ const GlossaryHeaderButtons = ({
|
||||
selectedData,
|
||||
permission,
|
||||
onEntityDelete,
|
||||
onAssetsUpdate,
|
||||
onAssetAdd,
|
||||
onUpdate,
|
||||
}: GlossaryHeaderButtonsProps) => {
|
||||
const { t } = useTranslation();
|
||||
@ -75,7 +74,7 @@ const GlossaryHeaderButtons = ({
|
||||
const history = useHistory();
|
||||
const [showActions, setShowActions] = useState(false);
|
||||
const [isDelete, setIsDelete] = useState<boolean>(false);
|
||||
const [showAddAssets, setShowAddAssets] = useState(false);
|
||||
|
||||
const [isNameEditing, setIsNameEditing] = useState<boolean>(false);
|
||||
|
||||
const editDisplayNamePermission = useMemo(() => {
|
||||
@ -133,10 +132,6 @@ const GlossaryHeaderButtons = ({
|
||||
setIsDelete(false);
|
||||
};
|
||||
|
||||
const handleAddAssetsClick = () => {
|
||||
setShowAddAssets(true);
|
||||
};
|
||||
|
||||
const onNameSave = (obj: { name: string; displayName: string }) => {
|
||||
const { name, displayName } = obj;
|
||||
let updatedDetails = cloneDeep(selectedData);
|
||||
@ -161,7 +156,7 @@ const GlossaryHeaderButtons = ({
|
||||
{
|
||||
label: t('label.asset-plural'),
|
||||
key: '2',
|
||||
onClick: () => handleAddAssetsClick(),
|
||||
onClick: onAssetAdd,
|
||||
},
|
||||
];
|
||||
|
||||
@ -388,14 +383,7 @@ const GlossaryHeaderButtons = ({
|
||||
onOk={handleCancelGlossaryExport}
|
||||
/>
|
||||
)}
|
||||
{selectedData.fullyQualifiedName && !isGlossary && (
|
||||
<AssetSelectionModal
|
||||
glossaryFQN={selectedData.fullyQualifiedName}
|
||||
open={showAddAssets}
|
||||
onCancel={() => setShowAddAssets(false)}
|
||||
onSave={onAssetsUpdate}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EntityNameModal
|
||||
entity={selectedData as EntityReference}
|
||||
visible={isNameEditing}
|
||||
|
@ -19,12 +19,10 @@ import AssetsTabs, {
|
||||
import GlossaryOverviewTab from 'components/GlossaryTerms/tabs/GlossaryOverviewTab.component';
|
||||
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import { getGlossaryTermDetailsPath } from 'constants/constants';
|
||||
import { myDataSearchIndex } from 'constants/Mydata.constants';
|
||||
import { Glossary } from 'generated/entity/data/glossary';
|
||||
import { t } from 'i18next';
|
||||
import React, { RefObject, useEffect, useMemo, useState } from 'react';
|
||||
import React, { RefObject, useMemo } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { searchData } from 'rest/miscAPI';
|
||||
import { getGlossaryTermsVersionsPath } from 'utils/RouterUtils';
|
||||
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
|
||||
import { getCountBadge } from '../../utils/CommonUtils';
|
||||
@ -34,12 +32,13 @@ type Props = {
|
||||
childGlossaryTerms: GlossaryTerm[];
|
||||
permissions: OperationPermission;
|
||||
isGlossary: boolean;
|
||||
|
||||
onAddAsset: () => void;
|
||||
onUpdate: (data: GlossaryTerm | Glossary) => void;
|
||||
refreshGlossaryTerms: () => void;
|
||||
onAssetClick?: (asset?: EntityDetailsObjectInterface) => void;
|
||||
assetsRef: RefObject<AssetsTabRef>;
|
||||
isSummaryPanelOpen: boolean;
|
||||
assetCount?: number;
|
||||
};
|
||||
|
||||
const GlossaryTabs = ({
|
||||
@ -47,10 +46,12 @@ const GlossaryTabs = ({
|
||||
childGlossaryTerms,
|
||||
isGlossary,
|
||||
onUpdate,
|
||||
onAddAsset,
|
||||
permissions,
|
||||
refreshGlossaryTerms,
|
||||
onAssetClick,
|
||||
assetsRef,
|
||||
assetCount,
|
||||
isSummaryPanelOpen,
|
||||
}: Props) => {
|
||||
const {
|
||||
@ -59,7 +60,6 @@ const GlossaryTabs = ({
|
||||
version,
|
||||
} = useParams<{ glossaryName: string; tab: string; version: string }>();
|
||||
const history = useHistory();
|
||||
const [assetCount, setAssetCount] = useState<number>(0);
|
||||
|
||||
const activeTabHandler = (tab: string) => {
|
||||
history.push({
|
||||
@ -69,30 +69,6 @@ const GlossaryTabs = ({
|
||||
});
|
||||
};
|
||||
|
||||
const fetchGlossaryTermAssets = async () => {
|
||||
if (glossaryFqn) {
|
||||
try {
|
||||
const res = await searchData(
|
||||
'',
|
||||
1,
|
||||
0,
|
||||
`(tags.tagFQN:"${glossaryFqn}")`,
|
||||
'',
|
||||
'',
|
||||
myDataSearchIndex
|
||||
);
|
||||
|
||||
setAssetCount(res.data.hits.total.value ?? 0);
|
||||
} catch (error) {
|
||||
setAssetCount(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchGlossaryTermAssets();
|
||||
}, [glossaryFqn]);
|
||||
|
||||
const activeTab = useMemo(() => {
|
||||
return tab ?? 'overview';
|
||||
}, [tab]);
|
||||
@ -144,7 +120,7 @@ const GlossaryTabs = ({
|
||||
<div data-testid="assets">
|
||||
{t('label.asset-plural')}
|
||||
<span className="p-l-xs ">
|
||||
{getCountBadge(assetCount, '', activeTab === 'assets')}
|
||||
{getCountBadge(assetCount ?? 0, '', activeTab === 'assets')}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
@ -154,6 +130,7 @@ const GlossaryTabs = ({
|
||||
isSummaryPanelOpen={isSummaryPanelOpen}
|
||||
permissions={permissions}
|
||||
ref={assetsRef}
|
||||
onAddAsset={onAddAsset}
|
||||
onAssetClick={onAssetClick}
|
||||
/>
|
||||
),
|
||||
|
@ -12,10 +12,14 @@
|
||||
*/
|
||||
|
||||
import { Col, Row } from 'antd';
|
||||
import { AssetSelectionModal } from 'components/Assets/AssetsSelectionModal/AssetSelectionModal';
|
||||
import { EntityDetailsObjectInterface } from 'components/Explore/explore.interface';
|
||||
import GlossaryHeader from 'components/Glossary/GlossaryHeader/GlossaryHeader.component';
|
||||
import GlossaryTabs from 'components/GlossaryTabs/GlossaryTabs.component';
|
||||
import React, { useRef } from 'react';
|
||||
import { myDataSearchIndex } from 'constants/Mydata.constants';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { searchData } from 'rest/miscAPI';
|
||||
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
|
||||
import { OperationPermission } from '../PermissionProvider/PermissionProvider.interface';
|
||||
import { AssetsTabRef } from './tabs/AssetsTabs.component';
|
||||
@ -41,39 +45,80 @@ const GlossaryTermsV1 = ({
|
||||
onAssetClick,
|
||||
isSummaryPanelOpen,
|
||||
}: Props) => {
|
||||
const { glossaryName: glossaryFqn } = useParams<{ glossaryName: string }>();
|
||||
const assetTabRef = useRef<AssetsTabRef>(null);
|
||||
const [assetModalVisible, setAssetModelVisible] = useState(false);
|
||||
|
||||
const [assetCount, setAssetCount] = useState<number>(0);
|
||||
|
||||
const fetchGlossaryTermAssets = async () => {
|
||||
if (glossaryFqn) {
|
||||
try {
|
||||
const res = await searchData(
|
||||
'',
|
||||
1,
|
||||
0,
|
||||
`(tags.tagFQN:"${glossaryFqn}")`,
|
||||
'',
|
||||
'',
|
||||
myDataSearchIndex
|
||||
);
|
||||
|
||||
setAssetCount(res.data.hits.total.value ?? 0);
|
||||
} catch (error) {
|
||||
setAssetCount(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchGlossaryTermAssets();
|
||||
}, [glossaryFqn]);
|
||||
|
||||
const handleAssetSave = () => {
|
||||
fetchGlossaryTermAssets();
|
||||
assetTabRef.current?.refreshAssets();
|
||||
};
|
||||
|
||||
return (
|
||||
<Row data-testid="glossary-term" gutter={[0, 8]}>
|
||||
<Col span={24}>
|
||||
<GlossaryHeader
|
||||
isGlossary={false}
|
||||
permissions={permissions}
|
||||
selectedData={glossaryTerm}
|
||||
onAssetsUpdate={() => {
|
||||
if (glossaryTerm.fullyQualifiedName) {
|
||||
assetTabRef.current?.refreshAssets();
|
||||
}
|
||||
}}
|
||||
onDelete={handleGlossaryTermDelete}
|
||||
onUpdate={(data) => handleGlossaryTermUpdate(data as GlossaryTerm)}
|
||||
/>
|
||||
</Col>
|
||||
<>
|
||||
<Row data-testid="glossary-term" gutter={[0, 8]}>
|
||||
<Col span={24}>
|
||||
<GlossaryHeader
|
||||
isGlossary={false}
|
||||
permissions={permissions}
|
||||
selectedData={glossaryTerm}
|
||||
onAssetAdd={() => setAssetModelVisible(true)}
|
||||
onDelete={handleGlossaryTermDelete}
|
||||
onUpdate={(data) => handleGlossaryTermUpdate(data as GlossaryTerm)}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<GlossaryTabs
|
||||
assetsRef={assetTabRef}
|
||||
childGlossaryTerms={childGlossaryTerms}
|
||||
isGlossary={false}
|
||||
isSummaryPanelOpen={isSummaryPanelOpen}
|
||||
permissions={permissions}
|
||||
refreshGlossaryTerms={refreshGlossaryTerms}
|
||||
selectedData={glossaryTerm}
|
||||
onAssetClick={onAssetClick}
|
||||
onUpdate={(data) => handleGlossaryTermUpdate(data as GlossaryTerm)}
|
||||
<Col span={24}>
|
||||
<GlossaryTabs
|
||||
assetCount={assetCount}
|
||||
assetsRef={assetTabRef}
|
||||
childGlossaryTerms={childGlossaryTerms}
|
||||
isGlossary={false}
|
||||
isSummaryPanelOpen={isSummaryPanelOpen}
|
||||
permissions={permissions}
|
||||
refreshGlossaryTerms={refreshGlossaryTerms}
|
||||
selectedData={glossaryTerm}
|
||||
onAddAsset={() => setAssetModelVisible(true)}
|
||||
onAssetClick={onAssetClick}
|
||||
onUpdate={(data) => handleGlossaryTermUpdate(data as GlossaryTerm)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
{glossaryTerm.fullyQualifiedName && (
|
||||
<AssetSelectionModal
|
||||
glossaryFQN={glossaryTerm.fullyQualifiedName}
|
||||
open={assetModalVisible}
|
||||
onCancel={() => setAssetModelVisible(false)}
|
||||
onSave={handleAssetSave}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,7 @@ import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import { SearchIndex } from 'enums/search.enum';
|
||||
import { t } from 'i18next';
|
||||
import { startCase } from 'lodash';
|
||||
import { find, startCase } from 'lodash';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
@ -46,6 +46,7 @@ import { getCountBadge } from '../../../utils/CommonUtils';
|
||||
import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder';
|
||||
|
||||
interface Props {
|
||||
onAddAsset: () => void;
|
||||
permissions: OperationPermission;
|
||||
onAssetClick?: (asset?: EntityDetailsObjectInterface) => void;
|
||||
isSummaryPanelOpen: boolean;
|
||||
@ -57,7 +58,10 @@ export interface AssetsTabRef {
|
||||
}
|
||||
|
||||
const AssetsTabs = forwardRef(
|
||||
({ permissions, onAssetClick, isSummaryPanelOpen }: Props, ref) => {
|
||||
(
|
||||
{ permissions, onAssetClick, isSummaryPanelOpen, onAddAsset }: Props,
|
||||
ref
|
||||
) => {
|
||||
const [itemCount, setItemCount] = useState<Record<AssetsUnion, number>>({
|
||||
table: 0,
|
||||
pipeline: 0,
|
||||
@ -98,22 +102,32 @@ const AssetsTabs = forwardRef(
|
||||
mlmodelResponse,
|
||||
containerResponse,
|
||||
]) => {
|
||||
setItemCount({
|
||||
const counts = {
|
||||
[EntityType.TOPIC]: topicResponse.data.hits.total.value,
|
||||
[EntityType.TABLE]: tableResponse.data.hits.total.value,
|
||||
[EntityType.DASHBOARD]: dashboardResponse.data.hits.total.value,
|
||||
[EntityType.PIPELINE]: pipelineResponse.data.hits.total.value,
|
||||
[EntityType.MLMODEL]: mlmodelResponse.data.hits.total.value,
|
||||
[EntityType.CONTAINER]: containerResponse.data.hits.total.value,
|
||||
});
|
||||
};
|
||||
setItemCount(counts);
|
||||
|
||||
setActiveFilter(
|
||||
tableResponse.data.hits.total.value
|
||||
? SearchIndex.TABLE
|
||||
: topicResponse.data.hits.total.value
|
||||
? SearchIndex.TOPIC
|
||||
: SearchIndex.DASHBOARD
|
||||
);
|
||||
find(counts, (count, key) => {
|
||||
if (count > 0) {
|
||||
key;
|
||||
|
||||
const option = AssetsFilterOptions.find(
|
||||
(el) => el.label === key
|
||||
);
|
||||
if (option) {
|
||||
setActiveFilter(option.value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
@ -236,7 +250,7 @@ const AssetsTabs = forwardRef(
|
||||
})}
|
||||
{data.length ? (
|
||||
<>
|
||||
{data.map(({ _source, _index, _id = '' }, index) => (
|
||||
{data.map(({ _source, _id = '' }, index) => (
|
||||
<TableDataCardV2
|
||||
className={classNames(
|
||||
'm-b-sm cursor-pointer',
|
||||
@ -245,7 +259,6 @@ const AssetsTabs = forwardRef(
|
||||
handleSummaryPanelDisplay={setSelectedCard}
|
||||
id={_id}
|
||||
key={index}
|
||||
searchIndex={_index as SearchIndex}
|
||||
source={_source}
|
||||
/>
|
||||
))}
|
||||
@ -271,7 +284,8 @@ const AssetsTabs = forwardRef(
|
||||
<Button
|
||||
ghost
|
||||
data-testid="add-new-asset-button"
|
||||
type="primary">
|
||||
type="primary"
|
||||
onClick={onAddAsset}>
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.asset'),
|
||||
})}
|
||||
|
@ -56,7 +56,6 @@ import {
|
||||
} from '../../utils/CommonUtils';
|
||||
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
|
||||
@ -153,6 +152,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
const mlModelTags = useMemo(() => {
|
||||
return getTagsWithoutTier(mlModelDetail.tags || []);
|
||||
}, [mlModelDetail.tags]);
|
||||
|
||||
const slashedMlModelName: TitleBreadcrumbProps['titleLinks'] = [
|
||||
{
|
||||
name: mlModelDetail.service.name || '',
|
||||
@ -162,14 +162,6 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
ServiceCategory.ML_MODEL_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: mlModelDetail.serviceType
|
||||
? serviceTypeLogo(mlModelDetail.serviceType || '')
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
name: getEntityName(mlModelDetail as unknown as EntityReference),
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
];
|
||||
|
||||
@ -553,6 +545,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
? onTierRemove
|
||||
: undefined
|
||||
}
|
||||
serviceType={mlModelDetail.serviceType ?? ''}
|
||||
tags={mlModelTags}
|
||||
tagsHandler={onTagUpdate}
|
||||
tier={mlModelTier}
|
||||
|
@ -273,6 +273,7 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
|
||||
}
|
||||
extraInfo={getExtraInfo()}
|
||||
followersList={[]}
|
||||
serviceType={currentVersionData.serviceType ?? ''}
|
||||
tags={getTags()}
|
||||
tier={{} as TagLabel}
|
||||
titleLinks={slashedMlModelName}
|
||||
|
@ -160,14 +160,6 @@ const mockProp: MyDataProps = {
|
||||
updateThreadHandler: jest.fn(),
|
||||
};
|
||||
|
||||
const mockObserve = jest.fn();
|
||||
const mockunObserve = jest.fn();
|
||||
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockunObserve,
|
||||
}));
|
||||
|
||||
describe('Test MyData page', () => {
|
||||
it('Check if there is an element in the page', async () => {
|
||||
const { container } = render(<MyData {...mockProp} />, {
|
||||
@ -197,8 +189,6 @@ describe('Test MyData page', () => {
|
||||
const obServerElement = await findByTestId(container, 'observer-element');
|
||||
|
||||
expect(obServerElement).toBeInTheDocument();
|
||||
|
||||
expect(mockObserve).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Onboarding placeholder should be visible in case of no activity feed present overall and the feeds are not loading', async () => {
|
||||
|
@ -763,6 +763,7 @@ const PipelineDetails = ({
|
||||
? onTierRemove
|
||||
: undefined
|
||||
}
|
||||
serviceType={pipelineDetails.serviceType ?? ''}
|
||||
tags={tags}
|
||||
tagsHandler={onTagUpdate}
|
||||
tier={tier}
|
||||
|
@ -20,7 +20,6 @@ import {
|
||||
screen,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { act } from 'react-test-renderer';
|
||||
@ -141,14 +140,6 @@ const PipelineDetailsProps = {
|
||||
onExtensionUpdate: jest.fn(),
|
||||
};
|
||||
|
||||
const mockObserve = jest.fn();
|
||||
const mockunObserve = jest.fn();
|
||||
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockunObserve,
|
||||
}));
|
||||
|
||||
jest.mock('../common/description/Description', () => {
|
||||
return jest.fn().mockReturnValue(<p>Description Component</p>);
|
||||
});
|
||||
|
@ -278,6 +278,7 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
|
||||
}
|
||||
extraInfo={getExtraInfo()}
|
||||
followersList={[]}
|
||||
serviceType={currentVersionData.serviceType ?? ''}
|
||||
tags={getTags()}
|
||||
tier={{} as TagLabel}
|
||||
titleLinks={slashedPipelineName}
|
||||
|
@ -466,6 +466,7 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
|
||||
? handleTierRemove
|
||||
: undefined
|
||||
}
|
||||
serviceType={table.serviceType ?? ''}
|
||||
tags={getTagsWithoutTier(table.tags || [])}
|
||||
tagsHandler={handleTagUpdate}
|
||||
tier={tier}
|
||||
|
@ -892,12 +892,11 @@ const TeamDetailsV1 = ({
|
||||
|
||||
return (
|
||||
<div data-testid="table-container">
|
||||
{assets.data.map(({ _source, _index, _id = '' }, index) => (
|
||||
{assets.data.map(({ _source, _id = '' }, index) => (
|
||||
<TableDataCardV2
|
||||
className="m-b-sm cursor-pointer"
|
||||
id={_id}
|
||||
key={index}
|
||||
searchIndex={_index as SearchIndex}
|
||||
source={_source}
|
||||
/>
|
||||
))}
|
||||
|
@ -452,6 +452,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
? onTierRemove
|
||||
: undefined
|
||||
}
|
||||
serviceType={topicDetails.serviceType ?? ''}
|
||||
tags={topicTags}
|
||||
tagsHandler={onTagUpdate}
|
||||
tier={tier}
|
||||
|
@ -109,14 +109,6 @@ const TopicDetailsProps = {
|
||||
onExtensionUpdate: jest.fn(),
|
||||
};
|
||||
|
||||
const mockObserve = jest.fn();
|
||||
const mockunObserve = jest.fn();
|
||||
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockunObserve,
|
||||
}));
|
||||
|
||||
jest.mock('../EntityLineage/EntityLineage.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>EntityLineage.component</p>);
|
||||
});
|
||||
@ -285,7 +277,5 @@ describe('Test TopicDetails component', () => {
|
||||
const obServerElement = await findByTestId(container, 'observer-element');
|
||||
|
||||
expect(obServerElement).toBeInTheDocument();
|
||||
|
||||
expect(mockObserve).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -273,6 +273,7 @@ const TopicVersion: FC<TopicVersionProp> = ({
|
||||
entityName={currentVersionData.name ?? ''}
|
||||
extraInfo={getExtraInfo()}
|
||||
followersList={[]}
|
||||
serviceType={currentVersionData.serviceType ?? ''}
|
||||
tags={getTags()}
|
||||
tier={{} as TagLabel}
|
||||
titleLinks={slashedTopicName}
|
||||
|
@ -25,7 +25,6 @@ import { ReactComponent as IconTeamsGrey } from 'assets/svg/teams-grey.svg';
|
||||
import { AxiosError } from 'axios';
|
||||
import TableDataCardV2 from 'components/common/table-data-card-v2/TableDataCardV2';
|
||||
import TeamsSelectable from 'components/TeamsSelectable/TeamsSelectable';
|
||||
import { SearchIndex } from 'enums/search.enum';
|
||||
import { capitalize, isEmpty, isEqual, toLower } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, {
|
||||
@ -848,12 +847,11 @@ const Users = ({
|
||||
<div data-testid="table-container">
|
||||
{entityData.data.length ? (
|
||||
<>
|
||||
{entityData.data.map(({ _source, _index, _id = '' }, index) => (
|
||||
{entityData.data.map(({ _source, _id = '' }, index) => (
|
||||
<TableDataCardV2
|
||||
className="m-b-sm cursor-pointer"
|
||||
id={_id}
|
||||
key={index}
|
||||
searchIndex={_index as SearchIndex}
|
||||
source={_source}
|
||||
/>
|
||||
))}
|
||||
|
@ -114,9 +114,11 @@ const mockEntityInfoProp = {
|
||||
versionHandler,
|
||||
entityFieldThreads: [],
|
||||
onThreadLinkSelect,
|
||||
serviceType: '',
|
||||
};
|
||||
|
||||
jest.mock('../../../utils/EntityUtils', () => ({
|
||||
...jest.requireActual('../../../utils/EntityUtils'),
|
||||
getEntityFeedLink: jest.fn(),
|
||||
}));
|
||||
|
||||
|
@ -11,11 +11,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ExclamationCircleOutlined, StarFilled } from '@ant-design/icons';
|
||||
import { StarFilled } from '@ant-design/icons';
|
||||
import { Button, Popover, Space, Tooltip } from 'antd';
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component';
|
||||
import Tags from 'components/Tag/Tags/tags';
|
||||
import { t } from 'i18next';
|
||||
import { cloneDeep, isEmpty, isUndefined, toString } from 'lodash';
|
||||
@ -23,6 +24,7 @@ import { EntityTags, ExtraInfo, TagOption } from 'Models';
|
||||
import React, { Fragment, useCallback, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getActiveAnnouncement } from 'rest/feedsAPI';
|
||||
import { serviceTypeLogo } from 'utils/ServiceUtils';
|
||||
import { ReactComponent as IconCommentPlus } from '../../../assets/svg/add-chat.svg';
|
||||
import { ReactComponent as IconComments } from '../../../assets/svg/comment.svg';
|
||||
import { ReactComponent as IconEdit } from '../../../assets/svg/ic-edit.svg';
|
||||
@ -53,7 +55,6 @@ import TagsContainer from '../../Tag/TagsContainer/tags-container';
|
||||
import TagsViewer from '../../Tag/TagsViewer/tags-viewer';
|
||||
import EntitySummaryDetails from '../EntitySummaryDetails/EntitySummaryDetails';
|
||||
import ProfilePicture from '../ProfilePicture/ProfilePicture';
|
||||
import TitleBreadcrumb from '../title-breadcrumb/title-breadcrumb.component';
|
||||
import { TitleBreadcrumbProps } from '../title-breadcrumb/title-breadcrumb.interface';
|
||||
import AnnouncementCard from './AnnouncementCard/AnnouncementCard';
|
||||
import AnnouncementDrawer from './AnnouncementDrawer/AnnouncementDrawer';
|
||||
@ -90,6 +91,7 @@ interface Props {
|
||||
onRestoreEntity?: () => void;
|
||||
isRecursiveDelete?: boolean;
|
||||
extraDropdownContent?: ItemType[];
|
||||
serviceType: string;
|
||||
}
|
||||
|
||||
const EntityPageInfo = ({
|
||||
@ -122,6 +124,7 @@ const EntityPageInfo = ({
|
||||
onRestoreEntity,
|
||||
isRecursiveDelete = false,
|
||||
extraDropdownContent,
|
||||
serviceType,
|
||||
}: Props) => {
|
||||
const history = useHistory();
|
||||
const tagThread = entityFieldThreads?.[0];
|
||||
@ -132,9 +135,6 @@ const EntityPageInfo = ({
|
||||
const [isViewMore, setIsViewMore] = useState<boolean>(false);
|
||||
const [tagList, setTagList] = useState<Array<TagOption>>([]);
|
||||
const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
|
||||
const [versionFollowButtonWidth, setVersionFollowButtonWidth] = useState(
|
||||
document.getElementById('version-and-follow-section')?.offsetWidth
|
||||
);
|
||||
|
||||
const [isAnnouncementDrawerOpen, setIsAnnouncementDrawer] =
|
||||
useState<boolean>(false);
|
||||
@ -376,101 +376,98 @@ const EntityPageInfo = ({
|
||||
}, [followersList]);
|
||||
|
||||
useAfterMount(() => {
|
||||
setVersionFollowButtonWidth(
|
||||
document.getElementById('version-and-follow-section')?.offsetWidth
|
||||
);
|
||||
if (ANNOUNCEMENT_ENTITIES.includes(entityType as EntityType)) {
|
||||
fetchActiveAnnouncement();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div data-testid="entity-page-info">
|
||||
<Space
|
||||
align="start"
|
||||
className="tw-justify-between"
|
||||
style={{ width: '100%' }}>
|
||||
<Space align="center">
|
||||
<TitleBreadcrumb
|
||||
titleLinks={titleLinks}
|
||||
widthDeductions={
|
||||
(versionFollowButtonWidth ? versionFollowButtonWidth : 0) + 30
|
||||
}
|
||||
/>
|
||||
{deleted && (
|
||||
<div className="deleted-badge-button" data-testid="deleted-badge">
|
||||
<ExclamationCircleOutlined className="tw-mr-1" />
|
||||
{t('label.deleted')}
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
<Space align="center" id="version-and-follow-section">
|
||||
{!isUndefined(version) ? (
|
||||
<>
|
||||
{!isUndefined(isVersionSelected) ? (
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
title={
|
||||
<p className="tw-text-xs">
|
||||
{t('message.viewing-older-version')}
|
||||
</p>
|
||||
}
|
||||
trigger="hover">
|
||||
{getVersionButton(toString(version))}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>{getVersionButton(toString(version))}</>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
{!isUndefined(isFollowing) ? (
|
||||
<Button
|
||||
className={classNames(
|
||||
'tw-border tw-border-primary tw-rounded',
|
||||
isFollowing ? 'tw-text-white' : 'tw-text-primary'
|
||||
)}
|
||||
data-testid="follow-button"
|
||||
size="small"
|
||||
type={isFollowing ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
!deleted && followHandler?.();
|
||||
}}>
|
||||
<Space>
|
||||
<StarFilled className="tw-text-xs" />
|
||||
{isFollowing ? t('label.un-follow') : t('label.follow')}
|
||||
<Popover content={getFollowers()} trigger="click">
|
||||
<span
|
||||
className={classNames(
|
||||
'tw-border-l tw-font-medium tw-cursor-pointer hover:tw-underline tw-pl-1',
|
||||
{ 'tw-border-primary': !isFollowing }
|
||||
)}
|
||||
data-testid="follower-value"
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
{followers}
|
||||
</span>
|
||||
</Popover>
|
||||
</Space>
|
||||
</Button>
|
||||
) : null}
|
||||
{!isVersionSelected && (
|
||||
<ManageButton
|
||||
allowSoftDelete={!deleted}
|
||||
canDelete={canDelete}
|
||||
deleted={deleted}
|
||||
entityFQN={entityFqn}
|
||||
entityId={entityId}
|
||||
entityName={entityName}
|
||||
entityType={entityType}
|
||||
extraDropdownContent={extraDropdownContent}
|
||||
isRecursiveDelete={isRecursiveDelete}
|
||||
onAnnouncementClick={() => setIsAnnouncementDrawer(true)}
|
||||
onRestoreEntity={onRestoreEntity}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</Space>
|
||||
<Space
|
||||
className="w-full"
|
||||
data-testid="entity-page-info"
|
||||
direction="vertical">
|
||||
<EntityHeader
|
||||
breadcrumb={titleLinks}
|
||||
entityData={{
|
||||
displayName: entityName,
|
||||
name: entityName,
|
||||
deleted,
|
||||
}}
|
||||
entityType={(entityType as EntityType) ?? EntityType.TABLE}
|
||||
extra={
|
||||
<Space align="center" id="version-and-follow-section">
|
||||
{!isUndefined(version) ? (
|
||||
<>
|
||||
{!isUndefined(isVersionSelected) ? (
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
title={
|
||||
<p className="tw-text-xs">
|
||||
{t('message.viewing-older-version')}
|
||||
</p>
|
||||
}
|
||||
trigger="hover">
|
||||
{getVersionButton(toString(version))}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>{getVersionButton(toString(version))}</>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
{!isUndefined(isFollowing) ? (
|
||||
<Button
|
||||
className={classNames(
|
||||
'tw-border tw-border-primary tw-rounded',
|
||||
isFollowing ? 'tw-text-white' : 'tw-text-primary'
|
||||
)}
|
||||
data-testid="follow-button"
|
||||
size="small"
|
||||
type={isFollowing ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
!deleted && followHandler?.();
|
||||
}}>
|
||||
<Space>
|
||||
<StarFilled className="tw-text-xs" />
|
||||
{isFollowing ? t('label.un-follow') : t('label.follow')}
|
||||
<Popover content={getFollowers()} trigger="click">
|
||||
<span
|
||||
className={classNames(
|
||||
'tw-border-l tw-font-medium tw-cursor-pointer hover:tw-underline tw-pl-1',
|
||||
{ 'tw-border-primary': !isFollowing }
|
||||
)}
|
||||
data-testid="follower-value"
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
{followers}
|
||||
</span>
|
||||
</Popover>
|
||||
</Space>
|
||||
</Button>
|
||||
) : null}
|
||||
{!isVersionSelected && (
|
||||
<ManageButton
|
||||
allowSoftDelete={!deleted}
|
||||
canDelete={canDelete}
|
||||
deleted={deleted}
|
||||
entityFQN={entityFqn}
|
||||
entityId={entityId}
|
||||
entityName={entityName}
|
||||
entityType={entityType}
|
||||
extraDropdownContent={extraDropdownContent}
|
||||
isRecursiveDelete={isRecursiveDelete}
|
||||
onAnnouncementClick={() => setIsAnnouncementDrawer(true)}
|
||||
onRestoreEntity={onRestoreEntity}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
icon={
|
||||
serviceType && (
|
||||
<img className="h-8" src={serviceTypeLogo(serviceType)} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<Space wrap className="tw-justify-between" style={{ width: '100%' }}>
|
||||
<Space wrap className="justify-between w-full" size={16}>
|
||||
<Space direction="vertical">
|
||||
<Space wrap align="center" data-testid="extrainfo" size={4}>
|
||||
{extraInfo.map((info, index) => (
|
||||
@ -499,9 +496,7 @@ const EntityPageInfo = ({
|
||||
{(!isEditable || !isTagEditable || deleted) && (
|
||||
<>
|
||||
{(tags.length > 0 || !isEmpty(tier)) && (
|
||||
<span className="d-flex align-center h-4">
|
||||
<IconTagGrey height={18} name="icon-tag" width={18} />
|
||||
</span>
|
||||
<IconTagGrey height={14} name="icon-tag" />
|
||||
)}
|
||||
{tier?.tagFQN && (
|
||||
<Tags
|
||||
@ -603,7 +598,7 @@ const EntityPageInfo = ({
|
||||
onClose={() => setIsAnnouncementDrawer(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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 { Button, Typography } from 'antd';
|
||||
import { ReactComponent as IconExternalLink } from 'assets/svg/external-link.svg';
|
||||
import classNames from 'classnames';
|
||||
import { toString } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ROUTES } from '../../../constants/constants';
|
||||
import { EntityType, FqnPart } from '../../../enums/entity.enum';
|
||||
import { SearchIndex } from '../../../enums/search.enum';
|
||||
import {
|
||||
getNameFromFQN,
|
||||
getPartialNameFromTableFQN,
|
||||
} from '../../../utils/CommonUtils';
|
||||
import { stringToHTML } from '../../../utils/StringsUtils';
|
||||
import { getEntityLink } from '../../../utils/TableUtils';
|
||||
import './TableDataCardTitle.less';
|
||||
|
||||
interface TableDataCardTitleProps {
|
||||
dataTestId?: string;
|
||||
id?: string;
|
||||
searchIndex: SearchIndex | EntityType;
|
||||
source: {
|
||||
fullyQualifiedName?: string;
|
||||
displayName?: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
};
|
||||
isPanel?: boolean;
|
||||
handleLinkClick?: (e: React.MouseEvent) => void;
|
||||
openEntityInNewPage?: boolean;
|
||||
}
|
||||
|
||||
const TableDataCardTitle = ({
|
||||
dataTestId,
|
||||
id,
|
||||
searchIndex,
|
||||
source,
|
||||
handleLinkClick,
|
||||
isPanel = false,
|
||||
openEntityInNewPage = false,
|
||||
}: TableDataCardTitleProps) => {
|
||||
const isTourRoute = location.pathname.includes(ROUTES.TOUR);
|
||||
|
||||
const { testId, displayName } = useMemo(
|
||||
() => ({
|
||||
testId: dataTestId
|
||||
? dataTestId
|
||||
: `${getPartialNameFromTableFQN(source.fullyQualifiedName ?? '', [
|
||||
FqnPart.Service,
|
||||
])}-${source.name}`,
|
||||
displayName:
|
||||
source.type === 'tag'
|
||||
? toString(getNameFromFQN(source.fullyQualifiedName ?? ''))
|
||||
: toString(source.displayName),
|
||||
}),
|
||||
[dataTestId, source]
|
||||
);
|
||||
|
||||
const title = (
|
||||
<Button
|
||||
data-testid={testId}
|
||||
id={`${id ?? testId}-title`}
|
||||
type="link"
|
||||
onClick={isTourRoute ? handleLinkClick : undefined}>
|
||||
{stringToHTML(displayName)}
|
||||
{openEntityInNewPage && (
|
||||
<IconExternalLink className="anticon" height={14} />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (isTourRoute) {
|
||||
return title;
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography.Title
|
||||
ellipsis
|
||||
className="m-b-0 text-base"
|
||||
level={5}
|
||||
title={displayName}>
|
||||
<Link
|
||||
className={classNames(
|
||||
'table-data-card-title-container w-fit-content w-max-90',
|
||||
{
|
||||
'button-hover': isPanel,
|
||||
}
|
||||
)}
|
||||
target={openEntityInNewPage ? '_blank' : '_self'}
|
||||
to={getEntityLink(searchIndex, source.fullyQualifiedName ?? '')}>
|
||||
{title}
|
||||
</Link>
|
||||
</Typography.Title>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableDataCardTitle;
|
@ -14,7 +14,6 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { SearchIndex } from '../../../enums/search.enum';
|
||||
import TableDataCardV2 from './TableDataCardV2';
|
||||
|
||||
jest.mock('../../../utils/TableUtils', () => ({
|
||||
@ -42,13 +41,16 @@ jest.mock('../table-data-card/TableDataCardBody', () => {
|
||||
|
||||
const mockHandleSummaryPanelDisplay = jest.fn();
|
||||
|
||||
jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({
|
||||
EntityHeader: jest.fn().mockImplementation(() => <p>EntityHeader</p>),
|
||||
}));
|
||||
|
||||
describe('Test TableDataCard Component', () => {
|
||||
it('Component should render', () => {
|
||||
const { getByTestId } = render(
|
||||
const { getByTestId, getByText } = render(
|
||||
<TableDataCardV2
|
||||
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||
id="1"
|
||||
searchIndex={SearchIndex.TABLE}
|
||||
source={{
|
||||
id: '1',
|
||||
name: 'Name1',
|
||||
@ -57,26 +59,9 @@ describe('Test TableDataCard Component', () => {
|
||||
{ wrapper: MemoryRouter }
|
||||
);
|
||||
const tableDataCard = getByTestId('table-data-card');
|
||||
const entityHeader = getByText('EntityHeader');
|
||||
|
||||
expect(tableDataCard).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Component should render for deleted', () => {
|
||||
const { getByTestId } = render(
|
||||
<TableDataCardV2
|
||||
handleSummaryPanelDisplay={mockHandleSummaryPanelDisplay}
|
||||
id="1"
|
||||
searchIndex={SearchIndex.TABLE}
|
||||
source={{
|
||||
id: '2',
|
||||
name: 'Name2',
|
||||
deleted: true,
|
||||
}}
|
||||
/>,
|
||||
{ wrapper: MemoryRouter }
|
||||
);
|
||||
const deleted = getByTestId('deleted');
|
||||
|
||||
expect(deleted).toBeInTheDocument();
|
||||
expect(entityHeader).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -11,36 +11,31 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
||||
import { Checkbox } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component';
|
||||
import { isString, startCase, uniqueId } from 'lodash';
|
||||
import { ExtraInfo } from 'Models';
|
||||
import React, { forwardRef, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { getEntityId, getEntityName } from 'utils/EntityUtils';
|
||||
import AppState from '../../../AppState';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import {
|
||||
getEntityBreadcrumbs,
|
||||
getEntityId,
|
||||
getEntityName,
|
||||
} from 'utils/EntityUtils';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
||||
import { ROUTES } from '../../../constants/constants';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { SearchIndex } from '../../../enums/search.enum';
|
||||
import { CurrentTourPageType } from '../../../enums/tour.enum';
|
||||
import { OwnerType } from '../../../enums/user.enum';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import {
|
||||
getEntityPlaceHolder,
|
||||
getOwnerValue,
|
||||
} from '../../../utils/CommonUtils';
|
||||
import {
|
||||
getEntityHeaderLabel,
|
||||
getServiceIcon,
|
||||
getUsagePercentile,
|
||||
} from '../../../utils/TableUtils';
|
||||
import { getServiceIcon, getUsagePercentile } from '../../../utils/TableUtils';
|
||||
import { SearchedDataProps } from '../../searched-data/SearchedData.interface';
|
||||
import '../table-data-card/TableDataCard.style.css';
|
||||
import TableDataCardBody from '../table-data-card/TableDataCardBody';
|
||||
import TableDataCardTitle from './TableDataCardTitle.component';
|
||||
import './TableDataCardV2.less';
|
||||
|
||||
export interface TableDataCardPropsV2 {
|
||||
@ -51,7 +46,6 @@ export interface TableDataCardPropsV2 {
|
||||
key: string;
|
||||
value: number;
|
||||
}[];
|
||||
searchIndex: SearchIndex | EntityType;
|
||||
handleSummaryPanelDisplay?: (
|
||||
details: SearchedDataProps['data'][number]['_source'],
|
||||
entityType: string
|
||||
@ -71,16 +65,14 @@ const TableDataCardV2: React.FC<TableDataCardPropsV2> = forwardRef<
|
||||
className,
|
||||
source,
|
||||
matches,
|
||||
searchIndex,
|
||||
handleSummaryPanelDisplay,
|
||||
showCheckboxes,
|
||||
checked,
|
||||
openEntityInNewPage = false,
|
||||
openEntityInNewPage,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const { tab } = useParams<{ tab: string }>();
|
||||
|
||||
const otherDetails = useMemo(() => {
|
||||
@ -137,21 +129,15 @@ const TableDataCardV2: React.FC<TableDataCardPropsV2> = forwardRef<
|
||||
return _otherDetails;
|
||||
}, [source]);
|
||||
|
||||
const handleLinkClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (location.pathname.includes(ROUTES.TOUR)) {
|
||||
AppState.currentTourPage = CurrentTourPageType.DATASET_PAGE;
|
||||
}
|
||||
};
|
||||
|
||||
const headerLabel = useMemo(() => {
|
||||
return getEntityHeaderLabel(source);
|
||||
}, [source]);
|
||||
|
||||
const serviceIcon = useMemo(() => {
|
||||
return getServiceIcon(source);
|
||||
}, [source]);
|
||||
|
||||
const breadcrumbs = useMemo(
|
||||
() => getEntityBreadcrumbs(source, source.entityType as EntityType),
|
||||
[source]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
@ -165,33 +151,20 @@ const TableDataCardV2: React.FC<TableDataCardPropsV2> = forwardRef<
|
||||
onClick={() => {
|
||||
handleSummaryPanelDisplay && handleSummaryPanelDisplay(source, tab);
|
||||
}}>
|
||||
<div>
|
||||
{showCheckboxes && (
|
||||
<Checkbox checked={checked} className="float-right" />
|
||||
)}
|
||||
{headerLabel}
|
||||
<div className="tw-flex tw-items-center">
|
||||
{serviceIcon}
|
||||
<TableDataCardTitle
|
||||
handleLinkClick={handleLinkClick}
|
||||
id={id}
|
||||
openEntityInNewPage={openEntityInNewPage}
|
||||
searchIndex={searchIndex}
|
||||
source={source}
|
||||
/>
|
||||
<EntityHeader
|
||||
titleIsLink
|
||||
breadcrumb={breadcrumbs}
|
||||
entityData={source}
|
||||
entityType={source.entityType as EntityType}
|
||||
extra={
|
||||
showCheckboxes && (
|
||||
<Checkbox checked={checked} className="m-l-auto" />
|
||||
)
|
||||
}
|
||||
icon={serviceIcon}
|
||||
openEntityInNewPage={openEntityInNewPage}
|
||||
/>
|
||||
|
||||
{source.deleted && (
|
||||
<>
|
||||
<div
|
||||
className="tw-rounded tw-bg-error-lite tw-text-error tw-text-xs tw-font-medium tw-h-5 tw-px-1.5 tw-py-0.5 tw-ml-2"
|
||||
data-testid="deleted">
|
||||
<ExclamationCircleOutlined className="tw-mr-1" />
|
||||
{t('label.deleted')}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-pt-3">
|
||||
<TableDataCardBody
|
||||
description={source.description || ''}
|
||||
|
@ -13,9 +13,10 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { ELASTICSEARCH_ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||
import { isUndefined, toString } from 'lodash';
|
||||
import { isUndefined } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import { PAGE_SIZE } from '../../constants/constants';
|
||||
import { MAX_RESULT_HITS } from '../../constants/explore.constants';
|
||||
import { Paging } from '../../generated/type/paging';
|
||||
@ -51,7 +52,7 @@ const SearchedData: React.FC<SearchedDataProps> = ({
|
||||
handleSummaryPanelDisplay,
|
||||
}) => {
|
||||
const highlightSearchResult = () => {
|
||||
return data.map(({ _source: table, highlight, _index }, index) => {
|
||||
return data.map(({ _source: table, highlight }, index) => {
|
||||
let tDesc = table.description ?? '';
|
||||
const highLightedTexts = highlight?.description || [];
|
||||
|
||||
@ -65,7 +66,7 @@ const SearchedData: React.FC<SearchedDataProps> = ({
|
||||
});
|
||||
}
|
||||
|
||||
let name = toString(table.displayName);
|
||||
let name = getEntityName(table);
|
||||
if (!isUndefined(highlight)) {
|
||||
name = highlight?.name?.join(' ') || name;
|
||||
}
|
||||
@ -102,7 +103,6 @@ const SearchedData: React.FC<SearchedDataProps> = ({
|
||||
handleSummaryPanelDisplay={handleSummaryPanelDisplay}
|
||||
id={`tabledatacard${index}`}
|
||||
matches={matches}
|
||||
searchIndex={_index}
|
||||
source={{ ...table, name, description: tDesc }}
|
||||
/>
|
||||
</div>
|
||||
|
@ -282,6 +282,7 @@
|
||||
"enter-property-value": "Enter Property Value",
|
||||
"enter-type-password": "Enter {{type}} Password",
|
||||
"entity-count": "{{entity}} Count",
|
||||
"entity-detail-plural": "{{entity}} details",
|
||||
"entity-hyphen-value": "{{entity}} - {{value}}",
|
||||
"entity-index": "{{entity}} index",
|
||||
"entity-name": "{{entity}} Name",
|
||||
|
@ -282,6 +282,7 @@
|
||||
"enter-property-value": "Ingrese el valor de la propiedad",
|
||||
"enter-type-password": "Ingrese la contraseña de {{type}}",
|
||||
"entity-count": "Cantidad de {{entity}}",
|
||||
"entity-detail-plural": "{{entity}} details",
|
||||
"entity-hyphen-value": "{{entity}} - {{value}}",
|
||||
"entity-index": "Índice de {{entity}}",
|
||||
"entity-name": "Nombre de {{entity}}",
|
||||
|
@ -282,6 +282,7 @@
|
||||
"enter-property-value": "Entrer une Valeur pour la Propriété",
|
||||
"enter-type-password": "Enter {{type}} Password",
|
||||
"entity-count": "{{entity}} Count",
|
||||
"entity-detail-plural": "{{entity}} details",
|
||||
"entity-hyphen-value": "{{entity}} - {{value}}",
|
||||
"entity-index": "{{entity}} index",
|
||||
"entity-name": "{{entity}} Name",
|
||||
|
@ -282,6 +282,7 @@
|
||||
"enter-property-value": "プロパティの値を入力",
|
||||
"enter-type-password": "{{type}} のパスワードを入力",
|
||||
"entity-count": "{{entity}}の数",
|
||||
"entity-detail-plural": "{{entity}} details",
|
||||
"entity-hyphen-value": "{{entity}} - {{value}}",
|
||||
"entity-index": "{{entity}} インデックス",
|
||||
"entity-name": "{{entity}} 名",
|
||||
|
@ -282,6 +282,7 @@
|
||||
"enter-property-value": "Introduzir valor da propriedade",
|
||||
"enter-type-password": "Introduzir {{type}} da senha",
|
||||
"entity-count": "{{entity}} contagem",
|
||||
"entity-detail-plural": "{{entity}} details",
|
||||
"entity-hyphen-value": "{{entity}} - {{value}}",
|
||||
"entity-index": "Indíce da {{entity}}",
|
||||
"entity-name": "Nome da {{entity}}",
|
||||
|
@ -282,6 +282,7 @@
|
||||
"enter-property-value": "输入属性值",
|
||||
"enter-type-password": "输入 {{type}} 密码",
|
||||
"entity-count": "{{entity}} Count",
|
||||
"entity-detail-plural": "{{entity}} details",
|
||||
"entity-hyphen-value": "{{entity}} - {{value}}",
|
||||
"entity-index": "{{entity}} index",
|
||||
"entity-name": "{{entity}} Name",
|
||||
|
@ -72,7 +72,6 @@ import { getContainerDetailPath } from 'utils/ContainerDetailUtils';
|
||||
import { getEntityLineage, getEntityName } from 'utils/EntityUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
|
||||
import { getLineageViewPath } from 'utils/RouterUtils';
|
||||
import { serviceTypeLogo } from 'utils/ServiceUtils';
|
||||
import { bytesToSize } from 'utils/StringsUtils';
|
||||
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
|
||||
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
|
||||
@ -286,7 +285,6 @@ const ContainerPage = () => {
|
||||
];
|
||||
|
||||
const breadcrumbTitles = useMemo(() => {
|
||||
const serviceType = containerData?.serviceType;
|
||||
const service = containerData?.service;
|
||||
const serviceName = service?.name;
|
||||
|
||||
@ -301,14 +299,8 @@ const ContainerPage = () => {
|
||||
url: serviceName
|
||||
? getServiceDetailsPath(serviceName, ServiceCategory.STORAGE_SERVICES)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
...parentContainerItems,
|
||||
{
|
||||
name: entityName,
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
];
|
||||
}, [containerData, containerName, entityName, parentContainers]);
|
||||
|
||||
@ -632,6 +624,7 @@ const ContainerPage = () => {
|
||||
isFollowing={isUserFollowing}
|
||||
isTagEditable={hasEditTagsPermission}
|
||||
removeTier={hasEditTierPermission ? handleRemoveTier : undefined}
|
||||
serviceType={containerData?.serviceType ?? ''}
|
||||
tags={tags}
|
||||
tagsHandler={handleUpdateTags}
|
||||
tier={tier}
|
||||
|
@ -65,7 +65,6 @@ import {
|
||||
import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils';
|
||||
import { deletePost, updateThreadData } from '../../utils/FeedUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
|
||||
export type ChartType = {
|
||||
@ -242,12 +241,6 @@ const DashboardDetailsPage = () => {
|
||||
ServiceCategory.DASHBOARD_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
{
|
||||
name: getEntityName(res),
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -33,6 +33,7 @@ import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichText
|
||||
import TabsPane from 'components/common/TabsPane/TabsPane';
|
||||
import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface';
|
||||
import PageContainerV1 from 'components/containers/PageContainerV1';
|
||||
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
||||
import {
|
||||
@ -110,10 +111,7 @@ import {
|
||||
} from '../../utils/FeedUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { getSettingPath } from '../../utils/RouterUtils';
|
||||
import {
|
||||
getServiceRouteFromServiceType,
|
||||
serviceTypeLogo,
|
||||
} from '../../utils/ServiceUtils';
|
||||
import { getServiceRouteFromServiceType } from '../../utils/ServiceUtils';
|
||||
import { getErrorText } from '../../utils/StringsUtils';
|
||||
import {
|
||||
getEntityLink,
|
||||
@ -283,7 +281,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
id = '',
|
||||
name,
|
||||
service,
|
||||
serviceType,
|
||||
database,
|
||||
tags,
|
||||
} = res;
|
||||
@ -312,7 +309,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
ServiceCategory.DATABASE_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
{
|
||||
name: getPartialNameFromTableFQN(
|
||||
@ -321,11 +317,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
),
|
||||
url: getDatabaseDetailsPath(database.fullyQualifiedName ?? ''),
|
||||
},
|
||||
{
|
||||
name: getEntityName(res),
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
@ -634,7 +625,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
|
||||
const getSchemaTableList = () => {
|
||||
return (
|
||||
<>
|
||||
<Col span={24}>
|
||||
<TableAntd
|
||||
bordered
|
||||
className="table-shadow"
|
||||
@ -659,7 +650,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
totalCount={tableInstanceCount}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
@ -785,10 +776,10 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
{databaseSchemaPermission.ViewAll ||
|
||||
databaseSchemaPermission.ViewBasic ? (
|
||||
<PageContainerV1>
|
||||
<Row
|
||||
className="p-x-md p-t-lg"
|
||||
data-testid="page-container"
|
||||
gutter={[0, 12]}>
|
||||
<PageLayoutV1
|
||||
pageTitle={t('label.entity-detail-plural', {
|
||||
entity: getEntityName(databaseSchema),
|
||||
})}>
|
||||
{IsSchemaDetailsLoading ? (
|
||||
<Skeleton
|
||||
active
|
||||
@ -820,6 +811,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
databaseSchemaPermission.EditAll ||
|
||||
databaseSchemaPermission.EditTags
|
||||
}
|
||||
serviceType={databaseSchema?.serviceType ?? ''}
|
||||
tags={tags}
|
||||
tagsHandler={onTagUpdate}
|
||||
tier={tier}
|
||||
@ -834,27 +826,6 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
</Col>
|
||||
<Col data-testid="description-container" span={24}>
|
||||
<Description
|
||||
description={description}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.DESCRIPTION,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={databaseSchemaFQN}
|
||||
entityName={databaseSchemaName}
|
||||
entityType={EntityType.DATABASE_SCHEMA}
|
||||
hasEditAccess={
|
||||
databaseSchemaPermission.EditDescription ||
|
||||
databaseSchemaPermission.EditAll
|
||||
}
|
||||
isEdit={isEdit}
|
||||
onCancel={onCancel}
|
||||
onDescriptionEdit={onDescriptionEdit}
|
||||
onDescriptionUpdate={onDescriptionUpdate}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
</Col>
|
||||
</>
|
||||
)}
|
||||
<Col span={24}>
|
||||
@ -869,7 +840,32 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
</Col>
|
||||
<Col className="p-y-md" span={24}>
|
||||
{activeTab === 1 && (
|
||||
<Fragment>{getSchemaTableList()}</Fragment>
|
||||
<Card className="h-full">
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col data-testid="description-container" span={24}>
|
||||
<Description
|
||||
description={description}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.DESCRIPTION,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={databaseSchemaFQN}
|
||||
entityName={databaseSchemaName}
|
||||
entityType={EntityType.DATABASE_SCHEMA}
|
||||
hasEditAccess={
|
||||
databaseSchemaPermission.EditDescription ||
|
||||
databaseSchemaPermission.EditAll
|
||||
}
|
||||
isEdit={isEdit}
|
||||
onCancel={onCancel}
|
||||
onDescriptionEdit={onDescriptionEdit}
|
||||
onDescriptionUpdate={onDescriptionUpdate}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
</Col>
|
||||
{getSchemaTableList()}
|
||||
</Row>
|
||||
</Card>
|
||||
)}
|
||||
{activeTab === 2 && (
|
||||
<Card className="p-t-xss p-b-md">
|
||||
@ -914,7 +910,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
/>
|
||||
) : null}
|
||||
</Col>
|
||||
</Row>
|
||||
</PageLayoutV1>
|
||||
</PageContainerV1>
|
||||
) : (
|
||||
<ErrorPlaceHolder>
|
||||
|
@ -181,6 +181,10 @@ jest.mock('react-router-dom', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('components/containers/PageLayoutV1', () => {
|
||||
return jest.fn().mockImplementation(({ children }) => children);
|
||||
});
|
||||
|
||||
describe('Tests for DatabaseSchemaPage', () => {
|
||||
it('Page should render properly for "Tables" tab', async () => {
|
||||
act(() => {
|
||||
|
@ -76,7 +76,6 @@ import {
|
||||
import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils';
|
||||
import { deletePost, updateThreadData } from '../../utils/FeedUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
|
||||
const DatasetDetailsPage: FunctionComponent = () => {
|
||||
@ -234,7 +233,6 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
ServiceCategory.DATABASE_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
{
|
||||
name: getPartialNameFromTableFQN(databaseFullyQualifiedName, [
|
||||
@ -251,11 +249,6 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
databaseSchemaFullyQualifiedName
|
||||
),
|
||||
},
|
||||
{
|
||||
name: getEntityName(res),
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
]);
|
||||
|
||||
addToRecentViewed({
|
||||
|
@ -206,7 +206,6 @@ const EntityVersionPage: FunctionComponent = () => {
|
||||
tags = [],
|
||||
database,
|
||||
service,
|
||||
serviceType,
|
||||
databaseSchema,
|
||||
} = res;
|
||||
const serviceName = service?.name ?? '';
|
||||
@ -219,7 +218,6 @@ const EntityVersionPage: FunctionComponent = () => {
|
||||
ServiceCategory.DATABASE_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
{
|
||||
name: getPartialNameFromTableFQN(
|
||||
@ -237,11 +235,6 @@ const EntityVersionPage: FunctionComponent = () => {
|
||||
databaseSchema?.fullyQualifiedName ?? ''
|
||||
),
|
||||
},
|
||||
{
|
||||
name: getEntityName(res),
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
]);
|
||||
|
||||
getTableVersions(id)
|
||||
|
@ -51,7 +51,6 @@ import {
|
||||
defaultFields,
|
||||
getFormattedPipelineDetails,
|
||||
} from '../../utils/PipelineDetailsUtils';
|
||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
|
||||
const PipelineDetailsPage = () => {
|
||||
@ -134,12 +133,6 @@ const PipelineDetailsPage = () => {
|
||||
ServiceCategory.PIPELINE_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
{
|
||||
name: getEntityName(res),
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -58,7 +58,6 @@ import {
|
||||
import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils';
|
||||
import { deletePost, updateThreadData } from '../../utils/FeedUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import {
|
||||
getCurrentTopicTab,
|
||||
@ -210,12 +209,6 @@ const TopicDetailsPage: FunctionComponent = () => {
|
||||
ServiceCategory.MESSAGING_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
{
|
||||
name: getEntityName(res),
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -333,12 +333,13 @@ jest.mock('components/common/DeleteWidget/DeleteWidgetModal', () => {
|
||||
<p data-testid="delete-entity">DeleteWidgetModal component</p>
|
||||
);
|
||||
});
|
||||
const mockObserve = jest.fn();
|
||||
const mockunObserve = jest.fn();
|
||||
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||
observe: mockObserve,
|
||||
unobserve: mockunObserve,
|
||||
jest.mock('components/containers/PageLayoutV1', () => {
|
||||
return jest.fn().mockImplementation(({ children }) => children);
|
||||
});
|
||||
|
||||
jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({
|
||||
EntityHeader: jest.fn().mockImplementation(() => <p>EntityHeader</p>),
|
||||
}));
|
||||
|
||||
describe('Test DatabaseDetails page', () => {
|
||||
@ -347,8 +348,7 @@ describe('Test DatabaseDetails page', () => {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const pageContainer = await findByTestId(container, 'page-container');
|
||||
const titleBreadcrumb = await findByText(container, /TitleBreadcrumb/i);
|
||||
const entityHeader = await findByText(container, 'EntityHeader');
|
||||
const descriptionContainer = await findByTestId(
|
||||
container,
|
||||
'description-container'
|
||||
@ -358,8 +358,7 @@ describe('Test DatabaseDetails page', () => {
|
||||
'database-databaseSchemas'
|
||||
);
|
||||
|
||||
expect(pageContainer).toBeInTheDocument();
|
||||
expect(titleBreadcrumb).toBeInTheDocument();
|
||||
expect(entityHeader).toBeInTheDocument();
|
||||
expect(descriptionContainer).toBeInTheDocument();
|
||||
expect(databaseTable).toBeInTheDocument();
|
||||
});
|
||||
@ -420,8 +419,7 @@ describe('Test DatabaseDetails page', () => {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const pageContainer = await findByTestId(container, 'page-container');
|
||||
const titleBreadcrumb = await findByText(container, /TitleBreadcrumb/i);
|
||||
const entityHeader = await findByText(container, 'EntityHeader');
|
||||
const descriptionContainer = await findByTestId(
|
||||
container,
|
||||
'description-container'
|
||||
@ -431,8 +429,7 @@ describe('Test DatabaseDetails page', () => {
|
||||
'database-databaseSchemas'
|
||||
);
|
||||
|
||||
expect(pageContainer).toBeInTheDocument();
|
||||
expect(titleBreadcrumb).toBeInTheDocument();
|
||||
expect(entityHeader).toBeInTheDocument();
|
||||
expect(descriptionContainer).toBeInTheDocument();
|
||||
expect(databaseTable).toBeInTheDocument();
|
||||
});
|
||||
|
@ -23,9 +23,10 @@ import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlac
|
||||
import NextPrevious from 'components/common/next-previous/NextPrevious';
|
||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import TabsPane from 'components/common/TabsPane/TabsPane';
|
||||
import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component';
|
||||
import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface';
|
||||
import PageContainerV1 from 'components/containers/PageContainerV1';
|
||||
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
||||
import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
||||
import {
|
||||
@ -314,12 +315,6 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
ServiceCategory.DATABASE_SERVICES
|
||||
)
|
||||
: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
},
|
||||
{
|
||||
name: getEntityName(res),
|
||||
url: '',
|
||||
activeTitle: true,
|
||||
},
|
||||
]);
|
||||
fetchDatabaseSchemasAndDBTModels();
|
||||
@ -658,10 +653,10 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
<>
|
||||
{databasePermission.ViewAll || databasePermission.ViewBasic ? (
|
||||
<PageContainerV1>
|
||||
<Row
|
||||
className=" p-x-md p-t-lg"
|
||||
data-testid="page-container"
|
||||
gutter={[0, 12]}>
|
||||
<PageLayoutV1
|
||||
pageTitle={t('label.entity-detail-plural', {
|
||||
entity: getEntityName(database),
|
||||
})}>
|
||||
{isDatabaseDetailsLoading ? (
|
||||
<Skeleton
|
||||
active
|
||||
@ -672,20 +667,31 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Col span={24}>
|
||||
<Space align="center" className="justify-between w-full">
|
||||
<TitleBreadcrumb titleLinks={slashedDatabaseName} />
|
||||
<ManageButton
|
||||
isRecursiveDelete
|
||||
allowSoftDelete={false}
|
||||
canDelete={databasePermission.Delete}
|
||||
entityFQN={databaseFQN}
|
||||
entityId={databaseId}
|
||||
entityName={databaseName}
|
||||
entityType={EntityType.DATABASE}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
{database && (
|
||||
<EntityHeader
|
||||
breadcrumb={slashedDatabaseName}
|
||||
entityData={database}
|
||||
entityType={EntityType.DATABASE}
|
||||
extra={
|
||||
<ManageButton
|
||||
isRecursiveDelete
|
||||
allowSoftDelete={false}
|
||||
canDelete={databasePermission.Delete}
|
||||
entityFQN={databaseFQN}
|
||||
entityId={databaseId}
|
||||
entityName={databaseName}
|
||||
entityType={EntityType.DATABASE}
|
||||
/>
|
||||
}
|
||||
icon={
|
||||
<img
|
||||
className="h-8"
|
||||
src={serviceTypeLogo(serviceType ?? '')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Col span={24}>
|
||||
{extraInfo.map((info, index) => (
|
||||
<Space key={index}>
|
||||
@ -809,7 +815,7 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
/>
|
||||
) : null}
|
||||
</Col>
|
||||
</Row>
|
||||
</PageLayoutV1>
|
||||
</PageContainerV1>
|
||||
) : (
|
||||
<ErrorPlaceHolder>
|
||||
|
@ -271,6 +271,10 @@ jest.mock('../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('components/containers/PageLayoutV1', () => {
|
||||
return jest.fn().mockImplementation(({ children }) => children);
|
||||
});
|
||||
|
||||
describe('Test ServicePage Component', () => {
|
||||
it('Component should render', async () => {
|
||||
const { container } = render(<ServicePage />, {
|
||||
|
@ -24,10 +24,11 @@ import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture';
|
||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import TabsPane from 'components/common/TabsPane/TabsPane';
|
||||
import TestConnection from 'components/common/TestConnection/TestConnection';
|
||||
import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component';
|
||||
import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface';
|
||||
import PageContainerV1 from 'components/containers/PageContainerV1';
|
||||
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
||||
import DataModelTable from 'components/DataModels/DataModelsTable';
|
||||
import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component';
|
||||
import Ingestion from 'components/Ingestion/Ingestion.component';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
||||
@ -702,7 +703,7 @@ const ServicePage: FunctionComponent = () => {
|
||||
getServiceByFQN(serviceName, serviceFQN, 'owner')
|
||||
.then((resService) => {
|
||||
if (resService) {
|
||||
const { description, serviceType } = resService;
|
||||
const { description } = resService;
|
||||
setServiceDetails(resService);
|
||||
setConnectionDetails(
|
||||
resService.connection?.config as DashboardConnection
|
||||
@ -716,12 +717,6 @@ const ServicePage: FunctionComponent = () => {
|
||||
getServiceRouteFromServiceType(serviceName)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: getEntityName(resService),
|
||||
url: '',
|
||||
imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined,
|
||||
activeTitle: true,
|
||||
},
|
||||
]);
|
||||
getOtherDetails();
|
||||
} else {
|
||||
@ -788,6 +783,30 @@ const ServicePage: FunctionComponent = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveOwner = async () => {
|
||||
const updatedData = {
|
||||
...serviceDetails,
|
||||
owner: undefined,
|
||||
} as ServicesUpdateRequest;
|
||||
|
||||
const jsonPatch = compare(serviceDetails || {}, updatedData);
|
||||
try {
|
||||
const res = await updateOwnerService(
|
||||
serviceName,
|
||||
serviceDetails?.id ?? '',
|
||||
jsonPatch
|
||||
);
|
||||
setServiceDetails(res);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.entity-updating-error', {
|
||||
entity: t('label.owner-lowercase'),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateOwner = (owner: ServicesType['owner']) => {
|
||||
if (isUndefined(owner)) {
|
||||
handleRemoveOwner();
|
||||
@ -832,30 +851,6 @@ const ServicePage: FunctionComponent = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveOwner = async () => {
|
||||
const updatedData = {
|
||||
...serviceDetails,
|
||||
owner: undefined,
|
||||
} as ServicesUpdateRequest;
|
||||
|
||||
const jsonPatch = compare(serviceDetails || {}, updatedData);
|
||||
try {
|
||||
const res = await updateOwnerService(
|
||||
serviceName,
|
||||
serviceDetails?.id ?? '',
|
||||
jsonPatch
|
||||
);
|
||||
setServiceDetails(res);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.entity-updating-error', {
|
||||
entity: t('label.owner-lowercase'),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onDescriptionEdit = (): void => {
|
||||
setIsEdit(true);
|
||||
};
|
||||
@ -1018,69 +1013,53 @@ const ServicePage: FunctionComponent = () => {
|
||||
{getEntityMissingError(serviceName as string, serviceFQN)}
|
||||
</ErrorPlaceHolder>
|
||||
) : (
|
||||
<>
|
||||
<PageLayoutV1
|
||||
pageTitle={t('label.entity-detail-plural', {
|
||||
entity: getEntityName(serviceDetails),
|
||||
})}>
|
||||
{servicePermission.ViewAll || servicePermission.ViewBasic ? (
|
||||
<Row
|
||||
className="p-x-md p-t-lg"
|
||||
data-testid="service-page"
|
||||
gutter={[0, 12]}>
|
||||
<Col span={24}>
|
||||
<Space align="center" className="justify-between w-full">
|
||||
<TitleBreadcrumb titleLinks={slashedTableName} />
|
||||
{serviceDetails?.serviceType !==
|
||||
MetadataServiceType.OpenMetadata && (
|
||||
<Tooltip
|
||||
placement="topRight"
|
||||
title={
|
||||
!servicePermission.Delete &&
|
||||
t('message.no-permission-for-action')
|
||||
}>
|
||||
<Button
|
||||
ghost
|
||||
data-testid="service-delete"
|
||||
disabled={!servicePermission.Delete}
|
||||
icon={
|
||||
<IcDeleteColored
|
||||
className="anticon"
|
||||
height={14}
|
||||
viewBox="0 0 24 24"
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={handleDelete}>
|
||||
{t('label.delete')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<DeleteWidgetModal
|
||||
isRecursiveDelete
|
||||
afterDeleteAction={() =>
|
||||
history.push(
|
||||
getSettingPath(
|
||||
GlobalSettingsMenuCategory.SERVICES,
|
||||
SERVICE_CATEGORY_TYPE[
|
||||
serviceCategory as keyof typeof SERVICE_CATEGORY_TYPE
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
allowSoftDelete={false}
|
||||
deleteMessage={getDeleteEntityMessage(
|
||||
serviceName || '',
|
||||
paging.total,
|
||||
schemaCount,
|
||||
tableCount
|
||||
)}
|
||||
entityId={serviceDetails?.id}
|
||||
entityName={serviceDetails?.name || ''}
|
||||
entityType={serviceName?.slice(0, -1)}
|
||||
visible={deleteWidgetVisible}
|
||||
onCancel={() => setDeleteWidgetVisible(false)}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<Row data-testid="service-page" gutter={[0, 12]}>
|
||||
{serviceDetails && (
|
||||
<EntityHeader
|
||||
breadcrumb={slashedTableName}
|
||||
entityData={serviceDetails}
|
||||
extra={
|
||||
serviceDetails?.serviceType !==
|
||||
MetadataServiceType.OpenMetadata && (
|
||||
<Tooltip
|
||||
placement="topRight"
|
||||
title={
|
||||
!servicePermission.Delete &&
|
||||
t('message.no-permission-for-action')
|
||||
}>
|
||||
<Button
|
||||
ghost
|
||||
data-testid="service-delete"
|
||||
disabled={!servicePermission.Delete}
|
||||
icon={
|
||||
<IcDeleteColored
|
||||
className="anticon"
|
||||
height={14}
|
||||
viewBox="0 0 24 24"
|
||||
width={14}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={handleDelete}>
|
||||
{t('label.delete')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
icon={
|
||||
<img
|
||||
className="h-8"
|
||||
src={serviceTypeLogo(serviceDetails.serviceType)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Col span={24}>
|
||||
<Space>
|
||||
{extraInfo.map((info) => (
|
||||
@ -1222,7 +1201,33 @@ const ServicePage: FunctionComponent = () => {
|
||||
{t('message.no-permission-to-view')}
|
||||
</ErrorPlaceHolder>
|
||||
)}
|
||||
</>
|
||||
|
||||
<DeleteWidgetModal
|
||||
isRecursiveDelete
|
||||
afterDeleteAction={() =>
|
||||
history.push(
|
||||
getSettingPath(
|
||||
GlobalSettingsMenuCategory.SERVICES,
|
||||
SERVICE_CATEGORY_TYPE[
|
||||
serviceCategory as keyof typeof SERVICE_CATEGORY_TYPE
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
allowSoftDelete={false}
|
||||
deleteMessage={getDeleteEntityMessage(
|
||||
serviceName || '',
|
||||
paging.total,
|
||||
schemaCount,
|
||||
tableCount
|
||||
)}
|
||||
entityId={serviceDetails?.id}
|
||||
entityName={serviceDetails?.name || ''}
|
||||
entityType={serviceName?.slice(0, -1)}
|
||||
visible={deleteWidgetVisible}
|
||||
onCancel={() => setDeleteWidgetVisible(false)}
|
||||
/>
|
||||
</PageLayoutV1>
|
||||
)}
|
||||
</PageContainerV1>
|
||||
);
|
||||
|
@ -529,8 +529,9 @@ const TagsPage = () => {
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Fetch all classifications initially
|
||||
* Do not set current if we already have currentClassification set
|
||||
*/
|
||||
fetchClassifications(true);
|
||||
fetchClassifications(!tagCategoryName);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -47,17 +47,6 @@
|
||||
box-shadow: @panels-shadow-color;
|
||||
}
|
||||
|
||||
//font weight
|
||||
.font-300 {
|
||||
font-weight: 300;
|
||||
}
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
.text-600 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// text color
|
||||
.text-primary {
|
||||
color: @primary;
|
||||
@ -244,17 +233,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Font Weight
|
||||
.font-normal {
|
||||
font-weight: 500;
|
||||
}
|
||||
.font-semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.transform-180 {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@ -293,6 +271,8 @@
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
color: @text-color !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
/* Opacity CSS start */
|
||||
|
@ -34,18 +34,3 @@ button {
|
||||
color: initial;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
// CheckBox
|
||||
.ant-checkbox-inner {
|
||||
border-width: 2px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-color: #7147e8;
|
||||
}
|
||||
|
||||
.ant-checkbox-inner::after {
|
||||
top: 48%;
|
||||
left: 18.5%;
|
||||
width: 6.25px;
|
||||
height: 10px;
|
||||
}
|
||||
|
@ -140,6 +140,9 @@
|
||||
}
|
||||
|
||||
//Height
|
||||
.h-inherit {
|
||||
height: inherit;
|
||||
}
|
||||
.h-3 {
|
||||
height: 12px;
|
||||
}
|
||||
|
@ -12,6 +12,9 @@
|
||||
*/
|
||||
|
||||
//font weight
|
||||
.font-thin {
|
||||
font-weight: 300;
|
||||
}
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
@ -164,3 +164,8 @@
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
// Vertical Align
|
||||
.vertical-baseline {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
@ -18,12 +18,19 @@ import {
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
} from 'components/EntityLineage/EntityLineage.interface';
|
||||
import { EntityUnion } from 'components/Explore/explore.interface';
|
||||
import {
|
||||
EntityUnion,
|
||||
EntityWithServices,
|
||||
} from 'components/Explore/explore.interface';
|
||||
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
import { SearchedDataProps } from 'components/searched-data/SearchedData.interface';
|
||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||
import { Tag } from 'generated/entity/classification/tag';
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { Topic } from 'generated/entity/data/topic';
|
||||
import i18next from 'i18next';
|
||||
import { get, isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
|
||||
import { Bucket, EntityDetailUnion } from 'Models';
|
||||
@ -34,8 +41,13 @@ import {
|
||||
getDashboardDetailsPath,
|
||||
getDatabaseDetailsPath,
|
||||
getDatabaseSchemaDetailsPath,
|
||||
getGlossaryTermDetailsPath,
|
||||
getMlModelDetailsPath,
|
||||
getPipelineDetailsPath,
|
||||
getServiceDetailsPath,
|
||||
getTableDetailsPath,
|
||||
getTagsDetailsPath,
|
||||
getTopicDetailsPath,
|
||||
} from '../constants/constants';
|
||||
import { AssetsType, EntityType, FqnPart } from '../enums/entity.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
@ -58,6 +70,9 @@ import {
|
||||
getPartialNameFromTableFQN,
|
||||
getTableFQNFromColumnFQN,
|
||||
} from './CommonUtils';
|
||||
import { getContainerDetailPath } from './ContainerDetailUtils';
|
||||
import Fqn from './Fqn';
|
||||
import { getGlossaryPath } from './RouterUtils';
|
||||
import {
|
||||
getDataTypeString,
|
||||
getTierFromTableTags,
|
||||
@ -884,3 +899,151 @@ export const getEntityReferenceListFromEntities = <
|
||||
|
||||
return entities.map((entity) => getEntityReferenceFromEntity(entity, type));
|
||||
};
|
||||
|
||||
export const getBreadcrumbForTable = (
|
||||
entity: Table,
|
||||
includeCurrent = false
|
||||
) => {
|
||||
const { service, database, databaseSchema } = entity;
|
||||
|
||||
return [
|
||||
{
|
||||
name: getEntityName(service),
|
||||
url: service?.name
|
||||
? getServiceDetailsPath(
|
||||
service?.name,
|
||||
ServiceCategory.DATABASE_SERVICES
|
||||
)
|
||||
: '',
|
||||
},
|
||||
{
|
||||
name: getEntityName(database),
|
||||
url: getDatabaseDetailsPath(database?.fullyQualifiedName ?? ''),
|
||||
},
|
||||
{
|
||||
name: getEntityName(databaseSchema),
|
||||
url: getDatabaseSchemaDetailsPath(
|
||||
databaseSchema?.fullyQualifiedName ?? ''
|
||||
),
|
||||
},
|
||||
...(includeCurrent
|
||||
? [
|
||||
{
|
||||
name: getEntityName(entity),
|
||||
url: '#',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
};
|
||||
|
||||
export const getBreadcrumbForEntitiesWithServiceOnly = (
|
||||
entity: EntityWithServices,
|
||||
includeCurrent = false
|
||||
) => {
|
||||
const { service } = entity;
|
||||
const serviceType =
|
||||
service?.type === 'objectStoreService'
|
||||
? ServiceCategory.STORAGE_SERVICES
|
||||
: service?.type;
|
||||
|
||||
return [
|
||||
{
|
||||
name: getEntityName(service),
|
||||
url: service?.name
|
||||
? getServiceDetailsPath(service?.name, serviceType)
|
||||
: '',
|
||||
},
|
||||
...(includeCurrent
|
||||
? [
|
||||
{
|
||||
name: getEntityName(entity),
|
||||
url: '#',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
};
|
||||
|
||||
export const getEntityBreadcrumbs = (
|
||||
entity: SearchedDataProps['data'][number]['_source'],
|
||||
entityType?: EntityType,
|
||||
includeCurrent = false
|
||||
) => {
|
||||
switch (entityType) {
|
||||
case EntityType.TABLE:
|
||||
return getBreadcrumbForTable(entity as Table, includeCurrent);
|
||||
case EntityType.GLOSSARY:
|
||||
case EntityType.GLOSSARY_TERM:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const glossary = (entity as GlossaryTerm).glossary;
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const fqnList = Fqn.split((entity as GlossaryTerm).fullyQualifiedName);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const tree = fqnList.slice(1, fqnList.length - 1);
|
||||
|
||||
return [
|
||||
{
|
||||
name: glossary.fullyQualifiedName,
|
||||
url: getGlossaryPath(glossary.fullyQualifiedName),
|
||||
},
|
||||
...tree.map((fqn, index, source) => ({
|
||||
name: fqn,
|
||||
url: getGlossaryPath(
|
||||
`${glossary.fullyQualifiedName}.${source
|
||||
.slice(0, index + 1)
|
||||
.join('.')}`
|
||||
),
|
||||
})),
|
||||
];
|
||||
case EntityType.TAG:
|
||||
return [
|
||||
{
|
||||
name: getEntityName((entity as Tag).classification),
|
||||
url: getTagsDetailsPath(
|
||||
(entity as Tag).classification?.fullyQualifiedName ?? ''
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
case EntityType.TOPIC:
|
||||
case EntityType.DASHBOARD:
|
||||
case EntityType.PIPELINE:
|
||||
case EntityType.MLMODEL:
|
||||
case EntityType.CONTAINER:
|
||||
default:
|
||||
return getBreadcrumbForEntitiesWithServiceOnly(
|
||||
entity as Topic,
|
||||
includeCurrent
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const getEntityLinkFromType = (
|
||||
fullyQualifiedName: string,
|
||||
entityType: EntityType
|
||||
) => {
|
||||
switch (entityType) {
|
||||
case EntityType.TABLE:
|
||||
return getTableDetailsPath(fullyQualifiedName);
|
||||
case EntityType.GLOSSARY:
|
||||
case EntityType.GLOSSARY_TERM:
|
||||
return getGlossaryTermDetailsPath(fullyQualifiedName);
|
||||
case EntityType.TAG:
|
||||
return getTagsDetailsPath(fullyQualifiedName);
|
||||
case EntityType.TOPIC:
|
||||
return getTopicDetailsPath(fullyQualifiedName);
|
||||
case EntityType.DASHBOARD:
|
||||
return getDashboardDetailsPath(fullyQualifiedName);
|
||||
case EntityType.PIPELINE:
|
||||
return getPipelineDetailsPath(fullyQualifiedName);
|
||||
case EntityType.MLMODEL:
|
||||
return getMlModelDetailsPath(fullyQualifiedName);
|
||||
case EntityType.CONTAINER:
|
||||
return getContainerDetailPath(fullyQualifiedName);
|
||||
case EntityType.DATABASE:
|
||||
return getDatabaseDetailsPath(fullyQualifiedName);
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReactComponent as ContainerIcon } from 'assets/svg/ic-storage.svg';
|
||||
import { AxiosError } from 'axios';
|
||||
import {
|
||||
OperationPermission,
|
||||
@ -301,6 +302,8 @@ export const serviceTypeLogo = (type: string) => {
|
||||
logo = DATABASE_DEFAULT;
|
||||
} else if (serviceTypes.mlmodelServices.includes(type)) {
|
||||
logo = ML_MODEL_DEFAULT;
|
||||
} else if (serviceTypes.storageServices.includes(type)) {
|
||||
logo = ContainerIcon;
|
||||
} else {
|
||||
logo = DEFAULT_SERVICE;
|
||||
}
|
||||
|
@ -14,7 +14,9 @@
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import { ExpandableConfig } from 'antd/lib/table/interface';
|
||||
import { ReactComponent as IconFlatFolder } from 'assets/svg/folder.svg';
|
||||
import { ReactComponent as ContainerIcon } from 'assets/svg/ic-storage.svg';
|
||||
import { ReactComponent as IconTag } from 'assets/svg/tag-grey.svg';
|
||||
import classNames from 'classnames';
|
||||
import { SourceType } from 'components/searched-data/SearchedData.interface';
|
||||
import { t } from 'i18next';
|
||||
@ -272,15 +274,15 @@ export const getEntityLink = (
|
||||
|
||||
export const getServiceIcon = (source: SourceType) => {
|
||||
if (source.entityType === EntityType.GLOSSARY_TERM) {
|
||||
return <SVGIcons alt="icon" className="m-r-xs" icon={Icons.FLAT_FOLDER} />;
|
||||
return <IconFlatFolder className="h-9" />;
|
||||
} else if (source.entityType === EntityType.TAG) {
|
||||
return <SVGIcons alt="icon" className="m-r-xs" icon={Icons.TAG} />;
|
||||
return <IconTag className="h-9" />;
|
||||
} else {
|
||||
return (
|
||||
<img
|
||||
alt="service-icon"
|
||||
className="inline h-8 p-r-xs"
|
||||
src={serviceTypeLogo((source.serviceType || source.entityType) ?? '')}
|
||||
className="inline h-9"
|
||||
src={serviceTypeLogo(source.serviceType || '')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user