chore(ui): remove unused tags files and minor ui fixes (#12264)

* remove unused tags files and minor ui fixes

* no tags shown as per global styling

* fix unit test
This commit is contained in:
Ashish Gupta 2023-07-11 14:05:17 +05:30 committed by GitHub
parent cf2aafa770
commit 8635fd7a28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 41 additions and 965 deletions

View File

@ -19,7 +19,7 @@ import DescriptionV1 from 'components/common/description/DescriptionV1';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { getVersionPathWithTab } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
@ -153,9 +153,7 @@ const ContainerVersion: React.FC<ContainerVersionProp> = ({
flex="220px">
<Space className="w-full" direction="vertical" size="large">
{Object.keys(TagSource).map((tagType) => (
<TagsContainerV1
isVersionView
showLimited
<TagsContainerV2
entityFqn={containerFQN}
entityType={EntityType.CONTAINER}
key={tagType}

View File

@ -21,7 +21,7 @@ import DescriptionV1 from 'components/common/description/DescriptionV1';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { getVersionPathWithTab } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
@ -195,9 +195,7 @@ const DashboardVersion: FC<DashboardVersionProp> = ({
flex="220px">
<Space className="w-full" direction="vertical" size="large">
{Object.keys(TagSource).map((tagType) => (
<TagsContainerV1
isVersionView
showLimited
<TagsContainerV2
entityFqn={currentVersionData.fullyQualifiedName}
entityType={EntityType.DASHBOARD}
key={tagType}

View File

@ -17,7 +17,7 @@ import DescriptionV1 from 'components/common/description/DescriptionV1';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import VersionTable from 'components/VersionTable/VersionTable.component';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { EntityTabs, EntityType, FqnPart } from 'enums/entity.enum';
@ -301,9 +301,7 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
flex="220px">
<Space className="w-full" direction="vertical" size="large">
{Object.keys(TagSource).map((tagType) => (
<TagsContainerV1
isVersionView
showLimited
<TagsContainerV2
entityFqn={currentVersionData.fullyQualifiedName}
entityType={EntityType.DASHBOARD_DATA_MODEL}
key={tagType}

View File

@ -19,7 +19,7 @@ import DescriptionV1 from 'components/common/description/DescriptionV1';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { getVersionPathWithTab } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
@ -173,9 +173,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
flex="220px">
<Space className="w-full" direction="vertical" size="large">
{Object.keys(TagSource).map((tagType) => (
<TagsContainerV1
isVersionView
showLimited
<TagsContainerV2
entityFqn={datasetFQN}
entityType={EntityType.TABLE}
key={tagType}

View File

@ -30,7 +30,7 @@ import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichText
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
import SourceList from 'components/MlModelDetail/SourceList.component';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import { getVersionPathWithTab } from 'constants/constants';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
@ -272,7 +272,7 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
</Col>
<Col className="m-b-xs" span={24}>
<Row gutter={8} wrap={false}>
<Col flex="120px">
<Col flex="130px">
<Typography.Text className="text-grey-muted">
{`${t('label.glossary-term-plural')} :`}
</Typography.Text>
@ -293,7 +293,7 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
<Col className="m-b-xs" span={24}>
<Row gutter={8} wrap={false}>
<Col flex="120px">
<Col flex="130px">
<Typography.Text className="text-grey-muted">
{`${t('label.tag-plural')} :`}
</Typography.Text>
@ -358,9 +358,7 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
flex="220px">
<Space className="w-full" direction="vertical" size="large">
{Object.keys(TagSource).map((tagType) => (
<TagsContainerV1
isVersionView
showLimited
<TagsContainerV2
entityFqn={currentVersionData.fullyQualifiedName}
entityType={EntityType.MLMODEL}
key={tagType}

View File

@ -21,7 +21,7 @@ import DescriptionV1 from 'components/common/description/DescriptionV1';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import TagsViewer from 'components/Tag/TagsViewer/tags-viewer';
import { getVersionPathWithTab } from 'constants/constants';
import { TABLE_SCROLL_VALUE } from 'constants/Table.constants';
@ -379,9 +379,7 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
flex="220px">
<Space className="w-full" direction="vertical" size="large">
{Object.keys(TagSource).map((tagType) => (
<TagsContainerV1
isVersionView
showLimited
<TagsContainerV2
entityFqn={currentVersionData.fullyQualifiedName}
entityType={EntityType.PIPELINE}
key={tagType}

View File

@ -12,7 +12,6 @@
*/
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
import { TagsDetailsProps } from 'components/Tag/TagsContainerV1/TagsContainerV1.interface';
import { Query } from 'generated/entity/data/query';
export interface TableQueryRightPanelProps {
@ -21,9 +20,3 @@ export interface TableQueryRightPanelProps {
permission: OperationPermission;
onQueryUpdate: (updatedQuery: Query, key: keyof Query) => Promise<void>;
}
export type TagDetails = {
isLoading: boolean;
options: TagsDetailsProps[];
isError: boolean;
};

View File

@ -204,7 +204,6 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
() =>
showLimited ? (
<TagsViewer
isTextPlaceholder
showNoDataPlaceholder={showNoDataPlaceholder}
tags={selectedTags}
type="border"

View File

@ -1,162 +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, Space } from 'antd';
import { ReactComponent as IconEdit } from 'assets/svg/edit-new.svg';
import Loader from 'components/Loader/Loader';
import { TableTagsProps } from 'components/TableTags/TableTags.interface';
import Tags from 'components/Tag/Tags/tags';
import { TAG_CONSTANT, TAG_START_WITH } from 'constants/Tag.constants';
import { TagSource } from 'generated/type/tagLabel';
import { isEmpty } from 'lodash';
import { EntityTags } from 'Models';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { getFilterTags } from 'utils/TableTags/TableTags.utils';
import { getTagPlaceholder } from 'utils/TagsUtils';
import TagTree from '../TagsTree/TagsTreeForm.component';
import TagsViewer from '../TagsViewer/tags-viewer';
import { TagsContainerEntityTableProps } from './TagsContainerEntityTable.interface';
const TagsContainerEntityTable = ({
isLoading,
isEditing,
permission,
selectedTags,
showEditButton,
tagType,
treeData,
onCancel,
onSelectionChange,
onAddButtonClick,
}: TagsContainerEntityTableProps) => {
const [tags, setTags] = useState<TableTagsProps>();
const isGlossaryType = useMemo(
() => tagType === TagSource.Glossary,
[tagType]
);
const showAddTagButton = useMemo(
() => permission && isEmpty(tags?.[tagType]),
[permission, tags?.[tagType]]
);
const selectedTagsInternal = useMemo(
() => tags?.[tagType].map(({ tagFQN }) => tagFQN as string),
[tags, tagType]
);
const showNoDataPlaceholder = useMemo(
() => !showAddTagButton && isEmpty(tags?.[tagType]),
[showAddTagButton, tags?.[tagType]]
);
const getUpdatedTags = (selectedTag: string[]): EntityTags[] => {
const updatedTags = selectedTag.map((t) => ({
tagFQN: t,
source: isGlossaryType ? TagSource.Glossary : TagSource.Classification,
}));
return updatedTags;
};
const handleSave = useCallback(
(selectedTag: string[]) => {
const updatedTags = getUpdatedTags(selectedTag);
onSelectionChange([
...updatedTags,
...((isGlossaryType
? tags?.[TagSource.Classification]
: tags?.[TagSource.Glossary]) ?? []),
]);
},
[isGlossaryType, tags, getUpdatedTags]
);
const editTagButton = useMemo(
() =>
!isEmpty(tags?.[tagType]) && showEditButton ? (
<Button
className="p-0 flex-center text-primary"
data-testid="edit-button"
icon={<IconEdit className="anticon" height={16} width={16} />}
size="small"
type="text"
onClick={onAddButtonClick}
/>
) : null,
[selectedTags, showEditButton, onAddButtonClick]
);
const addTagButton = useMemo(
() =>
showAddTagButton ? (
<span onClick={onAddButtonClick}>
<Tags
className="tw-font-semibold tw-text-primary"
startWith={TAG_START_WITH.PLUS}
tag={TAG_CONSTANT}
type="border"
/>
</span>
) : null,
[showAddTagButton]
);
const renderTags = useMemo(
() => (
<TagsViewer
isTextPlaceholder
showNoDataPlaceholder={showNoDataPlaceholder}
tags={tags?.[tagType] ?? []}
type="border"
/>
),
[showNoDataPlaceholder, tags?.[tagType]]
);
const tagsSelectContainer = useMemo(() => {
return isLoading ? (
<Loader size="small" />
) : (
<TagTree
defaultValue={selectedTagsInternal ?? []}
placeholder={getTagPlaceholder(isGlossaryType)}
treeData={treeData}
onCancel={onCancel}
onSubmit={handleSave}
/>
);
}, [isGlossaryType, selectedTagsInternal, treeData, onCancel, handleSave]);
useEffect(() => {
setTags(getFilterTags(selectedTags));
}, [selectedTags]);
return (
<div
className="w-full"
data-testid={isGlossaryType ? 'glossary-container' : 'tags-container'}>
{!isEditing && (
<Space wrap align="center" data-testid="entity-tags" size={4}>
{addTagButton}
{renderTags}
{editTagButton}
</Space>
)}
{isEditing && tagsSelectContainer}
</div>
);
};
export default TagsContainerEntityTable;

View File

@ -1,28 +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 { TagSource } from 'generated/type/tagLabel';
import { EntityTags } from 'Models';
import { HierarchyTagsProps } from '../TagsContainerV1/TagsContainerV1.interface';
export type TagsContainerEntityTableProps = {
isEditing: boolean;
isLoading: boolean;
permission: boolean;
selectedTags: EntityTags;
tagType: TagSource;
treeData: HierarchyTagsProps[];
showEditButton?: boolean;
onCancel: () => void;
onAddButtonClick: () => void;
onSelectionChange: (selectedTags: EntityTags[]) => void;
};

View File

@ -1,80 +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 { ThreadType } from 'generated/api/feed/createThread';
import { Tag } from 'generated/entity/classification/tag';
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
import { TagSource } from 'generated/type/tagLabel';
import { EntityTags } from 'Models';
interface TagsTreeProps {
title: string;
value: string;
key: string;
selectable: boolean;
}
export interface HierarchyTagsProps extends TagsTreeProps {
children: TagsTreeProps[];
}
export interface TagsDetailsProps {
name: string;
fqn: string;
classification: Tag['classification'];
source: TagSource;
}
export type TagDetailsProps = {
isLoading: boolean;
options: TagsDetailsProps[];
isError: boolean;
};
export type GlossaryDetailsProps = {
isLoading: boolean;
options: GlossaryTermDetailsProps[];
isError: boolean;
};
export type GlossaryTermDetailsProps = {
name: string;
fqn: string;
children: GlossaryTerm['children'];
parent: GlossaryTerm['parent'];
glossary: GlossaryTerm['glossary'];
source: TagSource;
};
export type TagsContainerV1Props = {
isVersionView?: boolean;
permission: boolean;
selectedTags: Array<EntityTags>;
onSelectionChange?: (selectedTags: Array<EntityTags>) => void;
placeholder?: string;
showLimited?: boolean;
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
entityType?: string;
entityThreadLink?: string;
entityFqn?: string;
tagType: TagSource;
};
export type TagsTreeComponentProps = {
placeholder: string;
treeData: HierarchyTagsProps[];
defaultValue: string[];
onChange?: (value: string[]) => void;
onSubmit: (tags: string[]) => void;
onCancel: () => void;
};

View File

@ -1,388 +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, Col, Form, Row, Space, Tooltip, Typography } from 'antd';
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
import Loader from 'components/Loader/Loader';
import { TableTagsProps } from 'components/TableTags/TableTags.interface';
import Tags from 'components/Tag/Tags/tags';
import {
API_RES_MAX_SIZE,
DE_ACTIVE_COLOR,
PAGE_SIZE_LARGE,
} from 'constants/constants';
import { TAG_CONSTANT, TAG_START_WITH } from 'constants/Tag.constants';
import { EntityType } from 'enums/entity.enum';
import { TagSource } from 'generated/type/tagLabel';
import { isEmpty } from 'lodash';
import { EntityTags } from 'Models';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { getGlossariesList, getGlossaryTerms } from 'rest/glossaryAPI';
import { getEntityFeedLink } from 'utils/EntityUtils';
import { getGlossaryTermHierarchy } from 'utils/GlossaryUtils';
import { getFilterTags } from 'utils/TableTags/TableTags.utils';
import {
getAllTagsForOptions,
getTagPlaceholder,
getTagsHierarchy,
} from 'utils/TagsUtils';
import {
getRequestTagsPath,
getUpdateTagsPath,
TASK_ENTITIES,
} from 'utils/TasksUtils';
import { ReactComponent as IconComments } from '../../../assets/svg/comment.svg';
import { ReactComponent as IconRequest } from '../../../assets/svg/request-icon.svg';
import TagTree from '../TagsTree/TagsTreeForm.component';
import TagsViewer from '../TagsViewer/tags-viewer';
import {
GlossaryDetailsProps,
GlossaryTermDetailsProps,
TagDetailsProps,
TagsContainerV1Props,
} from './TagsContainerV1.interface';
const TagsContainerV1 = ({
permission,
selectedTags,
entityType,
entityThreadLink,
entityFqn,
tagType,
onSelectionChange,
onThreadLinkSelect,
isVersionView,
}: TagsContainerV1Props) => {
const history = useHistory();
const [form] = Form.useForm();
const { t } = useTranslation();
const [isEditTags, setIsEditTags] = useState(false);
const [tagDetails, setTagDetails] = useState<TagDetailsProps>({
isLoading: false,
isError: false,
options: [],
});
const [glossaryDetails, setGlossaryDetails] = useState<GlossaryDetailsProps>({
isLoading: false,
isError: false,
options: [],
});
const [tags, setTags] = useState<TableTagsProps>();
const isGlossaryType = useMemo(
() => tagType === TagSource.Glossary,
[tagType]
);
const showAddTagButton = useMemo(
() => permission && isEmpty(tags?.[tagType]),
[permission, tags?.[tagType]]
);
const selectedTagsInternal = useMemo(
() => tags?.[tagType].map(({ tagFQN }) => tagFQN as string),
[tags, tagType]
);
const treeData = useMemo(() => {
const tags = getTagsHierarchy(tagDetails.options);
const glossary = getGlossaryTermHierarchy(glossaryDetails.options);
return [...tags, ...glossary];
}, [tagDetails.options, glossaryDetails.options]);
const fetchTags = async () => {
if (isEmpty(tagDetails.options) || tagDetails.isError) {
setTagDetails((pre) => ({ ...pre, isLoading: true }));
try {
const tags = await getAllTagsForOptions();
setTagDetails((pre) => ({
...pre,
options: tags.map((tag) => {
return {
name: tag.name,
fqn: tag.fullyQualifiedName ?? '',
classification: tag.classification,
source: TagSource.Classification,
};
}),
}));
setIsEditTags(true);
} catch (_error) {
setTagDetails((pre) => ({ ...pre, isError: true, options: [] }));
} finally {
setTagDetails((pre) => ({ ...pre, isLoading: false }));
}
}
};
const fetchGlossaryList = async () => {
if (isEmpty(glossaryDetails.options) || glossaryDetails.isError) {
setGlossaryDetails((pre) => ({ ...pre, isLoading: true }));
try {
const glossaryTermList: GlossaryTermDetailsProps[] = [];
const { data } = await getGlossariesList({
limit: PAGE_SIZE_LARGE,
});
const promises = data.map((item) =>
getGlossaryTerms({
glossary: item.id,
limit: API_RES_MAX_SIZE,
fields: 'children,parent',
})
);
const response = await Promise.allSettled(promises);
response.forEach((res) => {
if (res.status === 'fulfilled') {
glossaryTermList.push(
...res.value.data.map((data) => ({
name: data.name,
fqn: data.fullyQualifiedName ?? '',
children: data.children,
parent: data.parent,
glossary: data.glossary,
source: TagSource.Glossary,
}))
);
}
});
setGlossaryDetails((pre) => ({ ...pre, options: glossaryTermList }));
} catch (error) {
setGlossaryDetails((pre) => ({ ...pre, isError: true, options: [] }));
} finally {
setGlossaryDetails((pre) => ({ ...pre, isLoading: false }));
}
}
};
const showNoDataPlaceholder = useMemo(
() => !showAddTagButton && isEmpty(tags?.[tagType]),
[showAddTagButton, tags?.[tagType]]
);
const getUpdatedTags = (selectedTag: string[]): EntityTags[] => {
const updatedTags = selectedTag.map((t) => ({
tagFQN: t,
source: [...tagDetails.options, ...glossaryDetails.options].find(
(tag) => tag.fqn === t
)?.source,
}));
return updatedTags;
};
const handleSave = (data: string[]) => {
const updatedTags = getUpdatedTags(data);
if (onSelectionChange) {
onSelectionChange([
...updatedTags,
...((isGlossaryType
? tags?.[TagSource.Classification]
: tags?.[TagSource.Glossary]) ?? []),
]);
}
form.resetFields();
setIsEditTags(false);
};
const handleCancel = useCallback(() => {
setIsEditTags(false);
form.resetFields();
}, [form]);
const handleAddClick = useCallback(() => {
if (isGlossaryType) {
fetchGlossaryList();
} else {
fetchTags();
}
setIsEditTags(true);
}, [isGlossaryType, fetchGlossaryList, fetchTags]);
const addTagButton = useMemo(
() =>
showAddTagButton ? (
<span onClick={handleAddClick}>
<Tags
className="tw-font-semibold tw-text-primary"
startWith={TAG_START_WITH.PLUS}
tag={TAG_CONSTANT}
type="border"
/>
</span>
) : null,
[showAddTagButton, fetchTags, fetchGlossaryList]
);
const renderTags = useMemo(
() => (
<TagsViewer
isTextPlaceholder
showNoDataPlaceholder={showNoDataPlaceholder}
tags={tags?.[tagType] ?? []}
type="border"
/>
),
[showNoDataPlaceholder, tags?.[tagType]]
);
const tagsSelectContainer = useMemo(() => {
return tagDetails.isLoading || glossaryDetails.isLoading ? (
<Loader size="small" />
) : (
<TagTree
defaultValue={selectedTagsInternal ?? []}
placeholder={getTagPlaceholder(isGlossaryType)}
treeData={treeData}
onCancel={handleCancel}
onSubmit={handleSave}
/>
);
}, [
isGlossaryType,
selectedTagsInternal,
glossaryDetails,
tagDetails,
treeData,
handleCancel,
handleSave,
]);
const handleRequestTags = () => {
history.push(getRequestTagsPath(entityType as string, entityFqn as string));
};
const handleUpdateTags = () => {
history.push(getUpdateTagsPath(entityType as string, entityFqn as string));
};
const requestTagElement = useMemo(() => {
const hasTags = !isEmpty(tags?.[tagType]);
return TASK_ENTITIES.includes(entityType as EntityType) ? (
<Col>
<Button
className="p-0 flex-center"
data-testid="request-entity-tags"
size="small"
type="text"
onClick={hasTags ? handleUpdateTags : handleRequestTags}>
<Tooltip
placement="left"
title={
hasTags
? t('label.update-request-tag-plural')
: t('label.request-tag-plural')
}>
<IconRequest
className="anticon"
height={14}
name="request-tags"
style={{ color: DE_ACTIVE_COLOR }}
width={14}
/>
</Tooltip>
</Button>
</Col>
) : null;
}, [tags?.[tagType], handleUpdateTags, handleRequestTags]);
const conversationThreadElement = useMemo(
() => (
<Col>
<Button
className="p-0 flex-center"
data-testid="tag-thread"
size="small"
type="text"
onClick={() => {
if (onThreadLinkSelect) {
onThreadLinkSelect(
entityThreadLink ??
getEntityFeedLink(entityType, entityFqn, 'tags')
);
}
}}>
<Tooltip
placement="left"
title={t('label.list-entity', {
entity: t('label.conversation'),
})}>
<IconComments
height={14}
name="comments"
style={{ color: DE_ACTIVE_COLOR }}
width={14}
/>
</Tooltip>
</Button>
</Col>
),
[
entityType,
entityFqn,
entityThreadLink,
getEntityFeedLink,
onThreadLinkSelect,
]
);
useEffect(() => {
setTags(getFilterTags(selectedTags));
}, [selectedTags]);
return (
<div data-testid={isGlossaryType ? 'glossary-container' : 'tags-container'}>
<div className="d-flex justify-between m-b-xs">
<div className="d-flex items-center">
<Typography.Text className="right-panel-label">
{isGlossaryType ? t('label.glossary-term') : t('label.tag-plural')}
</Typography.Text>
{permission && !isEmpty(tags?.[tagType]) && (
<Button
className="cursor-pointer flex-center m-l-xss"
data-testid="edit-button"
icon={<EditIcon color={DE_ACTIVE_COLOR} width="14px" />}
size="small"
type="text"
onClick={handleAddClick}
/>
)}
</div>
{permission && !isVersionView && (
<Row gutter={8}>
{tagType === TagSource.Classification && requestTagElement}
{conversationThreadElement}
</Row>
)}
</div>
{!isEditTags && (
<Space wrap align="center" data-testid="entity-tags" size={4}>
{addTagButton}
{renderTags}
</Space>
)}
{isEditTags && tagsSelectContainer}
</div>
);
};
export default TagsContainerV1;

View File

@ -18,6 +18,7 @@ import { ReactElement } from 'react';
export type TagsContainerV2Props = {
permission: boolean;
isVersionView?: boolean;
selectedTags: EntityTags[];
entityType?: string;
entityThreadLink?: string;
@ -27,6 +28,6 @@ export type TagsContainerV2Props = {
showBottomEditButton?: boolean;
showInlineEditButton?: boolean;
children?: ReactElement;
onSelectionChange: (selectedTags: EntityTags[]) => Promise<void>;
onSelectionChange?: (selectedTags: EntityTags[]) => Promise<void>;
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
};

View File

@ -44,6 +44,7 @@ import { TagsContainerV2Props } from './TagsContainerV2.interface';
const TagsContainerV2 = ({
permission,
isVersionView,
selectedTags,
entityType,
entityThreadLink,
@ -134,12 +135,15 @@ const TagsContainerV2 = ({
source: tagType,
}));
await onSelectionChange([
...updatedTags,
...((isGlossaryType
? tags?.[TagSource.Classification]
: tags?.[TagSource.Glossary]) ?? []),
]);
if (onSelectionChange) {
await onSelectionChange([
...updatedTags,
...((isGlossaryType
? tags?.[TagSource.Classification]
: tags?.[TagSource.Glossary]) ?? []),
]);
}
form.resetFields();
setIsEditTags(false);
};
@ -166,7 +170,6 @@ const TagsContainerV2 = ({
const renderTags = useMemo(
() => (
<TagsViewer
isTextPlaceholder
showNoDataPlaceholder={showNoDataPlaceholder}
tags={tags?.[tagType] ?? []}
type="border"
@ -289,8 +292,12 @@ const TagsContainerV2 = ({
onClick={handleAddClick}
/>
)}
{tagType === TagSource.Classification && requestTagElement}
{onThreadLinkSelect && conversationThreadElement}
{permission && !isVersionView && (
<Row gutter={8}>
{tagType === TagSource.Classification && requestTagElement}
{onThreadLinkSelect && conversationThreadElement}
</Row>
)}
</Row>
)}
</Space>
@ -302,6 +309,7 @@ const TagsContainerV2 = ({
showHeader,
isEditTags,
permission,
isVersionView,
isGlossaryType,
requestTagElement,
conversationThreadElement,

View File

@ -1,78 +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 { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Col, Form, Row, Space, TreeSelect } from 'antd';
import { useForm } from 'antd/lib/form/Form';
import classNames from 'classnames';
import React from 'react';
import { TagsTreeComponentProps } from '../TagsContainerV1/TagsContainerV1.interface';
const TagTree = ({
defaultValue,
placeholder,
treeData,
onSubmit,
onCancel,
}: TagsTreeComponentProps) => {
const [form] = useForm();
return (
<Form form={form} name="tagsForm" onFinish={(data) => onSubmit(data.tags)}>
<Row gutter={[0, 8]}>
<Col className="gutter-row d-flex justify-end" span={24}>
<Space align="center">
<Button
className="p-x-05"
data-testid="cancelAssociatedTag"
icon={<CloseOutlined size={12} />}
size="small"
onClick={onCancel}
/>
<Button
className="p-x-05"
data-testid="saveAssociatedTag"
htmlType="submit"
icon={<CheckOutlined size={12} />}
size="small"
type="primary"
/>
</Space>
</Col>
<Col className="gutter-row" span={24}>
<Form.Item noStyle name="tags">
<TreeSelect
autoFocus
multiple
showSearch
treeDefaultExpandAll
treeLine
className={classNames('w-full')}
data-testid="tag-selector"
defaultValue={defaultValue}
placeholder={placeholder}
removeIcon={
<CloseOutlined data-testid="remove-tags" height={8} width={8} />
}
showCheckedStrategy={TreeSelect.SHOW_ALL}
treeData={treeData}
treeNodeFilterProp="title"
/>
</Form.Item>
</Col>
</Row>
</Form>
);
};
export default TagTree;

View File

@ -17,6 +17,5 @@ export interface TagsViewerProps {
tags: Array<EntityTags>;
sizeCap?: number;
type?: 'label' | 'contained' | 'outlined' | 'border';
isTextPlaceholder?: boolean;
showNoDataPlaceholder?: boolean;
}

View File

@ -18,7 +18,6 @@ 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 { useTranslation } from 'react-i18next';
import { LIST_SIZE, NO_DATA_PLACEHOLDER } from '../../../constants/constants';
import { TagSource } from '../../../generated/type/tagLabel';
import { TagsViewerProps } from './tags-viewer.interface';
@ -28,10 +27,8 @@ const TagsViewer: FunctionComponent<TagsViewerProps> = ({
tags,
sizeCap = LIST_SIZE,
type = 'label',
isTextPlaceholder,
showNoDataPlaceholder = true,
}: TagsViewerProps) => {
const { t } = useTranslation();
const tagChipStye = {
margin: '0 0 8px 0',
justifyContent: 'start',
@ -65,11 +62,7 @@ const TagsViewer: FunctionComponent<TagsViewerProps> = ({
<Space wrap size={4}>
{isEmpty(sortedTagsBySource) && showNoDataPlaceholder ? (
<Typography.Text className="text-grey-muted m-r-xss">
{isTextPlaceholder
? t('label.no-entity', {
entity: t('label.tag-plural'),
})
: NO_DATA_PLACEHOLDER}
{NO_DATA_PLACEHOLDER}
</Typography.Text>
) : sizeCap > -1 ? (
<>

View File

@ -90,8 +90,8 @@ jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => {
return jest.fn().mockReturnValue(<p>RichTextEditorPreviwer</p>);
});
jest.mock('components/Tag/TagsContainerV1/TagsContainerV1', () => {
return jest.fn().mockReturnValue(<p>TagsContainerV1</p>);
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () => {
return jest.fn().mockReturnValue(<p>TagsContainerV2</p>);
});
jest.mock('components/Tag/Tags/tags', () => {

View File

@ -19,7 +19,7 @@ import DescriptionV1 from 'components/common/description/DescriptionV1';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import TopicSchemaFields from 'components/TopicDetails/TopicSchema/TopicSchema';
import { getVersionPathWithTab } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
@ -142,9 +142,8 @@ const TopicVersion: FC<TopicVersionProp> = ({
flex="220px">
<Space className="w-full" direction="vertical" size="large">
{Object.keys(TagSource).map((tagType) => (
<TagsContainerV1
<TagsContainerV2
isVersionView
showLimited
entityFqn={currentVersionData.fullyQualifiedName}
entityType={EntityType.TOPIC}
key={tagType}

View File

@ -60,10 +60,10 @@ jest.mock('components/common/description/Description', () => {
.mockReturnValue(<div data-testid="description">Description</div>);
});
jest.mock('components/Tag/TagsContainerV1/TagsContainerV1', () => {
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () => {
return jest
.fn()
.mockReturnValue(<div data-testid="entity-page-info">TagsContainerV1</div>);
.mockReturnValue(<div data-testid="entity-page-info">TagsContainerV2</div>);
});
jest.mock('components/FeedEditor/FeedEditor', () => {

View File

@ -13,18 +13,8 @@
import { AxiosError } from 'axios';
import { ModifiedGlossaryTerm } from 'components/Glossary/GlossaryTermTab/GlossaryTermTab.interface';
import {
GlossaryTermDetailsProps,
HierarchyTagsProps,
} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface';
import { API_RES_MAX_SIZE, PAGE_SIZE_LARGE } from 'constants/constants';
import { TagSource } from 'generated/type/tagLabel';
import { isUndefined, omit } from 'lodash';
import {
getGlossariesList,
getGlossaryTerms,
ListGlossaryTermsParams,
} from 'rest/glossaryAPI';
import { ListGlossaryTermsParams } from 'rest/glossaryAPI';
import { searchData } from 'rest/miscAPI';
import { WILD_CARD_CHAR } from '../constants/char.constants';
import { SearchIndex } from '../enums/search.enum';
@ -237,86 +227,3 @@ export const formatRelatedTermOptions = (
}))
: [];
};
export const getGlossaryTermHierarchy = (
data: GlossaryTermDetailsProps[]
): HierarchyTagsProps[] => {
const nodes: Record<string, HierarchyTagsProps> = {};
const tree: HierarchyTagsProps[] = [];
data.forEach((obj) => {
if (obj.fqn) {
nodes[obj.fqn] = {
title: obj.name,
value: obj.fqn,
key: obj.fqn,
selectable: true,
children: [],
};
const parentNode =
obj.parent &&
obj.parent.fullyQualifiedName &&
nodes[obj.parent.fullyQualifiedName];
parentNode && nodes[obj.fqn] && parentNode.children?.push(nodes[obj.fqn]);
if (!parentNode) {
const glossaryName = obj.glossary.name ?? '';
const existInTree = tree.find((item) => item.title === glossaryName);
if (existInTree) {
nodes[glossaryName].children?.push(nodes[obj.fqn]);
} else {
nodes[glossaryName] = {
title: glossaryName,
value: obj.glossary.fullyQualifiedName ?? '',
key: obj.glossary.fullyQualifiedName ?? '',
selectable: false,
children: [],
};
nodes[glossaryName].children?.push(nodes[obj.fqn]);
tree.push(nodes[glossaryName]);
}
}
}
});
return tree;
};
export const getGlossaryTermsList = async () => {
try {
const glossaryTermList: GlossaryTermDetailsProps[] = [];
const { data } = await getGlossariesList({
limit: PAGE_SIZE_LARGE,
});
const promises = data.map((item) =>
getGlossaryTerms({
glossary: item.id,
limit: API_RES_MAX_SIZE,
fields: 'children,parent',
})
);
const response = await Promise.allSettled(promises);
response.forEach((res) => {
if (res.status === 'fulfilled') {
glossaryTermList.push(
...res.value.data.map((data) => ({
name: data.name,
fqn: data.fullyQualifiedName ?? '',
children: data.children,
parent: data.parent,
glossary: data.glossary,
source: TagSource.Glossary,
}))
);
}
});
return Promise.resolve(glossaryTermList);
} catch (error) {
return Promise.reject({ data: (error as AxiosError).response });
}
};

View File

@ -18,10 +18,6 @@ import { ReactComponent as DeleteIcon } from 'assets/svg/ic-delete.svg';
import { AxiosError } from 'axios';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
import Loader from 'components/Loader/Loader';
import {
HierarchyTagsProps,
TagsDetailsProps,
} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { getExplorePath, PAGE_SIZE } from 'constants/constants';
import { delimiterRegex } from 'constants/regex.constants';
@ -329,77 +325,6 @@ export const getUsageCountLink = (tagFQN: string) => {
});
};
export const getTagsHierarchy = (
tags: TagsDetailsProps[]
): HierarchyTagsProps[] => {
const filteredTags = tags.filter(
(tag) => !tag.fqn?.startsWith(`Tier${FQN_SEPARATOR_CHAR}Tier`)
);
let hierarchyTags: HierarchyTagsProps[] = [];
filteredTags.forEach((tags) => {
const haveParent = hierarchyTags.find(
(h) => h.title === tags?.classification?.name
);
if (haveParent) {
hierarchyTags = hierarchyTags.map((h) => {
if (h.title === tags?.classification?.name) {
return {
...h,
children: [
...h.children,
{
title: tags.name,
value: tags.fqn,
key: tags.fqn,
selectable: true,
},
],
};
} else {
return h;
}
});
} else {
hierarchyTags.push({
title: tags.classification?.name ?? '',
value: tags.classification?.name ?? '',
children: [
{
title: tags.name,
value: tags.fqn,
key: tags.fqn,
selectable: true,
},
],
key: tags.classification?.name ?? '',
selectable: false,
});
}
});
return hierarchyTags;
};
export const getAllTagsList = async () => {
try {
const tags = await getAllTagsForOptions();
return Promise.resolve(
tags.map((tag) => ({
name: tag.name,
fqn: tag.fullyQualifiedName ?? '',
classification: tag.classification,
source: TagSource.Classification,
}))
);
} catch (error) {
return Promise.reject({ data: (error as AxiosError).response });
}
};
export const getTagPlaceholder = (isGlossaryType: boolean): string =>
isGlossaryType
? i18next.t('label.search-entity', {