mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-25 08:58:26 +00:00
feat(ui): sortable domain list (#6736)
This commit is contained in:
parent
86ee0e58de
commit
b0a82ca7f4
65
datahub-web-react/src/app/domain/DomainItemMenu.tsx
Normal file
65
datahub-web-react/src/app/domain/DomainItemMenu.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { Dropdown, Menu, message, Modal } from 'antd';
|
||||
import { EntityType } from '../../types.generated';
|
||||
import { useEntityRegistry } from '../useEntityRegistry';
|
||||
import { useDeleteDomainMutation } from '../../graphql/domain.generated';
|
||||
import { MenuIcon } from '../entity/shared/EntityDropdown/EntityDropdown';
|
||||
|
||||
type Props = {
|
||||
urn: string;
|
||||
name: string;
|
||||
onDelete?: () => void;
|
||||
};
|
||||
|
||||
export default function DomainItemMenu({ name, urn, onDelete }: Props) {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const [deleteDomainMutation] = useDeleteDomainMutation();
|
||||
|
||||
const deleteDomain = () => {
|
||||
deleteDomainMutation({
|
||||
variables: {
|
||||
urn,
|
||||
},
|
||||
})
|
||||
.then(({ errors }) => {
|
||||
if (!errors) {
|
||||
message.success('Deleted Domain!');
|
||||
onDelete?.();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.destroy();
|
||||
message.error({ content: `Failed to delete Domain!: An unknown error occurred.`, duration: 3 });
|
||||
});
|
||||
};
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
Modal.confirm({
|
||||
title: `Delete Domain '${name}'`,
|
||||
content: `Are you sure you want to remove this ${entityRegistry.getEntityName(EntityType.Domain)}?`,
|
||||
onOk() {
|
||||
deleteDomain();
|
||||
},
|
||||
onCancel() {},
|
||||
okText: 'Yes',
|
||||
maskClosable: true,
|
||||
closable: true,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item onClick={onConfirmDelete} key="delete">
|
||||
<DeleteOutlined /> Delete
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<MenuIcon fontSize={20} />
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
65
datahub-web-react/src/app/domain/DomainListColumns.tsx
Normal file
65
datahub-web-react/src/app/domain/DomainListColumns.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Tag, Tooltip, Typography } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { Maybe, Ownership } from '../../types.generated';
|
||||
import { useEntityRegistry } from '../useEntityRegistry';
|
||||
import AvatarsGroup from '../shared/avatar/AvatarsGroup';
|
||||
import DomainItemMenu from './DomainItemMenu';
|
||||
|
||||
interface DomainEntry {
|
||||
name: string;
|
||||
entities: string;
|
||||
urn: string;
|
||||
ownership?: Maybe<Ownership>;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const AvatarGroupWrapper = styled.div`
|
||||
margin-right: 10px;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const DomainNameContainer = styled.div`
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
display: inline;
|
||||
`;
|
||||
|
||||
export function DomainListMenuColumn(handleDelete: (urn: string) => void) {
|
||||
return (record: DomainEntry) => (
|
||||
<DomainItemMenu name={record.name} urn={record.urn} onDelete={() => handleDelete(record.urn)} />
|
||||
);
|
||||
}
|
||||
|
||||
export function DomainNameColumn(logoIcon: JSX.Element) {
|
||||
return (record: DomainEntry) => (
|
||||
<Link to={record.url}>
|
||||
{logoIcon}
|
||||
<DomainNameContainer>
|
||||
<Typography.Text>{record.name}</Typography.Text>
|
||||
</DomainNameContainer>
|
||||
<Tooltip title={`There are ${record.entities} entities in this domain.`}>
|
||||
<Tag>{record.entities} entities</Tag>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function DomainOwnersColumn(ownership: Maybe<Ownership>) {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
|
||||
if (!ownership) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { owners } = ownership;
|
||||
if (!owners || owners.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<AvatarGroupWrapper>
|
||||
<AvatarsGroup size={24} owners={owners} entityRegistry={entityRegistry} maxCount={4} />
|
||||
</AvatarGroupWrapper>
|
||||
);
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { DeleteOutlined, MoreOutlined } from '@ant-design/icons';
|
||||
import { Col, Dropdown, List, Menu, message, Modal, Row, Tag, Tooltip, Typography } from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { IconStyleType } from '../entity/Entity';
|
||||
import { Domain, EntityType } from '../../types.generated';
|
||||
import { useEntityRegistry } from '../useEntityRegistry';
|
||||
import AvatarsGroup from '../shared/avatar/AvatarsGroup';
|
||||
import { getElasticCappedTotalValueText } from '../entity/shared/constants';
|
||||
import { useDeleteDomainMutation } from '../../graphql/domain.generated';
|
||||
|
||||
const DomainItemContainer = styled(Row)`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const DomainStartContainer = styled(Col)`
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const DomainEndContainer = styled(Col)`
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const DomainHeaderContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const DomainNameContainer = styled.div`
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
`;
|
||||
|
||||
const AvatarGroupWrapper = styled.div`
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
const MenuIcon = styled(MoreOutlined)<{ fontSize?: number }>`
|
||||
font-size: ${(props) => props.fontSize || '24'}px;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
domain: Domain;
|
||||
onDelete?: () => void;
|
||||
};
|
||||
|
||||
export default function DomainListItem({ domain, onDelete }: Props) {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const displayName = entityRegistry.getDisplayName(EntityType.Domain, domain);
|
||||
const logoIcon = entityRegistry.getIcon(EntityType.Domain, 12, IconStyleType.ACCENT);
|
||||
const owners = domain.ownership?.owners;
|
||||
const totalEntitiesText = getElasticCappedTotalValueText(domain.entities?.total || 0);
|
||||
const [deleteDomainMutation] = useDeleteDomainMutation();
|
||||
|
||||
const deleteDomain = () => {
|
||||
deleteDomainMutation({
|
||||
variables: {
|
||||
urn: domain.urn,
|
||||
},
|
||||
})
|
||||
.then(({ errors }) => {
|
||||
if (!errors) {
|
||||
message.success('Deleted Domain!');
|
||||
onDelete?.();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.destroy();
|
||||
message.error({ content: `Failed to delee Domain!: An unknown error occurred.`, duration: 3 });
|
||||
});
|
||||
};
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
Modal.confirm({
|
||||
title: `Delete Domain '${displayName}'`,
|
||||
content: `Are you sure you want to remove this ${entityRegistry.getEntityName(EntityType.Domain)}?`,
|
||||
onOk() {
|
||||
deleteDomain();
|
||||
},
|
||||
onCancel() {},
|
||||
okText: 'Yes',
|
||||
maskClosable: true,
|
||||
closable: true,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<List.Item data-testid={domain.urn}>
|
||||
<DomainItemContainer>
|
||||
<DomainStartContainer>
|
||||
<Link to={entityRegistry.getEntityUrl(EntityType.Domain, domain.urn)}>
|
||||
<DomainHeaderContainer>
|
||||
{logoIcon}
|
||||
<DomainNameContainer>
|
||||
<Typography.Text>{displayName}</Typography.Text>
|
||||
</DomainNameContainer>
|
||||
<Tooltip title={`There are ${totalEntitiesText} entities in this domain.`}>
|
||||
<Tag>{totalEntitiesText} entities</Tag>
|
||||
</Tooltip>
|
||||
</DomainHeaderContainer>
|
||||
</Link>
|
||||
</DomainStartContainer>
|
||||
<DomainEndContainer>
|
||||
{owners && owners.length > 0 && (
|
||||
<AvatarGroupWrapper>
|
||||
<AvatarsGroup size={24} owners={owners} entityRegistry={entityRegistry} maxCount={4} />
|
||||
</AvatarGroupWrapper>
|
||||
)}
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item onClick={onConfirmDelete}>
|
||||
<DeleteOutlined /> Delete
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<MenuIcon fontSize={20} />
|
||||
</Dropdown>
|
||||
</DomainEndContainer>
|
||||
</DomainItemContainer>
|
||||
</List.Item>
|
||||
);
|
||||
}
|
||||
@ -1,31 +1,28 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Empty, List, Pagination, Typography } from 'antd';
|
||||
import { Button, Empty, Pagination, Typography } from 'antd';
|
||||
import { useLocation } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
import * as QueryString from 'query-string';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Domain } from '../../types.generated';
|
||||
import { AlignType } from 'rc-table/lib/interface';
|
||||
import { EntityType } from '../../types.generated';
|
||||
import { useListDomainsQuery } from '../../graphql/domain.generated';
|
||||
import CreateDomainModal from './CreateDomainModal';
|
||||
import { Message } from '../shared/Message';
|
||||
import TabToolbar from '../entity/shared/components/styled/TabToolbar';
|
||||
import DomainListItem from './DomainListItem';
|
||||
import { SearchBar } from '../search/SearchBar';
|
||||
import { useEntityRegistry } from '../useEntityRegistry';
|
||||
import { scrollToTop } from '../shared/searchUtils';
|
||||
import { addToListDomainsCache, removeFromListDomainsCache } from './utils';
|
||||
import { OnboardingTour } from '../onboarding/OnboardingTour';
|
||||
import { DOMAINS_INTRO_ID, DOMAINS_CREATE_DOMAIN_ID } from '../onboarding/config/DomainsOnboardingConfig';
|
||||
import { getElasticCappedTotalValueText } from '../entity/shared/constants';
|
||||
import { StyledTable } from '../entity/shared/components/styled/StyledTable';
|
||||
import { IconStyleType } from '../entity/Entity';
|
||||
import { DomainOwnersColumn, DomainListMenuColumn, DomainNameColumn } from './DomainListColumns';
|
||||
|
||||
const DomainsContainer = styled.div``;
|
||||
|
||||
const DomainsStyledList = styled(List)`
|
||||
&&& {
|
||||
width: 100%;
|
||||
border-color: ${(props) => props.theme.styles['border-color-base']};
|
||||
}
|
||||
`;
|
||||
|
||||
const DomainsPaginationContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -85,6 +82,48 @@ export const DomainsList = () => {
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const logoIcon = entityRegistry.getIcon(EntityType.Domain, 12, IconStyleType.ACCENT);
|
||||
const allColumns = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: '',
|
||||
key: 'name',
|
||||
sorter: (sourceA, sourceB) => {
|
||||
return sourceA.name.localeCompare(sourceB.name);
|
||||
},
|
||||
render: DomainNameColumn(logoIcon),
|
||||
},
|
||||
{
|
||||
title: 'Owners',
|
||||
dataIndex: 'ownership',
|
||||
width: '10%',
|
||||
key: 'ownership',
|
||||
render: DomainOwnersColumn,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: '',
|
||||
width: '5%',
|
||||
align: 'right' as AlignType,
|
||||
key: 'menu',
|
||||
render: DomainListMenuColumn(handleDelete),
|
||||
},
|
||||
];
|
||||
|
||||
const tableData = domains.map((domain) => {
|
||||
const displayName = entityRegistry.getDisplayName(EntityType.Domain, domain);
|
||||
const totalEntitiesText = getElasticCappedTotalValueText(domain.entities?.total || 0);
|
||||
const url = entityRegistry.getEntityUrl(EntityType.Domain, domain.urn);
|
||||
|
||||
return {
|
||||
name: displayName,
|
||||
entities: totalEntitiesText,
|
||||
urn: domain.urn,
|
||||
ownership: domain.ownership,
|
||||
url,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{!data && loading && <Message type="loading" content="Loading domains..." />}
|
||||
@ -113,19 +152,12 @@ export const DomainsList = () => {
|
||||
hideRecommendations
|
||||
/>
|
||||
</TabToolbar>
|
||||
<DomainsStyledList
|
||||
bordered
|
||||
locale={{
|
||||
emptyText: <Empty description="No Domains!" image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||
}}
|
||||
dataSource={domains}
|
||||
renderItem={(item: any) => (
|
||||
<DomainListItem
|
||||
key={item.urn}
|
||||
domain={item as Domain}
|
||||
onDelete={() => handleDelete(item.urn)}
|
||||
/>
|
||||
)}
|
||||
<StyledTable
|
||||
columns={allColumns}
|
||||
dataSource={tableData}
|
||||
rowKey="urn"
|
||||
pagination={false}
|
||||
locale={{ emptyText: <Empty description="No Domains!" image={Empty.PRESENTED_IMAGE_SIMPLE} /> }}
|
||||
/>
|
||||
<DomainsPaginationContainer>
|
||||
<PaginationInfo>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user