feat(ui) Use new Tabs component on entity profile pages (#13640)

This commit is contained in:
Chris Collins 2025-07-08 17:58:08 -04:00 committed by GitHub
parent b9b97e4bfe
commit d505ed7c24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 133 additions and 275 deletions

View File

@ -7,7 +7,7 @@ import { Tooltip } from '@components/components/Tooltip';
import { colors } from '@src/alchemy-components/theme';
const StyledTabs = styled(AntTabs)`
const StyledTabs = styled(AntTabs)<{ $addPaddingLeft?: boolean; $hideTabsHeader: boolean }>`
flex: 1;
overflow: hidden;
@ -17,9 +17,26 @@ const StyledTabs = styled(AntTabs)`
color: ${colors.gray[600]};
}
.ant-tabs-tab + .ant-tabs-tab {
margin-left: 16px;
}
${({ $addPaddingLeft }) =>
$addPaddingLeft
? `
.ant-tabs-tab {
margin-left: 16px;
}
`
: `
.ant-tabs-tab + .ant-tabs-tab {
margin-left: 16px;
}
`}
${({ $hideTabsHeader }) =>
$hideTabsHeader &&
`
.ant-tabs-nav {
display: none;
}
`}
.ant-tabs-tab-active .ant-tabs-tab-btn {
color: ${(props) => props.theme.styles['primary-color']};
@ -43,16 +60,17 @@ const StyledTabs = styled(AntTabs)`
}
`;
const TabViewWrapper = styled.div`
const TabViewWrapper = styled.div<{ $disabled?: boolean }>`
display: flex;
align-items: center;
gap: 8px;
gap: 4px;
${({ $disabled }) => $disabled && `color: ${colors.gray[1800]};`}
`;
function TabView({ tab }: { tab: Tab }) {
return (
<Tooltip title={tab.tooltip}>
<TabViewWrapper id={tab.id}>
<TabViewWrapper id={tab.id} $disabled={tab.disabled} data-testid={tab.dataTestId}>
{tab.name}
{!!tab.count && <Pill label={`${tab.count}`} size="xs" color="violet" />}
</TabViewWrapper>
@ -68,6 +86,8 @@ export interface Tab {
onSelectTab?: () => void;
tooltip?: string;
id?: string;
dataTestId?: string;
disabled?: boolean;
}
export interface Props {
@ -78,6 +98,8 @@ export interface Props {
onUrlChange?: (url: string) => void;
defaultTab?: string;
getCurrentUrl?: () => string;
addPaddingLeft?: boolean;
hideTabsHeader?: boolean;
}
export function Tabs({
@ -88,6 +110,8 @@ export function Tabs({
onUrlChange = (url) => window.history.replaceState({}, '', url),
defaultTab,
getCurrentUrl = () => window.location.pathname,
addPaddingLeft,
hideTabsHeader,
}: Props) {
const { TabPane } = AntTabs;
@ -128,10 +152,15 @@ export function Tabs({
}
return (
<StyledTabs activeKey={selectedTab} onChange={handleTabClick}>
<StyledTabs
activeKey={selectedTab}
onChange={handleTabClick}
$addPaddingLeft={addPaddingLeft}
$hideTabsHeader={!!hideTabsHeader}
>
{tabs.map((tab) => {
return (
<TabPane tab={<TabView tab={tab} />} key={tab.key}>
<TabPane tab={<TabView tab={tab} />} key={tab.key} disabled={tab.disabled}>
{tab.component}
</TabPane>
);

View File

@ -27,7 +27,6 @@ import { getDataForEntityType } from '@app/entityV2/shared/containers/profile/ut
import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNotesSection';
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
import { useGetApplicationQuery } from '@graphql/application.generated';
@ -115,9 +114,8 @@ export class ApplicationEntity implements Entity<Application> {
},
{
name: 'Assets',
getDynamicName: (entityData, _, loading) => {
const assetCount = entityData?.children?.total;
return <TabNameWithCount name="Assets" count={assetCount} loading={loading} />;
getCount: (entityData, _, loading) => {
return !loading ? entityData?.children?.total : undefined;
},
component: ApplicationEntitiesTab,
icon: AppstoreOutlined,

View File

@ -9,6 +9,7 @@ import TabToolbar from '@app/entity/shared/components/styled/TabToolbar';
import { ANTD_GRAY } from '@app/entity/shared/constants';
import { TestResults } from '@app/entity/shared/tabs/Dataset/Governance/TestResults';
import { useGetValidationsTab } from '@app/entity/shared/tabs/Dataset/Validations/useGetValidationsTab';
import { GOVERNANCE_TAB_NAME } from '@app/entityV2/dataset/constants';
const TabTitle = styled.span`
margin-left: 4px;
@ -41,7 +42,7 @@ export const GovernanceTab = () => {
// If no tab was selected, select a default tab.
useEffect(() => {
if (!selectedTab) {
if (!selectedTab && basePath.endsWith(GOVERNANCE_TAB_NAME)) {
// Route to the default tab.
history.replace(`${basePath}/${DEFAULT_TAB}`);
}

View File

@ -22,7 +22,6 @@ import { EntityActionItem } from '@app/entityV2/shared/entity/EntityActions';
import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNotesSection';
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
import { useGetApplicationQuery } from '@graphql/application.generated';
@ -106,9 +105,8 @@ export class ApplicationEntity implements Entity<Application> {
},
{
name: 'Assets',
getDynamicName: (entityData, _, loading) => {
const assetCount = entityData?.children?.total;
return <TabNameWithCount name="Assets" count={assetCount} loading={loading} />;
getCount: (entityData, _, loading) => {
return !loading ? entityData?.children?.total : undefined;
},
component: ApplicationEntitiesTab,
icon: AppstoreOutlined,

View File

@ -39,7 +39,6 @@ import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/Docume
import { EmbedTab } from '@app/entityV2/shared/tabs/Embed/EmbedTab';
import { ChartDashboardsTab } from '@app/entityV2/shared/tabs/Entity/ChartDashboardsTab';
import { InputFieldsTab } from '@app/entityV2/shared/tabs/Entity/InputFieldsTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { IncidentTab } from '@app/entityV2/shared/tabs/Incident/IncidentTab';
import { LineageTab } from '@app/entityV2/shared/tabs/Lineage/LineageTab';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
@ -197,9 +196,8 @@ export class ChartEntity implements Entity<Chart> {
},
{
name: 'Incidents',
getDynamicName: (_, chart, loading) => {
const activeIncidentCount = chart?.chart?.activeIncidents?.total;
return <TabNameWithCount name="Incidents" count={activeIncidentCount} loading={loading} />;
getCount: (_, chart, loading) => {
return !loading ? chart?.chart?.activeIncidents?.total : undefined;
},
icon: WarningOutlined,
component: IncidentTab,

View File

@ -40,7 +40,6 @@ import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/Docume
import { EmbedTab } from '@app/entityV2/shared/tabs/Embed/EmbedTab';
import { DashboardChartsTab } from '@app/entityV2/shared/tabs/Entity/DashboardChartsTab';
import { DashboardDatasetsTab } from '@app/entityV2/shared/tabs/Entity/DashboardDatasetsTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { IncidentTab } from '@app/entityV2/shared/tabs/Incident/IncidentTab';
import { LineageTab } from '@app/entityV2/shared/tabs/Lineage/LineageTab';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
@ -193,9 +192,8 @@ export class DashboardEntity implements Entity<Dashboard> {
name: 'Incidents',
icon: WarningOutlined,
component: IncidentTab,
getDynamicName: (_, dashboard, loading) => {
const activeIncidentCount = dashboard?.dashboard?.activeIncidents?.total;
return <TabNameWithCount name="Incidents" count={activeIncidentCount} loading={loading} />;
getCount: (_, dashboard) => {
return dashboard?.dashboard?.activeIncidents?.total;
},
},
]}

View File

@ -22,7 +22,6 @@ import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNote
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import { DataFlowJobsTab } from '@app/entityV2/shared/tabs/Entity/DataFlowJobsTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { IncidentTab } from '@app/entityV2/shared/tabs/Incident/IncidentTab';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
import { getDataProduct, isOutputPort } from '@app/entityV2/shared/utils';
@ -110,9 +109,8 @@ export class DataFlowEntity implements Entity<DataFlow> {
name: 'Incidents',
icon: WarningCircle,
component: IncidentTab,
getDynamicName: (_, dataFlow, loading) => {
const activeIncidentCount = dataFlow?.dataFlow?.activeIncidents?.total;
return <TabNameWithCount name="Incidents" count={activeIncidentCount} loading={loading} />;
getCount: (_, dataFlow) => {
return dataFlow?.dataFlow?.activeIncidents?.total;
},
},
{

View File

@ -27,7 +27,6 @@ import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNote
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import { DataJobFlowTab } from '@app/entityV2/shared/tabs/Entity/DataJobFlowTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { IncidentTab } from '@app/entityV2/shared/tabs/Incident/IncidentTab';
import { LineageTab } from '@app/entityV2/shared/tabs/Lineage/LineageTab';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
@ -142,9 +141,8 @@ export class DataJobEntity implements Entity<DataJob> {
name: 'Incidents',
icon: WarningCircle,
component: IncidentTab,
getDynamicName: (_, dataJob, loading) => {
const activeIncidentCount = dataJob?.dataJob?.activeIncidents?.total;
return <TabNameWithCount name="Incidents" count={activeIncidentCount} loading={loading} />;
getCount: (_, dataJob) => {
return dataJob?.dataJob?.activeIncidents?.total;
},
},
]}

View File

@ -30,7 +30,6 @@ import { EntityActionItem } from '@app/entityV2/shared/entity/EntityActions';
import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNotesSection';
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
import { useGetDataProductQuery } from '@graphql/dataProduct.generated';
@ -118,9 +117,8 @@ export class DataProductEntity implements Entity<DataProduct> {
},
{
name: 'Assets',
getDynamicName: (entityData, _, loading) => {
const assetCount = entityData?.entities?.total;
return <TabNameWithCount name="Assets" count={assetCount} loading={loading} />;
getCount: (entityData, _) => {
return entityData?.entities?.total;
},
component: DataProductEntitiesTab,
icon: AppstoreOutlined,

View File

@ -17,9 +17,11 @@ import * as React from 'react';
import { GenericEntityProperties } from '@app/entity/shared/types';
import { Entity, EntityCapabilityType, IconStyleType, PreviewType } from '@app/entityV2/Entity';
import { GOVERNANCE_TAB_NAME, QUALITY_TAB_NAME } from '@app/entityV2/dataset/constants';
import { Preview } from '@app/entityV2/dataset/preview/Preview';
import { OperationsTab } from '@app/entityV2/dataset/profile/OperationsTab';
import { DatasetStatsSummarySubHeader } from '@app/entityV2/dataset/profile/stats/stats/DatasetStatsSummarySubHeader';
import { useGetColumnTabCount } from '@app/entityV2/dataset/profile/useGetColumnTabCount';
import { EntityMenuItems } from '@app/entityV2/shared/EntityDropdown/EntityMenuActions';
import { SubType, TYPE_ICON_CLASS_NAME } from '@app/entityV2/shared/components/subtypes';
import { EntityProfile } from '@app/entityV2/shared/containers/profile/EntityProfile';
@ -49,8 +51,6 @@ import { AcrylValidationsTab } from '@app/entityV2/shared/tabs/Dataset/Validatio
import ViewDefinitionTab from '@app/entityV2/shared/tabs/Dataset/View/ViewDefinitionTab';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import { EmbedTab } from '@app/entityV2/shared/tabs/Embed/EmbedTab';
import ColumnTabNameHeader from '@app/entityV2/shared/tabs/Entity/ColumnTabNameHeader';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { IncidentTab } from '@app/entityV2/shared/tabs/Incident/IncidentTab';
import { LineageTab } from '@app/entityV2/shared/tabs/Lineage/LineageTab';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
@ -156,7 +156,7 @@ export class DatasetEntity implements Entity<Dataset> {
name: 'Columns',
component: SchemaTab,
icon: LayoutOutlined,
getDynamicName: ColumnTabNameHeader,
getCount: useGetColumnTabCount,
},
{
name: 'View Definition',
@ -203,12 +203,12 @@ export class DatasetEntity implements Entity<Dataset> {
name: 'Properties',
component: PropertiesTab,
icon: UnorderedListOutlined,
getDynamicName: (_, dataset: GetDatasetQuery, loading) => {
getCount: (_, dataset: GetDatasetQuery) => {
const customPropertiesCount = dataset?.dataset?.properties?.customProperties?.length || 0;
const structuredPropertiesCount =
dataset?.dataset?.structuredProperties?.properties?.length || 0;
const propertiesCount = customPropertiesCount + structuredPropertiesCount;
return <TabNameWithCount name="Properties" count={propertiesCount} loading={loading} />;
return propertiesCount;
},
},
{
@ -234,12 +234,12 @@ export class DatasetEntity implements Entity<Dataset> {
},
},
{
name: 'Quality',
name: QUALITY_TAB_NAME,
component: AcrylValidationsTab, // Use SaaS specific Validations Tab.
icon: CheckCircleOutlined,
},
{
name: 'Governance',
name: GOVERNANCE_TAB_NAME,
icon: () => (
<span
style={{
@ -268,9 +268,8 @@ export class DatasetEntity implements Entity<Dataset> {
name: 'Incidents',
icon: WarningOutlined,
component: IncidentTab,
getDynamicName: (_, dataset, loading) => {
const activeIncidentCount = dataset?.dataset?.activeIncidents?.total;
return <TabNameWithCount name="Incidents" count={activeIncidentCount} loading={loading} />;
getCount: (_, dataset) => {
return dataset?.dataset?.activeIncidents?.total;
},
},
]}

View File

@ -0,0 +1,2 @@
export const GOVERNANCE_TAB_NAME = 'Governance';
export const QUALITY_TAB_NAME = 'Quality';

View File

@ -0,0 +1,8 @@
import { useGetEntityWithSchema } from '@app/entityV2/shared/tabs/Dataset/Schema/useGetEntitySchema';
export const useGetColumnTabCount = () => {
const { entityWithSchema, loading } = useGetEntityWithSchema();
const fieldsCount = entityWithSchema?.schemaMetadata?.fields?.length || 0;
return !loading ? fieldsCount : undefined;
};

View File

@ -22,7 +22,6 @@ import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNote
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { SUMMARY_TAB_ICON } from '@app/entityV2/shared/summary/HeaderComponents';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
import { useGetDomainQuery } from '@graphql/domain.generated';
@ -111,9 +110,8 @@ export class DomainEntity implements Entity<Domain> {
{
id: EntityProfileTab.DOMAIN_ENTITIES_TAB,
name: 'Assets',
getDynamicName: (entityData, _, loading) => {
const assetCount = entityData?.entities?.total;
return <TabNameWithCount name="Assets" count={assetCount} loading={loading} />;
getCount: (entityData, _) => {
return entityData?.entities?.total;
},
component: DomainEntitiesTab,
icon: AppstoreOutlined,
@ -127,9 +125,8 @@ export class DomainEntity implements Entity<Domain> {
{
id: EntityProfileTab.DATA_PRODUCTS_TAB,
name: 'Data Products',
getDynamicName: (entityData, _, loading) => {
const dataProductsCount = entityData?.dataProducts?.total;
return <TabNameWithCount name="Data Products" count={dataProductsCount} loading={loading} />;
getCount: (entityData, _) => {
return entityData?.dataProducts?.total;
},
component: DataProductsTab,
icon: FileDoneOutlined,

View File

@ -4,11 +4,11 @@ import * as React from 'react';
import { GenericEntityProperties } from '@app/entity/shared/types';
import { Entity, EntityCapabilityType, IconStyleType, PreviewType } from '@app/entityV2/Entity';
import GlossaryRelatedAssetsTabHeader from '@app/entityV2/glossaryTerm/GlossaryRelatedAssetsTabHeader';
import { Preview } from '@app/entityV2/glossaryTerm/preview/Preview';
import GlossaryRelatedEntity from '@app/entityV2/glossaryTerm/profile/GlossaryRelatedEntity';
import GlossayRelatedTerms from '@app/entityV2/glossaryTerm/profile/GlossaryRelatedTerms';
import { RelatedTermTypes } from '@app/entityV2/glossaryTerm/profile/GlossaryRelatedTermsResult';
import useGlossaryRelatedAssetsTabCount from '@app/entityV2/glossaryTerm/profile/useGlossaryRelatedAssetsTabCount';
import { EntityMenuItems } from '@app/entityV2/shared/EntityDropdown/EntityMenuActions';
import { TYPE_ICON_CLASS_NAME } from '@app/entityV2/shared/components/subtypes';
import { EntityProfile } from '@app/entityV2/shared/containers/profile/EntityProfile';
@ -24,7 +24,6 @@ import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNote
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { SchemaTab } from '@app/entityV2/shared/tabs/Dataset/Schema/SchemaTab';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
import { FetchedEntity } from '@app/lineage/types';
@ -110,7 +109,7 @@ export class GlossaryTermEntity implements Entity<GlossaryTerm> {
},
{
name: 'Related Assets',
getDynamicName: GlossaryRelatedAssetsTabHeader,
getCount: useGlossaryRelatedAssetsTabCount,
component: GlossaryRelatedEntity,
icon: AppstoreOutlined,
},
@ -130,13 +129,11 @@ export class GlossaryTermEntity implements Entity<GlossaryTerm> {
},
{
name: 'Related Terms',
getDynamicName: (entityData, _, loading) => {
getCount: (entityData, _, loading) => {
const totalRelatedTerms = Object.keys(RelatedTermTypes).reduce((acc, curr) => {
return acc + (entityData?.[curr]?.total || 0);
}, 0);
return (
<TabNameWithCount name="Related Terms" count={totalRelatedTerms} loading={loading} />
);
return !loading ? totalRelatedTerms : undefined;
},
component: GlossayRelatedTerms,
icon: () => <BookmarkSimple style={{ marginRight: 6 }} />,

View File

@ -1,21 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { Pill } from '@src/alchemy-components';
import { useEntityData } from '@src/app/entity/shared/EntityContext';
import { formatNumber } from '@src/app/shared/formatNumber';
import { useGetSearchResultsForMultipleQuery } from '@src/graphql/search.generated';
const Styled = styled.div`
display: flex;
align-items: center;
`;
const TabName = styled.div`
padding-right: 4px;
`;
function GlossaryRelatedAssetsTabHeader() {
export default function useGlossaryRelatedAssetsTabCount() {
const { entityData } = useEntityData();
// To get the number of related assets
@ -44,12 +30,5 @@ function GlossaryRelatedAssetsTabHeader() {
fetchPolicy: 'cache-and-network',
});
return (
<Styled>
<TabName>Related Assets</TabName>
<Pill label={formatNumber(data?.searchAcrossEntities?.total || 0)} />
</Styled>
);
return data?.searchAcrossEntities?.total || 0;
}
export default GlossaryRelatedAssetsTabHeader;

View File

@ -21,7 +21,6 @@ import { getDataForEntityType } from '@app/entityV2/shared/containers/profile/ut
import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNotesSection';
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { LineageTab } from '@app/entityV2/shared/tabs/Lineage/LineageTab';
import { FeatureTableTab } from '@app/entityV2/shared/tabs/ML/MlFeatureFeatureTableTab';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
@ -128,9 +127,8 @@ export class MLFeatureEntity implements Entity<MlFeature> {
name: 'Incidents',
icon: WarningCircle,
component: IncidentTab,
getDynamicName: (_, mlFeature, loading) => {
const activeIncidentCount = mlFeature?.mlFeature?.activeIncidents?.total;
return <TabNameWithCount name="Incidents" count={activeIncidentCount} loading={loading} />;
getCount: (_, mlFeature) => {
return mlFeature?.mlFeature?.activeIncidents?.total;
},
},
]}

View File

@ -68,8 +68,7 @@ export default function SourcesView() {
</ViewRawButtonContainer>
</div>
<List
style={{ marginTop: '24px', padding: '16px 32px' }}
bordered
style={{ padding: '0 32px 16px 32px' }}
dataSource={sources}
header={<Typography.Title level={3}>Sources</Typography.Title>}
renderItem={(item) => (

View File

@ -24,7 +24,6 @@ import { getDataForEntityType } from '@app/entityV2/shared/containers/profile/ut
import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNotesSection';
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
import { IncidentTab } from '@app/entityV2/shared/tabs/Incident/IncidentTab';
import { LineageTab } from '@app/entityV2/shared/tabs/Lineage/LineageTab';
import { PropertiesTab } from '@app/entityV2/shared/tabs/Properties/PropertiesTab';
@ -135,9 +134,8 @@ export class MLModelEntity implements Entity<MlModel> {
name: 'Incidents',
icon: WarningOutlined,
component: IncidentTab,
getDynamicName: (_, mlModel, loading) => {
const activeIncidentCount = mlModel?.mlModel?.activeIncidents?.total;
return <TabNameWithCount name="Incidents" count={activeIncidentCount} loading={loading} />;
getCount: (_, mlModel) => {
return mlModel?.mlModel?.activeIncidents?.total;
},
},
]}

View File

@ -30,13 +30,7 @@ import {
import { EntityActionItem } from '@app/entityV2/shared/entity/EntityActions';
import NonExistentEntityPage from '@app/entityV2/shared/entity/NonExistentEntityPage';
import DynamicTab from '@app/entityV2/shared/tabs/Entity/weaklyTypedAspects/DynamicTab';
import {
EntitySidebarSection,
EntitySidebarTab,
EntityTab,
TabContextType,
TabRenderType,
} from '@app/entityV2/shared/types';
import { EntitySidebarSection, EntitySidebarTab, EntityTab, TabContextType } from '@app/entityV2/shared/types';
import { useIsSeparateSiblingsMode } from '@app/entityV2/shared/useIsSeparateSiblingsMode';
import VersionsDrawer from '@app/entityV2/shared/versioning/VersionsDrawer';
import LineageExplorer from '@app/lineage/LineageExplorer';
@ -155,6 +149,7 @@ const Body = styled.div<{ $isShowNavBarRedesign?: boolean }>`
`;
const BodyContent = styled.div<{ $isShowNavBarRedesign?: boolean }>`
padding-top: 12px;
background-color: #ffffff;
border-radius: ${(props) =>
props.$isShowNavBarRedesign ? props.theme.styles['border-radius-navbar-redesign'] : '8px'};
@ -169,15 +164,6 @@ const BodyContent = styled.div<{ $isShowNavBarRedesign?: boolean }>`
overflow: hidden;
`;
const TabsWrapper = styled.div``;
const TabContent = styled.div`
display: flex;
flex-direction: column;
flex: 1;
overflow: auto;
`;
const StyledAlert = styled(Alert)`
box-sizing: border-box;
position: fixed;
@ -406,20 +392,7 @@ export const EntityProfile = <T, U>({
)}
<Body $isShowNavBarRedesign={isShowNavBarRedesign}>
<BodyContent $isShowNavBarRedesign={isShowNavBarRedesign}>
{!isTabFullsize && (
<TabsWrapper>
<EntityTabs tabs={visibleTabs} selectedTab={routedTab} />
</TabsWrapper>
)}
<TabContent>
{routedTab && (
<routedTab.component
properties={routedTab.properties}
contextType={TabContextType.PROFILE}
renderType={TabRenderType.DEFAULT}
/>
)}
</TabContent>
<EntityTabs tabs={visibleTabs} selectedTab={routedTab} />
</BodyContent>
</Body>
</HeaderAndTabsFlex>

View File

@ -1,82 +1,31 @@
import { Tabs } from 'antd';
import React, { useEffect } from 'react';
import { Tabs } from '@components';
import React, { useContext, useEffect } from 'react';
import styled from 'styled-components/macro';
import { Tab } from '@components/components/Tabs/Tabs';
import { useBaseEntity, useEntityData, useRouteToTab } from '@app/entity/shared/EntityContext';
import { EntityTab } from '@app/entityV2/shared/types';
import { EntityTab, TabContextType, TabRenderType } from '@app/entityV2/shared/types';
import TabFullsizedContext from '@app/shared/TabFullsizedContext';
type Props = {
tabs: EntityTab[];
selectedTab?: EntityTab;
};
const Header = styled.div`
const TabContent = styled.div`
display: flex;
align-items: center;
flex-direction: column;
flex: 1;
overflow: auto;
height: 100%;
`;
const UnborderedTabs = styled(Tabs)`
width: 100%;
padding: 12px 14px 10px 12px;
&&& .ant-tabs-nav {
margin-bottom: 0;
&::before {
border-bottom: none;
}
}
&&& .ant-tabs-nav-wrap {
background-color: #f6f7fa;
border-radius: 4px;
gap: 3px;
padding: 2px;
}
&&& .ant-tabs-tab {
padding: 10px 16px;
margin: 0;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
}
&&& .ant-tabs-tab-active {
background-color: ${(p) => p.theme.styles['primary-color']};
}
&&& .ant-tabs-tab-active .ant-tabs-tab-btn {
color: #ffffff;
&:hover {
color: #ffffff;
}
}
&&& .ant-tabs-ink-bar {
height: 0px;
}
&&& .ant-tabs-content-holder {
display: none;
}
background-color: #ffffff;
`;
const Tab = styled(Tabs.TabPane)`
font-size: 10px;
line-height: normal;
font-weight: 400;
`;
const tabIconStyle = { fontSize: 14, marginRight: 6 };
export const EntityTabs = <T,>({ tabs, selectedTab }: Props) => {
const { entityData, loading } = useEntityData();
const routeToTab = useRouteToTab();
const baseEntity = useBaseEntity<T>();
const { isTabFullsize } = useContext(TabFullsizedContext);
const enabledTabs = tabs.filter((tab) => tab.display?.enabled(entityData, baseEntity));
@ -86,42 +35,30 @@ export const EntityTabs = <T,>({ tabs, selectedTab }: Props) => {
}
}, [loading, enabledTabs, selectedTab, routeToTab]);
const finalTabs: Tab[] = tabs.map((t) => ({
key: t.name,
name: t.name,
component: (
<TabContent>
<t.component
properties={t.properties}
contextType={TabContextType.PROFILE}
renderType={TabRenderType.DEFAULT}
/>
</TabContent>
),
disabled: !t.display?.enabled(entityData, baseEntity),
dataTestId: `${t.name}-entity-tab-header`,
count: t.getCount?.(entityData, baseEntity, loading),
}));
return (
<UnborderedTabs
animated={false}
activeKey={selectedTab?.name || ''}
size="large"
onTabClick={(tab: string) => routeToTab({ tabName: tab })}
>
{tabs.map((tab) => {
const TabIcon = tab.icon;
const tabName = (tab.getDynamicName && tab.getDynamicName(entityData, baseEntity, loading)) || tab.name;
if (!tab.display?.enabled(entityData, baseEntity)) {
return (
<Tab
tab={
<span data-testid={`${tab.name}-entity-tab-header`}>
{TabIcon && <TabIcon style={tabIconStyle} />}
{tab.name}
</span>
}
key={tab.name}
disabled
/>
);
}
return (
<Tab
tab={
<Header data-testid={`${tab.name}-entity-tab-header`}>
{TabIcon && <TabIcon style={tabIconStyle} />}
{tabName}
</Header>
}
key={tab.name}
/>
);
})}
</UnborderedTabs>
<Tabs
onChange={(t) => routeToTab({ tabName: t })}
selectedTab={selectedTab?.name}
tabs={finalTabs}
hideTabsHeader={isTabFullsize}
addPaddingLeft
/>
);
};

View File

@ -4,6 +4,7 @@ import { useHistory, useLocation } from 'react-router';
import styled from 'styled-components';
import { useEntityData } from '@app/entity/shared/EntityContext';
import { QUALITY_TAB_NAME } from '@app/entityV2/dataset/constants';
import { AcrylAssertionList } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/AcrylAssertionList';
import { AcrylAssertionSummaryTab } from '@app/entityV2/shared/tabs/Dataset/Validations/AssertionList/Summary/AcrylAssertionSummaryTab';
import { DataContractTab } from '@app/entityV2/shared/tabs/Dataset/Validations/contract/DataContractTab';
@ -67,7 +68,7 @@ export const AcrylValidationsTab = () => {
// If no tab was selected, select a default tab.
useEffect(() => {
if (!selectedTab) {
if (!selectedTab && basePath.endsWith(QUALITY_TAB_NAME)) {
// Route to the default tab.
history.replace(`${basePath}/${DEFAULT_TAB}?${SEPARATE_SIBLINGS_URL_PARAM}=${isHideSiblingMode}`);
}

View File

@ -1,13 +0,0 @@
import React from 'react';
import { useGetEntityWithSchema } from '@app/entityV2/shared/tabs/Dataset/Schema/useGetEntitySchema';
import TabNameWithCount from '@app/entityV2/shared/tabs/Entity/TabNameWithCount';
const ColumnTabNameHeader = () => {
const { entityWithSchema, loading } = useGetEntityWithSchema();
const fieldsCount = entityWithSchema?.schemaMetadata?.fields?.length || 0;
return <TabNameWithCount name="Columns" count={fieldsCount} loading={loading} />;
};
export default ColumnTabNameHeader;

View File

@ -1,30 +0,0 @@
import React from 'react';
import styled from 'styled-components';
import { Pill } from '@src/alchemy-components';
import { formatNumber } from '@src/app/shared/formatNumber';
const Container = styled.div`
display: flex;
align-items: center;
`;
const TabName = styled.div`
margin-right: 8px;
`;
type Props = {
name: string;
count: number;
loading: boolean;
};
const TabNameWithCount = ({ name, count = 0, loading }: Props) => {
return (
<Container>
<TabName>{name}</TabName>
{!loading && <Pill label={formatNumber(count)} size="sm" />}
</Container>
);
};
export default TabNameWithCount;

View File

@ -40,7 +40,7 @@ const StyledList = styled(List)`
margin-left: -20px;
border-bottom: none;
padding-bottom: 0px;
padding-top: 15px;
padding-top: 0px;
}
` as typeof List;
@ -106,7 +106,6 @@ export const EntityList = ({
<>
<ScrollWrapper>
<StyledList
bordered
dataSource={entities}
header={title || `${entities.length || 0} ${entityRegistry.getCollectionName(type)}`}
renderItem={(item) => (

View File

@ -1,5 +1,3 @@
import { ReactElement } from 'react';
export enum TabRenderType {
/**
* A default, full screen tab.
@ -63,7 +61,7 @@ export type EntityTab = {
};
properties?: any;
id?: string;
getDynamicName?: (GenericEntityProperties, T, loading: boolean) => ReactElement;
getCount?: (GenericEntityProperties, T, loading: boolean) => number | undefined;
supportsFullsize?: boolean; // As per TabFullsizedContext
};

View File

@ -82,7 +82,7 @@ describe("glossaryTerm", () => {
it("can apply filters on related entities", () => {
cy.clickOptionWithText("CypressNode");
cy.clickOptionWithText("GlossaryNewTerm");
cy.clickOptionWithSpecificClass(".anticon.anticon-appstore", 0);
cy.clickTextOptionWithClass(".ant-tabs-tab", "Related Assets");
elementVisibility();
cy.clickOptionWithSpecificClass(".anticon-filter", 0);
cy.waitTextVisible("Filter");
@ -102,7 +102,7 @@ describe("glossaryTerm", () => {
it("can search related entities by a specific tag using advanced search", () => {
cy.clickOptionWithText("CypressNode");
cy.clickOptionWithText("GlossaryNewTerm");
cy.clickOptionWithSpecificClass(".anticon.anticon-appstore", 0);
cy.clickTextOptionWithClass(".ant-tabs-tab", "Related Assets");
elementVisibility();
applyAdvancedSearchFilter("Tag", "Cypress");
elementVisibility();