feat(ui): supported read more in for tags on entity pages (#12569)

* supported readmore in for tags on entity pages

* missing props
This commit is contained in:
Ashish Gupta 2023-07-24 18:41:36 +05:30 committed by GitHub
parent 4a8f19a275
commit 8f956467ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 332 additions and 205 deletions

View File

@ -67,6 +67,7 @@ import {
} from './DashboardDetails.interface';
import TableDescription from 'components/TableDescription/TableDescription.component';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
const DashboardDetails = ({
charts,
@ -603,6 +604,7 @@ const DashboardDetails = ({
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={dashboardDetails.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DASHBOARD}
@ -618,6 +620,7 @@ const DashboardDetails = ({
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={dashboardDetails.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DASHBOARD}

View File

@ -64,7 +64,7 @@ jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () =>
jest.fn().mockImplementation(() => <div>TagsContainerV2</div>)
);
jest.mock('components/Tag/TagsViewer/tags-viewer', () =>
jest.mock('components/Tag/TagsViewer/TagsViewer', () =>
jest.fn().mockImplementation(() => <div>TagsViewer</div>)
);

View File

@ -26,6 +26,7 @@ import SchemaEditor from 'components/schema-editor/SchemaEditor';
import { SourceType } from 'components/searched-data/SearchedData.interface';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { getDataModelDetailsPath, getVersionPath } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { CSMode } from 'enums/codemirror.enum';
@ -203,6 +204,7 @@ const DataModelDetails = ({
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={dashboardDataModelFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DASHBOARD_DATA_MODEL}
@ -213,6 +215,7 @@ const DataModelDetails = ({
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={dashboardDataModelFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DASHBOARD_DATA_MODEL}

View File

@ -15,7 +15,7 @@ import { Col, Divider, Row, Typography } from 'antd';
import { ReactComponent as IconExternalLink } from 'assets/svg/external-links.svg';
import classNames from 'classnames';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { SummaryEntityType } from 'enums/EntitySummary.enum';
import { ExplorePageTabs } from 'enums/Explore.enum';
import { Container } from 'generated/entity/data/container';
@ -118,7 +118,6 @@ function ContainerSummary({
<TagsViewer
sizeCap={2}
tags={(entityDetails.tags || []).map((tag) => getTagValue(tag))}
type="border"
/>
) : (
<Typography.Text className="text-grey-body">

View File

@ -17,7 +17,7 @@ import { AxiosError } from 'axios';
import classNames from 'classnames';
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { ExplorePageTabs } from 'enums/Explore.enum';
import { TagLabel } from 'generated/type/tagLabel';
import { ChartType } from 'pages/DashboardDetailsPage/DashboardDetailsPage.component';
@ -185,7 +185,6 @@ function DashboardSummary({
tags={(entityDetails.tags || []).map((tag) =>
getTagValue(tag)
)}
type="border"
/>
) : (
<Typography.Text className="text-grey-body">

View File

@ -16,7 +16,7 @@ import { ReactComponent as IconExternalLink } from 'assets/svg/external-links.sv
import classNames from 'classnames';
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { ExplorePageTabs } from 'enums/Explore.enum';
import { TagLabel } from 'generated/type/tagLabel';
import React, { useMemo } from 'react';
@ -148,7 +148,6 @@ function MlModelSummary({
tags={(entityDetails.tags || []).map((tag) =>
getTagValue(tag)
)}
type="border"
/>
) : (
<Typography.Text className="text-grey-body">

View File

@ -16,7 +16,7 @@ import { ReactComponent as IconExternalLink } from 'assets/svg/external-links.sv
import classNames from 'classnames';
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { ExplorePageTabs } from 'enums/Explore.enum';
import { TagLabel } from 'generated/type/tagLabel';
import React, { useMemo } from 'react';
@ -152,7 +152,6 @@ function PipelineSummary({
tags={(entityDetails.tags || []).map((tag) =>
getTagValue(tag)
)}
type="border"
/>
) : (
<Typography.Text className="text-grey-body">

View File

@ -12,7 +12,7 @@
*/
import { Col, Row, Space, Typography } from 'antd';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { MAX_CHAR_LIMIT_ENTITY_SUMMARY } from '../../../../../constants/constants';
@ -86,7 +86,6 @@ function SummaryListItem({
<TagsViewer
sizeCap={2}
tags={(entityDetails.tags || []).map((tag) => getTagValue(tag))}
type="border"
/>
</Col>
)}

View File

@ -29,7 +29,7 @@ jest.mock('../../../../common/rich-text-editor/RichTextEditorPreviewer', () =>
))
);
jest.mock('components/Tag/TagsViewer/tags-viewer', () =>
jest.mock('components/Tag/TagsViewer/TagsViewer', () =>
jest
.fn()
.mockImplementation(() => <div data-testid="TagsViewer">TagsViewer</div>)

View File

@ -22,7 +22,7 @@ import {
ResourceEntity,
} from 'components/PermissionProvider/PermissionProvider.interface';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { mockTablePermission } from 'constants/mockTourData.constants';
import { ClientErrors } from 'enums/axios.enum';
import { ExplorePageTabs } from 'enums/Explore.enum';
@ -364,7 +364,6 @@ function TableSummary({
tags={(entityDetails.tags || []).map((tag) =>
getTagValue(tag)
)}
type="border"
/>
) : (
<Typography.Text className="text-grey-body">

View File

@ -15,7 +15,7 @@ import { Col, Divider, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component';
import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { getTeamAndUserDetailsPath } from 'constants/constants';
import { ClientErrors } from 'enums/axios.enum';
import { isArray, isEmpty } from 'lodash';
@ -195,7 +195,6 @@ function TopicSummary({
tags={(entityDetails.tags || []).map((tag) =>
getTagValue(tag)
)}
type="border"
/>
) : (
<Typography.Text className="text-grey-body">

View File

@ -25,6 +25,7 @@ import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAss
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { TagLabel, TagSource } from 'generated/type/schema';
import { EntityFieldThreadCount } from 'interface/feed.interface';
@ -425,6 +426,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={mlModelDetail.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.MLMODEL}
@ -440,6 +442,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={mlModelDetail.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.MLMODEL}

View File

@ -31,7 +31,7 @@ import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHead
import SourceList from 'components/MlModelDetail/SourceList.component';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { getVersionPathWithTab } from 'constants/constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { EntityTabs, EntityType } from 'enums/entity.enum';
@ -284,7 +284,6 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
getFilterTags(feature.tags ?? [])
.Glossary
}
type="border"
/>
</Col>
</Row>
@ -304,7 +303,6 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
getFilterTags(feature.tags ?? [])
.Classification
}
type="border"
/>
</Col>
</Row>

View File

@ -31,6 +31,7 @@ import TableDescription from 'components/TableDescription/TableDescription.compo
import TableTags from 'components/TableTags/TableTags.component';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import TasksDAGView from 'components/TasksDAGView/TasksDAGView';
import { EntityField } from 'constants/Feeds.constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
@ -601,6 +602,7 @@ const PipelineDetails = ({
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={pipelineFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.PIPELINE}
@ -616,6 +618,7 @@ const PipelineDetails = ({
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={pipelineFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.PIPELINE}

View File

@ -25,7 +25,7 @@ import EntityVersionTimeLine from 'components/EntityVersionTimeLine/EntityVersio
import Loader from 'components/Loader/Loader';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { getVersionPathWithTab } from 'constants/constants';
import { TABLE_SCROLL_VALUE } from 'constants/Table.constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
@ -299,7 +299,6 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
<TagsViewer
sizeCap={-1}
tags={getFilterTags(tags || []).Classification}
type="border"
/>
),
},
@ -310,11 +309,7 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
accessor: 'tags',
width: 272,
render: (tags) => (
<TagsViewer
sizeCap={-1}
tags={getFilterTags(tags || []).Glossary}
type="border"
/>
<TagsViewer sizeCap={-1} tags={getFilterTags(tags || []).Glossary} />
),
},
],

View File

@ -42,7 +42,7 @@ jest.mock('components/common/rich-text-editor/RichTextEditorPreviewer', () =>
jest.fn().mockImplementation(() => <div>RichTextEditorPreviewer</div>)
);
jest.mock('components/Tag/TagsViewer/tags-viewer', () =>
jest.mock('components/Tag/TagsViewer/TagsViewer', () =>
jest.fn().mockImplementation(() => <div>TagsViewer</div>)
);

View File

@ -13,7 +13,7 @@
import EntitySummaryDetails from 'components/common/EntitySummaryDetails/EntitySummaryDetails';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { TagLabel } from 'generated/type/tagLabel';
import { isEmpty, isNil } from 'lodash';
import { ExtraInfo } from 'Models';
@ -69,7 +69,6 @@ const TableDataCardBody: FunctionComponent<Props> = ({
<TagsViewer
sizeCap={3}
tags={(tags ?? []).map((tag) => getTagValue(tag))}
type="border"
/>
</div>
)}

View File

@ -12,7 +12,7 @@
*/
import { Space, Typography } from 'antd';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { Column } from 'generated/entity/data/container';
import { isEmpty } from 'lodash';
import React, { FC } from 'react';

View File

@ -15,6 +15,7 @@ import { ThreadType } from 'generated/api/feed/createThread';
import { TagSource } from 'generated/type/tagLabel';
import { EntityTags } from 'Models';
import { ReactElement } from 'react';
import { DisplayType } from '../TagsViewer/TagsViewer.interface';
export type TagsContainerV2Props = {
permission: boolean;
@ -28,6 +29,7 @@ export type TagsContainerV2Props = {
showBottomEditButton?: boolean;
showInlineEditButton?: boolean;
children?: ReactElement;
displayType?: DisplayType;
onSelectionChange?: (selectedTags: EntityTags[]) => Promise<void>;
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
};

View File

@ -34,7 +34,7 @@ import { ReactComponent as IconComments } from '../../../assets/svg/comment.svg'
import { ReactComponent as IconRequest } from '../../../assets/svg/request-icon.svg';
import TagSelectForm from '../TagsSelectForm/TagsSelectForm.component';
import TagsV1 from '../TagsV1/TagsV1.component';
import TagsViewer from '../TagsViewer/tags-viewer';
import TagsViewer from '../TagsViewer/TagsViewer';
import { TagsContainerV2Props } from './TagsContainerV2.interface';
const TagsContainerV2 = ({
@ -45,6 +45,7 @@ const TagsContainerV2 = ({
entityThreadLink,
entityFqn,
tagType,
displayType,
showHeader = true,
showBottomEditButton,
showInlineEditButton,
@ -166,13 +167,13 @@ const TagsContainerV2 = ({
() => (
<Col>
<TagsViewer
displayType={displayType}
showNoDataPlaceholder={showNoDataPlaceholder}
tags={tags?.[tagType] ?? []}
type="border"
/>
</Col>
),
[showNoDataPlaceholder, tags?.[tagType]]
[displayType, showNoDataPlaceholder, tags?.[tagType]]
);
const tagsSelectContainer = useMemo(() => {

View File

@ -14,8 +14,13 @@
import { EntityTags } from 'Models';
export interface TagsViewerProps {
tags: Array<EntityTags>;
tags: EntityTags[];
sizeCap?: number;
type?: 'label' | 'contained' | 'outlined' | 'border';
displayType?: DisplayType;
showNoDataPlaceholder?: boolean;
}
export enum DisplayType {
READ_MORE = 'read-more',
POPOVER = 'popover',
}

View File

@ -0,0 +1,114 @@
/*
* 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 { fireEvent, render, screen } from '@testing-library/react';
import { NO_DATA_PLACEHOLDER } from 'constants/constants';
import React from 'react';
import TagsViewer from './TagsViewer';
import { DisplayType } from './TagsViewer.interface';
const tags = [
{ tagFQN: `tags.tag_1`, source: 'Classification' },
{ tagFQN: `tags.tag_2`, source: 'Classification' },
{ tagFQN: `tags.tag_3`, source: 'Classification' },
{ tagFQN: `test.tags.term_1`, source: 'Glossary' },
{ tagFQN: `test.tags.term_2`, source: 'Glossary' },
{ tagFQN: `test.tags.term_3`, source: 'Glossary' },
];
jest.mock('../TagsV1/TagsV1.component', () => {
return jest.fn().mockReturnValue(<p>TagsV1</p>);
});
describe('Test TagsViewer Component', () => {
it('Should render placeholder if tags is empty', () => {
render(<TagsViewer sizeCap={-1} tags={[]} />);
const placeholder = screen.getByText(NO_DATA_PLACEHOLDER);
expect(placeholder).toBeInTheDocument();
});
it('Should render all tags', () => {
render(<TagsViewer sizeCap={-1} tags={tags} />);
const allTags = screen.getAllByText('TagsV1');
expect(allTags).toHaveLength(6);
});
it('Should render tags as per sizeCap', () => {
render(<TagsViewer sizeCap={2} tags={tags} />);
const allTags = screen.getAllByText('TagsV1');
expect(allTags).toHaveLength(2);
});
it('Should render tags on popover style', () => {
render(<TagsViewer tags={tags} />);
const sizeTags = screen.getAllByText('TagsV1');
expect(sizeTags).toHaveLength(5);
const popoverElement = screen.getByTestId('popover-element');
expect(popoverElement).toBeInTheDocument();
const plusButton = screen.getByTestId('plus-more-count');
expect(plusButton).toHaveTextContent('+1 more');
});
it('Should render tags on read more style', () => {
render(<TagsViewer displayType={DisplayType.READ_MORE} tags={tags} />);
const sizeTags = screen.getAllByText('TagsV1');
expect(sizeTags).toHaveLength(5);
const readMoreElement = screen.getByTestId('read-more-element');
expect(readMoreElement).toBeInTheDocument();
const readButton = screen.getByTestId('read-button');
expect(readButton).toHaveTextContent('label.read-type');
});
it('Should render all tags on popover click', () => {
render(<TagsViewer tags={tags} />);
const plusButton = screen.getByTestId('plus-more-count');
expect(plusButton).toHaveTextContent('+1 more');
fireEvent.click(plusButton);
const sizeTags = screen.getAllByText('TagsV1');
expect(sizeTags).toHaveLength(6);
});
it('Should render all tags on read more click', () => {
render(<TagsViewer displayType={DisplayType.READ_MORE} tags={tags} />);
const readButton = screen.getByTestId('read-button');
expect(readButton).toHaveTextContent('label.read-type');
fireEvent.click(readButton);
const sizeTags = screen.getAllByText('TagsV1');
expect(sizeTags).toHaveLength(6);
});
});

View File

@ -0,0 +1,147 @@
/*
* 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 { Button, Popover, Tag, Typography } from 'antd';
import classNames from 'classnames';
import { TAG_START_WITH } from 'constants/Tag.constants';
import { isEmpty, sortBy, uniqBy } from 'lodash';
import { EntityTags } from 'Models';
import React, {
FunctionComponent,
useCallback,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { LIST_SIZE, NO_DATA_PLACEHOLDER } from '../../../constants/constants';
import { TagSource } from '../../../generated/type/tagLabel';
import TagsV1 from '../TagsV1/TagsV1.component';
import './tags-viewer.less';
import { DisplayType, TagsViewerProps } from './TagsViewer.interface';
const TagsViewer: FunctionComponent<TagsViewerProps> = ({
tags,
sizeCap = LIST_SIZE,
displayType = DisplayType.POPOVER,
showNoDataPlaceholder = true,
}: TagsViewerProps) => {
const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const getTagsElement = useCallback(
(tag: EntityTags) => (
<TagsV1
className={classNames(
{ 'diff-added tw-mx-1': tag?.added },
{ 'diff-removed': tag?.removed }
)}
showOnlyName={tag.source === TagSource.Glossary}
startWith={TAG_START_WITH.SOURCE_ICON}
tag={tag}
/>
),
[]
);
// sort tags by source so that "Glossary" tags always comes first
const sortedTagsBySource = useMemo(
() => sortBy(uniqBy(tags, 'tagFQN'), 'source'),
[tags]
);
const hasMoreElement = useMemo(
() => sortedTagsBySource.length > (sizeCap ?? 0),
[sizeCap, sortedTagsBySource]
);
const readMoreRenderElement = useMemo(
() => (
<div data-testid="read-more-element">
{isOpen &&
sortedTagsBySource.slice(sizeCap).map((tag) => (
<p className="text-left" key={tag}>
{getTagsElement(tag)}
</p>
))}
{hasMoreElement && (
<Button
className="m-t-xss"
data-testid="read-button"
size="small"
type="link"
onClick={() => setIsOpen(!isOpen)}>
{t('label.read-type', {
type: isOpen ? t('label.less') : t('label.more'),
})}
</Button>
)}
</div>
),
[sizeCap, isOpen, hasMoreElement, sortedTagsBySource]
);
const popoverRenderElement = useMemo(
() => (
<div data-testid="popover-element">
{sortedTagsBySource.slice(sizeCap).length > 0 && (
<Popover
content={
<>
{sortedTagsBySource.slice(sizeCap).map((tag) => (
<p className="text-left" key={tag}>
{getTagsElement(tag)}
</p>
))}
</>
}
overlayClassName="tag-popover-container"
placement="bottom"
trigger="click">
<Tag
className="cursor-pointer plus-more-tag"
data-testid="plus-more-count">{`+${
sortedTagsBySource.length - (sizeCap ?? 0)
} more`}</Tag>
</Popover>
)}
</div>
),
[sizeCap, sortedTagsBySource]
);
if (isEmpty(sortedTagsBySource) && showNoDataPlaceholder) {
return (
<Typography.Text className="text-grey-muted m-r-xss">
{NO_DATA_PLACEHOLDER}
</Typography.Text>
);
}
if (sizeCap < 0) {
return <>{sortedTagsBySource.map(getTagsElement)}</>;
}
return (
<>
{sortedTagsBySource.slice(0, sizeCap).map(getTagsElement)}
{displayType === DisplayType.POPOVER
? popoverRenderElement
: readMoreRenderElement}
{}
</>
);
};
export default TagsViewer;

View File

@ -1,61 +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 { getAllByTestId, getByText, render } from '@testing-library/react';
import React from 'react';
import TagsViewer from './tags-viewer';
const tags = [
{ tagFQN: `tags.tag 1`, source: 'Classification' },
{ tagFQN: `tags.tag 2`, source: 'Classification' },
{ tagFQN: `test.tags.term`, source: 'Glossary' },
];
jest.mock('components/common/rich-text-editor/RichTextEditorPreviewer', () => {
return jest.fn().mockReturnValue(<p>RichTextEditorPreviewer</p>);
});
describe('Test TagsViewer Component', () => {
it('Component should render', () => {
const { container } = render(<TagsViewer sizeCap={-1} tags={tags} />);
const TagViewer = getAllByTestId(container, 'tags');
expect(TagViewer).toHaveLength(3);
});
it('Should render tags', () => {
const { container } = render(<TagsViewer sizeCap={-1} tags={tags} />);
const TagViewer = getAllByTestId(container, 'tags');
expect(TagViewer).toHaveLength(3);
const tag1 = getByText(container, /tags.tag 1/);
const tag2 = getByText(container, /tags.tag 2/);
const tag3 = getByText(container, /tags.term/);
expect(tag1).toBeInTheDocument();
expect(tag2).toBeInTheDocument();
expect(tag3).toBeInTheDocument();
});
it('Should render tags and glossary with their respective symbol', () => {
const { container } = render(<TagsViewer sizeCap={-1} tags={tags} />);
const TagViewer = getAllByTestId(container, 'tags');
const tagIcons = getAllByTestId(container, 'tags-icon');
const glossaryIcons = getAllByTestId(container, 'glossary-icon');
expect(TagViewer).toHaveLength(3);
expect(tagIcons).toHaveLength(2);
expect(glossaryIcons).toHaveLength(1);
});
});

View File

@ -1,97 +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 { Popover, Tag, Typography } from 'antd';
import classNames from 'classnames';
import { TAG_START_WITH } from 'constants/Tag.constants';
import { isEmpty, sortBy, uniqBy } from 'lodash';
import { EntityTags } from 'Models';
import React, { FunctionComponent, useCallback, useMemo } from 'react';
import { LIST_SIZE, NO_DATA_PLACEHOLDER } from '../../../constants/constants';
import { TagSource } from '../../../generated/type/tagLabel';
import TagsV1 from '../TagsV1/TagsV1.component';
import { TagsViewerProps } from './tags-viewer.interface';
import './tags-viewer.less';
const TagsViewer: FunctionComponent<TagsViewerProps> = ({
tags,
sizeCap = LIST_SIZE,
type = 'label',
showNoDataPlaceholder = true,
}: TagsViewerProps) => {
const getTagsElement = useCallback(
(tag: EntityTags, index: number) => (
<TagsV1
className={classNames(
{ 'diff-added tw-mx-1': tag?.added },
{ 'diff-removed': tag?.removed }
)}
key={index}
showOnlyName={tag.source === TagSource.Glossary}
startWith={TAG_START_WITH.SOURCE_ICON}
tag={tag}
/>
),
[type]
);
// sort tags by source so that "Glossary" tags always comes first
const sortedTagsBySource = useMemo(
() => sortBy(uniqBy(tags, 'tagFQN'), 'source'),
[tags]
);
if (isEmpty(sortedTagsBySource) && showNoDataPlaceholder) {
return (
<Typography.Text className="text-grey-muted m-r-xss">
{NO_DATA_PLACEHOLDER}
</Typography.Text>
);
}
return (
<div>
{sizeCap > -1 ? (
<>
{sortedTagsBySource.slice(0, sizeCap).map(getTagsElement)}
{sortedTagsBySource.slice(sizeCap).length > 0 && (
<Popover
content={
<>
{sortedTagsBySource.slice(sizeCap).map((tag, index) => (
<p className="text-left" key={index}>
{getTagsElement(tag, index)}
</p>
))}
</>
}
overlayClassName="tag-popover-container"
placement="bottom"
trigger="click">
<Tag
className="cursor-pointer plus-more-tag"
data-testid="plus-more-count">{`+${
sortedTagsBySource.length - sizeCap
} more`}</Tag>
</Popover>
)}
</>
) : (
sortedTagsBySource.map(getTagsElement)
)}
</div>
);
};
export default TagsViewer;

View File

@ -12,7 +12,7 @@
*/
import { Typography } from 'antd';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { LabelType, State, TagLabel, TagSource } from 'generated/type/tagLabel';
import { EntityTags } from 'Models';
import React from 'react';
@ -68,7 +68,7 @@ const TagsInput: React.FC<Props> = ({
{t('label.tag-plural')}
</Typography.Text>
</div>
<TagsViewer sizeCap={-1} tags={tags} type="border" />
<TagsViewer sizeCap={-1} tags={tags} />
</>
) : (
<TagsContainerV2

View File

@ -27,6 +27,7 @@ import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.in
import SampleDataTopic from 'components/SampleDataTopic/SampleDataTopic';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { getTopicDetailsPath } from 'constants/constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { TagLabel } from 'generated/type/schema';
@ -315,6 +316,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={topicDetails.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.TOPIC}
@ -329,6 +331,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={topicDetails.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.TOPIC}

View File

@ -30,7 +30,7 @@ import {
} from '../../utils/TableUtils';
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
import Searchbar from '../common/searchbar/Searchbar';
import TagsViewer from '../Tag/TagsViewer/tags-viewer';
import TagsViewer from '../Tag/TagsViewer/TagsViewer';
import { VersionTableProps } from './VersionTable.interfaces';
const VersionTable = ({
@ -121,7 +121,6 @@ const VersionTable = ({
<TagsViewer
sizeCap={-1}
tags={getFilterTags(tags ?? []).Classification}
type="border"
/>
),
},
@ -132,11 +131,7 @@ const VersionTable = ({
accessor: 'tags',
width: 272,
render: (tags: Column['tags']) => (
<TagsViewer
sizeCap={-1}
tags={getFilterTags(tags ?? []).Glossary}
type="border"
/>
<TagsViewer sizeCap={-1} tags={getFilterTags(tags ?? []).Glossary} />
),
},
],

View File

@ -12,7 +12,7 @@
*/
import { Col, Divider, Row, Typography } from 'antd';
import { EntityUnion } from 'components/Explore/explore.interface';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { TagLabel } from 'generated/type/tagLabel';
import React from 'react';
import { useTranslation } from 'react-i18next';

View File

@ -496,6 +496,7 @@
"last-updated": "Last Updated",
"latest": "Latest",
"leave-team": "Leave Team",
"less": "Less",
"less-lowercase": "less",
"line": "Line",
"line-plural": "Lines",

View File

@ -496,6 +496,7 @@
"last-updated": "Última actualización",
"latest": "Último",
"leave-team": "Dejar el equipo",
"less": "Less",
"less-lowercase": "menos",
"line": "Línea",
"line-plural": "Lines",

View File

@ -496,6 +496,7 @@
"last-updated": "Dernière Mise à Jour",
"latest": "Dernier·ère",
"leave-team": "Quitter une Equipe",
"less": "Less",
"less-lowercase": "moins",
"line": "Ligne",
"line-plural": "Lignes",

View File

@ -496,6 +496,7 @@
"last-updated": "最終更新日",
"latest": "最新",
"leave-team": "チームを抜ける",
"less": "Less",
"less-lowercase": "less",
"line": "Line",
"line-plural": "Lines",

View File

@ -496,6 +496,7 @@
"last-updated": "Última atualização em",
"latest": "Mais recente",
"leave-team": "Sair do time",
"less": "Less",
"less-lowercase": "menos",
"line": "Line",
"line-plural": "Lines",

View File

@ -496,6 +496,7 @@
"last-updated": "最近更新",
"latest": "最新",
"leave-team": "离开团队",
"less": "Less",
"less-lowercase": "更少",
"line": "行",
"line-plural": "行",

View File

@ -36,6 +36,7 @@ import {
} from 'components/PermissionProvider/PermissionProvider.interface';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { getContainerDetailPath, getVersionPath } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
@ -518,6 +519,7 @@ const ContainerPage = () => {
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={containerName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.CONTAINER}
@ -530,6 +532,7 @@ const ContainerPage = () => {
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={containerName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.CONTAINER}

View File

@ -34,6 +34,7 @@ import {
} from 'components/PermissionProvider/PermissionProvider.interface';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { compare, Operation } from 'fast-json-patch';
import { LabelType } from 'generated/entity/data/table';
@ -651,6 +652,7 @@ const DatabaseDetails: FunctionComponent = () => {
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={databaseFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DATABASE}
@ -661,6 +663,7 @@ const DatabaseDetails: FunctionComponent = () => {
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={databaseFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DATABASE}

View File

@ -30,6 +30,7 @@ import {
} from 'components/PermissionProvider/PermissionProvider.interface';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { compare, Operation } from 'fast-json-patch';
import { Include } from 'generated/type/include';
@ -476,6 +477,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={databaseSchemaFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DATABASE_SCHEMA}
@ -486,6 +488,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={databaseSchemaFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DATABASE_SCHEMA}

View File

@ -21,7 +21,8 @@ import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichText
import Loader from 'components/Loader/Loader';
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import TagsViewer from 'components/Tag/TagsViewer/TagsViewer';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { NO_DATA_PLACEHOLDER, PAGE_SIZE } from 'constants/constants';
import { ServiceCategory } from 'enums/service.enum';
import { Database } from 'generated/entity/data/database';
@ -224,7 +225,7 @@ function ServiceMainTabContent({
width: 200,
key: 'tags',
render: (_, record: ServicePageData) => (
<TagsViewer sizeCap={-1} tags={record.tags ?? []} type="border" />
<TagsViewer tags={record.tags ?? []} />
),
},
...(ServiceCategory.DATABASE_SERVICES === serviceCategory
@ -337,6 +338,7 @@ function ServiceMainTabContent({
flex="320px">
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={serviceFQN}
entityType={entityType}
permission={editTagsPermission}
@ -346,6 +348,7 @@ function ServiceMainTabContent({
onSelectionChange={handleTagSelection}
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={serviceFQN}
entityType={entityType}
permission={editTagsPermission}

View File

@ -40,6 +40,7 @@ import TableProfilerV1 from 'components/TableProfiler/TableProfilerV1';
import TableQueries from 'components/TableQueries/TableQueries';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { useTourProvider } from 'components/TourProvider/TourProvider';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { getTableTabPath, getVersionPath } from 'constants/constants';
@ -488,6 +489,7 @@ const TableDetailsPageV1 = () => {
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={datasetFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.TABLE}
@ -502,6 +504,7 @@ const TableDetailsPageV1 = () => {
/>
<TagsContainerV2
displayType={DisplayType.READ_MORE}
entityFqn={datasetFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.TABLE}