Fix #3348: UI: Tags dropdown does not render glossary terms consistently (#3349)

Co-authored-by: darth-coder00 <86726556+darth-coder00@users.noreply.github.com>
This commit is contained in:
Vivek Ratnavel Subramanian 2022-03-10 08:29:59 -08:00 committed by GitHub
parent f0dd85b9f7
commit 3c89a11aa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 227 additions and 190 deletions

View File

@ -13,7 +13,7 @@
import classNames from 'classnames';
import { cloneDeep } from 'lodash';
import { EditorContentRef, FormatedUsersData } from 'Models';
import { EditorContentRef, FormattedUsersData } from 'Models';
import React, { useRef, useState } from 'react';
import { UrlEntityCharRegEx } from '../../constants/regex.constants';
import { PageLayoutType } from '../../enums/layout.enum';
@ -52,13 +52,13 @@ const AddGlossary = ({
const [name, setName] = useState('');
const [description] = useState<string>('');
const [showRevieweModal, setShowRevieweModal] = useState(false);
const [reviewer, setReviewer] = useState<Array<FormatedUsersData>>([]);
const [reviewer, setReviewer] = useState<Array<FormattedUsersData>>([]);
const onReviewerModalCancel = () => {
setShowRevieweModal(false);
};
const handleReviewerSave = (reviewer: Array<FormatedUsersData>) => {
const handleReviewerSave = (reviewer: Array<FormattedUsersData>) => {
setReviewer(reviewer);
onReviewerModalCancel();
};

View File

@ -15,8 +15,8 @@ import classNames from 'classnames';
import { cloneDeep, isEmpty, isUndefined } from 'lodash';
import {
EditorContentRef,
FormatedGlossaryTermData,
FormatedUsersData,
FormattedGlossaryTermData,
FormattedUsersData,
} from 'Models';
import React, { useEffect, useRef, useState } from 'react';
import { PageLayoutType } from '../../enums/layout.enum';
@ -68,16 +68,16 @@ const AddGlossaryTerm = ({
const [description] = useState<string>('');
const [showRevieweModal, setShowRevieweModal] = useState(false);
const [showRelatedTermsModal, setShowRelatedTermsModal] = useState(false);
const [reviewer, setReviewer] = useState<Array<FormatedUsersData>>([]);
const [reviewer, setReviewer] = useState<Array<FormattedUsersData>>([]);
const [relatedTerms, setRelatedTerms] = useState<
Array<FormatedGlossaryTermData>
Array<FormattedGlossaryTermData>
>([]);
const [synonyms, setSynonyms] = useState('');
const [references, setReferences] = useState<TermReference[]>([]);
useEffect(() => {
if (glossaryData?.reviewers && glossaryData?.reviewers.length) {
setReviewer(glossaryData?.reviewers as FormatedUsersData[]);
setReviewer(glossaryData?.reviewers as FormattedUsersData[]);
}
}, [glossaryData]);
@ -85,7 +85,7 @@ const AddGlossaryTerm = ({
setShowRelatedTermsModal(false);
};
const handleRelatedTermsSave = (terms: Array<FormatedGlossaryTermData>) => {
const handleRelatedTermsSave = (terms: Array<FormattedGlossaryTermData>) => {
setRelatedTerms(terms);
onRelatedTermsModalCancel();
};
@ -94,7 +94,7 @@ const AddGlossaryTerm = ({
setShowRevieweModal(false);
};
const handleReviewerSave = (reviewer: Array<FormatedUsersData>) => {
const handleReviewerSave = (reviewer: Array<FormattedUsersData>) => {
setReviewer(reviewer);
onReviewerModalCancel();
};

View File

@ -13,7 +13,7 @@
import classNames from 'classnames';
import { compare } from 'fast-json-patch';
import { EntityTags } from 'Models';
import { EntityTags, TagOption } from 'Models';
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useAuthContext } from '../../auth-provider/AuthProvider';
@ -50,6 +50,10 @@ import RequestDescriptionModal from '../Modals/RequestDescriptionModal/RequestDe
import TagsContainer from '../tags-container/tags-container';
import Tags from '../tags/tags';
import { ChartType, DashboardDetailsProps } from './DashboardDetails.interface';
import {
fetchGlossaryTerms,
getGlossaryTermlist,
} from '../../utils/GlossaryUtils';
const DashboardDetails = ({
entityName,
@ -104,7 +108,8 @@ const DashboardDetails = ({
chart: ChartType;
index: number;
}>();
const [tagList, setTagList] = useState<Array<string>>([]);
const [tagList, setTagList] = useState<Array<TagOption>>([]);
const [tagFetchFailed, setTagFetchFailed] = useState<boolean>(false);
const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
const [threadLink, setThreadLink] = useState<string>('');
const [selectedField, setSelectedField] = useState<string>('');
@ -292,10 +297,7 @@ const DashboardDetails = ({
}
};
const handleChartTagSelection = (
selectedTags?: Array<EntityTags>,
allTags?: Array<string>
) => {
const handleChartTagSelection = (selectedTags?: Array<EntityTags>) => {
if (selectedTags && editChartTags) {
const prevTags = editChartTags.chart.tags?.filter((tag) =>
selectedTags.some((selectedTag) => selectedTag.tagFQN === tag.tagFQN)
@ -310,7 +312,7 @@ const DashboardDetails = ({
.map((tag) => ({
labelType: 'Manual',
state: 'Confirmed',
source: (allTags || []).includes(tag.tagFQN) ? 'Tag' : 'Glossary',
source: tag.source,
tagFQN: tag.tagFQN,
}));
@ -330,11 +332,30 @@ const DashboardDetails = ({
}
};
const fetchTags = () => {
const fetchTagsAndGlossaryTerms = () => {
setIsTagLoading(true);
getTagCategories()
.then((res) => {
setTagList(getTaglist(res.data));
Promise.all([getTagCategories(), fetchGlossaryTerms()])
.then((values) => {
let tagsAndTerms: TagOption[] = [];
if (values[0].data) {
tagsAndTerms = getTaglist(values[0].data).map((tag) => {
return { fqn: tag, source: 'Tag' };
});
}
if (values[1] && values[1].length > 0) {
const glossaryTerms: TagOption[] = getGlossaryTermlist(values[1]).map(
(tag) => {
return { fqn: tag, source: 'Glossary' };
}
);
tagsAndTerms = [...tagsAndTerms, ...glossaryTerms];
}
setTagList(tagsAndTerms);
setTagFetchFailed(false);
})
.catch(() => {
setTagList([]);
setTagFetchFailed(true);
})
.finally(() => {
setIsTagLoading(false);
@ -505,7 +526,10 @@ const DashboardDetails = ({
className="tw-group tw-relative tableBody-cell"
onClick={() => {
if (!editChartTags) {
fetchTags();
// Fetch tags and terms only once
if (tagList.length === 0 || tagFetchFailed) {
fetchTagsAndGlossaryTerms();
}
handleEditChartTag(chart, index);
}
}}>
@ -532,7 +556,6 @@ const DashboardDetails = ({
position="left"
trigger="click">
<TagsContainer
allowGlossary
editable={editChartTags?.index === index}
isLoading={
isTagLoading &&
@ -546,7 +569,7 @@ const DashboardDetails = ({
handleChartTagSelection();
}}
onSelectionChange={(tags) => {
handleChartTagSelection(tags, tagList);
handleChartTagSelection(tags);
}}>
{chart.tags?.length ? (
<button

View File

@ -13,7 +13,7 @@
import classNames from 'classnames';
import { cloneDeep, isNil, isUndefined, lowerCase } from 'lodash';
import { EntityFieldThreads, EntityTags } from 'Models';
import { EntityFieldThreads, EntityTags, TagOption } from 'Models';
import React, { Fragment, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useExpanded, useTable } from 'react-table';
@ -52,6 +52,10 @@ import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPr
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
import TagsContainer from '../tags-container/tags-container';
import Tags from '../tags/tags';
import {
fetchGlossaryTerms,
getGlossaryTermlist,
} from '../../utils/GlossaryUtils';
type Props = {
owner: Table['owner'];
@ -141,16 +145,34 @@ const EntityTable = ({
index: number;
}>();
const [allTags, setAllTags] = useState<Array<string>>([]);
const [allTags, setAllTags] = useState<Array<TagOption>>([]);
const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
const [tagFetchFailed, setTagFetchFailed] = useState<boolean>(false);
const fetchTags = () => {
const fetchTagsAndGlossaryTerms = () => {
setIsTagLoading(true);
getTagCategories()
.then((res) => {
if (res.data) {
setAllTags(getTaglist(res.data));
Promise.all([getTagCategories(), fetchGlossaryTerms()])
.then((values) => {
let tagsAndTerms: TagOption[] = [];
if (values[0].data) {
tagsAndTerms = getTaglist(values[0].data).map((tag) => {
return { fqn: tag, source: 'Tag' };
});
}
if (values[1] && values[1].length > 0) {
const glossaryTerms: TagOption[] = getGlossaryTermlist(values[1]).map(
(tag) => {
return { fqn: tag, source: 'Glossary' };
}
);
tagsAndTerms = [...tagsAndTerms, ...glossaryTerms];
}
setAllTags(tagsAndTerms);
setTagFetchFailed(false);
})
.catch(() => {
setAllTags([]);
setTagFetchFailed(true);
})
.finally(() => {
setIsTagLoading(false);
@ -189,22 +211,24 @@ const EntityTable = ({
const updateColumnTags = (
tableCols: ModifiedTableColumn[],
changedColName: string,
newColumnTags: Array<string>
newColumnTags: Array<TagOption>
) => {
const getUpdatedTags = (column: Column) => {
const prevTags = column?.tags?.filter((tag) => {
return newColumnTags.includes(tag?.tagFQN as string);
return newColumnTags
.map((tag) => tag.fqn)
.includes(tag?.tagFQN as string);
});
const newTags: Array<EntityTags> = newColumnTags
.filter((tag) => {
return !prevTags?.map((prevTag) => prevTag.tagFQN).includes(tag);
return !prevTags?.map((prevTag) => prevTag.tagFQN).includes(tag.fqn);
})
.map((tag) => ({
labelType: LabelType.Manual,
state: State.Confirmed,
source: allTags.includes(tag) ? 'Tag' : 'Glossary',
tagFQN: tag,
source: tag.source,
tagFQN: tag.fqn,
}));
const updatedTags = [...(prevTags as TagLabel[]), ...newTags];
@ -240,7 +264,11 @@ const EntityTable = ({
};
const handleTagSelection = (selectedTags?: Array<EntityTags>) => {
const newSelectedTags = selectedTags?.map((tag) => tag.tagFQN);
const newSelectedTags: TagOption[] | undefined = selectedTags?.map(
(tag) => {
return { fqn: tag.tagFQN, source: tag.source };
}
);
if (newSelectedTags && editColumnTag) {
const tableCols = cloneDeep(tableColumns);
updateColumnTags(tableCols, editColumnTag.column.name, newSelectedTags);
@ -485,7 +513,10 @@ const EntityTable = ({
onClick={() => {
if (!editColumnTag) {
handleEditColumnTag(row.original, row.id);
fetchTags();
// Fetch tags and terms only once
if (allTags.length === 0 || tagFetchFailed) {
fetchTagsAndGlossaryTerms();
}
}
}}>
<NonAdminAction
@ -495,7 +526,6 @@ const EntityTable = ({
position="left"
trigger="click">
<TagsContainer
allowGlossary
editable={editColumnTag?.index === row.id}
isLoading={
isTagLoading &&

View File

@ -13,7 +13,7 @@
import classNames from 'classnames';
import { cloneDeep, includes, isEqual } from 'lodash';
import { EntityTags, FormatedUsersData } from 'Models';
import { EntityTags, FormattedUsersData } from 'Models';
import React, { useEffect, useState } from 'react';
import {
LIST_SIZE,
@ -50,7 +50,7 @@ const GlossaryDetails = ({ isHasAccess, glossary, updateGlossary }: props) => {
const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
const [showRevieweModal, setShowRevieweModal] = useState(false);
const [reviewer, setReviewer] = useState<Array<FormatedUsersData>>([]);
const [reviewer, setReviewer] = useState<Array<FormattedUsersData>>([]);
const tabs = [
{
@ -70,7 +70,7 @@ const GlossaryDetails = ({ isHasAccess, glossary, updateGlossary }: props) => {
setShowRevieweModal(false);
};
const handleReviewerSave = (data: Array<FormatedUsersData>) => {
const handleReviewerSave = (data: Array<FormattedUsersData>) => {
if (!isEqual(data, reviewer)) {
let updatedGlossary = cloneDeep(glossary);
const oldReviewer = data.filter((d) => includes(reviewer, d));
@ -171,7 +171,7 @@ const GlossaryDetails = ({ isHasAccess, glossary, updateGlossary }: props) => {
if (glossary.reviewers && glossary.reviewers.length) {
setReviewer(
glossary.reviewers.map((d) => ({
...(d as FormatedUsersData),
...(d as FormattedUsersData),
type: 'user',
}))
);

View File

@ -15,8 +15,8 @@ import classNames from 'classnames';
import { cloneDeep, includes, isEqual } from 'lodash';
import {
EntityTags,
FormatedGlossaryTermData,
FormatedUsersData,
FormattedGlossaryTermData,
FormattedUsersData,
GlossaryTermAssets,
} from 'Models';
import React, { Fragment, useEffect, useState } from 'react';
@ -73,7 +73,7 @@ const GlossaryTermsV1 = ({
);
const [references, setReferences] = useState(glossaryTerm.references || []);
const [reviewer, setReviewer] = useState<Array<FormatedUsersData>>([]);
const [relatedTerms, setRelatedTerms] = useState<FormatedGlossaryTermData[]>(
const [relatedTerms, setRelatedTerms] = useState<FormattedGlossaryTermData[]>(
[]
);
@ -125,7 +125,7 @@ const GlossaryTermsV1 = ({
setShowRevieweModal(false);
};
const handleReviewerSave = (data: Array<FormatedUsersData>) => {
const handleReviewerSave = (data: Array<FormattedUsersData>) => {
if (!isEqual(data, reviewer)) {
let updatedGlossaryTerm = cloneDeep(glossaryTerm);
const oldReviewer = data.filter((d) => includes(reviewer, d));
@ -277,7 +277,7 @@ const GlossaryTermsV1 = ({
if (glossaryTerm.reviewers && glossaryTerm.reviewers.length) {
setReviewer(
glossaryTerm.reviewers.map((d) => ({
...(d as FormatedUsersData),
...(d as FormattedUsersData),
type: 'user',
}))
);

View File

@ -12,7 +12,7 @@
*/
import { isUndefined } from 'lodash';
import { FormatedGlossaryTermData, SearchResponse } from 'Models';
import { FormattedGlossaryTermData, SearchResponse } from 'Models';
import React, { useEffect, useState } from 'react';
import { searchData } from '../../../axiosAPIs/miscAPI';
import { PAGE_SIZE } from '../../../constants/constants';
@ -24,9 +24,9 @@ import Searchbar from '../../common/searchbar/Searchbar';
import Loader from '../../Loader/Loader';
type RelatedTermsModalProp = {
relatedTerms?: Array<FormatedGlossaryTermData>;
relatedTerms?: Array<FormattedGlossaryTermData>;
onCancel: () => void;
onSave: (terms: Array<FormatedGlossaryTermData>) => void;
onSave: (terms: Array<FormattedGlossaryTermData>) => void;
header: string;
};
@ -38,14 +38,14 @@ const RelatedTermsModal = ({
}: RelatedTermsModalProp) => {
const [searchText, setSearchText] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [options, setOptions] = useState<FormatedGlossaryTermData[]>([]);
const [options, setOptions] = useState<FormattedGlossaryTermData[]>([]);
const [selectedOption, setSelectedOption] = useState<
FormatedGlossaryTermData[]
FormattedGlossaryTermData[]
>(relatedTerms ?? []);
const getSearchedTerms = (searchedData: FormatedGlossaryTermData[]) => {
const getSearchedTerms = (searchedData: FormattedGlossaryTermData[]) => {
const currOptions = selectedOption.map((item) => item.fqdn || item.name);
const data = searchedData.filter((item: FormatedGlossaryTermData) => {
const data = searchedData.filter((item: FormattedGlossaryTermData) => {
return !currOptions.includes(item.fqdn);
});
@ -81,8 +81,8 @@ const RelatedTermsModal = ({
if (!isChecked) {
setSelectedOption((pre) => pre.filter((option) => option.id !== id));
} else {
const newOption: FormatedGlossaryTermData =
options.find((d) => d.id === id) || ({} as FormatedGlossaryTermData);
const newOption: FormattedGlossaryTermData =
options.find((d) => d.id === id) || ({} as FormattedGlossaryTermData);
setSelectedOption([...selectedOption, newOption]);
}
};

View File

@ -13,7 +13,7 @@
import { AxiosResponse } from 'axios';
import { isUndefined } from 'lodash';
import { FormatedUsersData, SearchResponse } from 'Models';
import { FormattedUsersData, SearchResponse } from 'Models';
import React, { useEffect, useState } from 'react';
import { getSuggestions, searchData } from '../../../axiosAPIs/miscAPI';
import { WILD_CARD_CHAR } from '../../../constants/char.constants';
@ -25,9 +25,9 @@ import Searchbar from '../../common/searchbar/Searchbar';
import Loader from '../../Loader/Loader';
type ReviewerModalProp = {
reviewer?: Array<FormatedUsersData>;
reviewer?: Array<FormattedUsersData>;
onCancel: () => void;
onSave: (reviewer: Array<FormatedUsersData>) => void;
onSave: (reviewer: Array<FormattedUsersData>) => void;
header: string;
};
@ -39,14 +39,14 @@ const ReviewerModal = ({
}: ReviewerModalProp) => {
const [searchText, setSearchText] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [options, setOptions] = useState<FormatedUsersData[]>([]);
const [selectedOption, setSelectedOption] = useState<FormatedUsersData[]>(
const [options, setOptions] = useState<FormattedUsersData[]>([]);
const [selectedOption, setSelectedOption] = useState<FormattedUsersData[]>(
reviewer ?? []
);
const getSearchedReviewers = (searchedData: FormatedUsersData[]) => {
const getSearchedReviewers = (searchedData: FormattedUsersData[]) => {
const currOptions = selectedOption.map((item) => item.name);
const data = searchedData.filter((item: FormatedUsersData) => {
const data = searchedData.filter((item: FormattedUsersData) => {
return !currOptions.includes(item.name);
});
@ -98,8 +98,8 @@ const ReviewerModal = ({
if (!isChecked) {
setSelectedOption((pre) => pre.filter((option) => option.id !== id));
} else {
const newOption: FormatedUsersData =
options.find((d) => d.id === id) || ({} as FormatedUsersData);
const newOption: FormattedUsersData =
options.find((d) => d.id === id) || ({} as FormattedUsersData);
setSelectedOption([...selectedOption, newOption]);
}
};

View File

@ -13,7 +13,13 @@
import classNames from 'classnames';
import { isEmpty, isUndefined } from 'lodash';
import { EntityFieldThreads, EntityTags, ExtraInfo, TableDetail } from 'Models';
import {
EntityFieldThreads,
EntityTags,
ExtraInfo,
TableDetail,
TagOption,
} from 'Models';
import React, { Fragment, useEffect, useState } from 'react';
import { FOLLOWERS_VIEW_CAP, LIST_SIZE } from '../../../constants/constants';
import { Operation } from '../../../generated/entity/policies/accessControl/rule';
@ -32,6 +38,10 @@ import PopOver from '../popover/PopOver';
import TitleBreadcrumb from '../title-breadcrumb/title-breadcrumb.component';
import { TitleBreadcrumbProps } from '../title-breadcrumb/title-breadcrumb.interface';
import FollowersModal from './FollowersModal';
import {
fetchGlossaryTerms,
getGlossaryTermlist,
} from '../../../utils/GlossaryUtils';
type Props = {
titleLinks: TitleBreadcrumbProps['titleLinks'];
@ -85,7 +95,8 @@ const EntityPageInfo = ({
const [entityFollowers, setEntityFollowers] =
useState<Array<User>>(followersList);
const [isViewMore, setIsViewMore] = useState<boolean>(false);
const [tagList, setTagList] = useState<Array<string>>([]);
const [tagList, setTagList] = useState<Array<TagOption>>([]);
const [tagFetchFailed, setTagFetchFailed] = useState<boolean>(false);
const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
const handleTagSelection = (selectedTags?: Array<EntityTags>) => {
@ -105,7 +116,7 @@ const EntityPageInfo = ({
.map((tag) => ({
labelType: LabelType.Manual,
state: State.Confirmed,
source: tagList.includes(tag.tagFQN) ? 'Tag' : 'Glossary',
source: tag.source,
tagFQN: tag.tagFQN,
}));
tagsHandler?.([...prevTags, ...newTags]);
@ -206,11 +217,31 @@ const EntityPageInfo = ({
</div>
);
};
const fetchTags = () => {
const fetchTagsAndGlossaryTerms = () => {
setIsTagLoading(true);
getTagCategories()
.then((res) => {
setTagList(getTaglist(res.data));
Promise.all([getTagCategories(), fetchGlossaryTerms()])
.then((values) => {
let tagsAndTerms: TagOption[] = [];
if (values[0].data) {
tagsAndTerms = getTaglist(values[0].data).map((tag) => {
return { fqn: tag, source: 'Tag' };
});
}
if (values[1] && values[1].length > 0) {
const glossaryTerms: TagOption[] = getGlossaryTermlist(values[1]).map(
(tag) => {
return { fqn: tag, source: 'Glossary' };
}
);
tagsAndTerms = [...tagsAndTerms, ...glossaryTerms];
}
setTagList(tagsAndTerms);
setTagFetchFailed(false);
})
.catch(() => {
setTagList([]);
setTagFetchFailed(true);
})
.finally(() => {
setIsTagLoading(false);
@ -392,11 +423,13 @@ const EntityPageInfo = ({
<div
className="tw-inline-block"
onClick={() => {
fetchTags();
// Fetch tags and terms only once
if (tagList.length === 0 || tagFetchFailed) {
fetchTagsAndGlossaryTerms();
}
setIsEditable(true);
}}>
<TagsContainer
allowGlossary
editable={isEditable}
isLoading={isTagLoading}
selectedTags={getSelectedTags()}

View File

@ -12,6 +12,7 @@
*/
import React from 'react';
import { TagOption } from 'Models';
export enum DropDownType {
LINK = 'link',
@ -21,7 +22,7 @@ export enum DropDownType {
export type DropDownListItem = {
name: string | React.ReactElement;
value?: string;
value?: string | TagOption;
group?: string;
to?: string;
disabled?: boolean;

View File

@ -11,17 +11,16 @@
* limitations under the License.
*/
import { EntityTags } from 'Models';
import { EntityTags, TagOption } from 'Models';
import { ReactNode } from 'react';
import { TagProps } from '../tags/tags.interface';
export type TagsContainerProps = {
allowGlossary?: boolean;
children?: ReactNode;
editable?: boolean;
dropDownHorzPosRight?: boolean;
selectedTags: Array<EntityTags>;
tagList: Array<string>;
tagList: Array<TagOption>;
type?: TagProps['type'];
showTags?: boolean;
onSelectionChange: (selectedTags: Array<EntityTags>) => void;

View File

@ -15,7 +15,11 @@ import { getByTestId, render } from '@testing-library/react';
import React from 'react';
import TagsContainer from './tags-container';
const tagList = ['tag 1', 'tag 2', 'tag 3'];
const tagList = [
{ fqn: 'tag 1', source: 'Tag' },
{ fqn: 'tag 2', source: 'Tag' },
{ fqn: 'tag 3', source: 'Glossary' },
];
const onCancel = jest.fn();
const onSelectionChange = jest.fn();
@ -38,7 +42,7 @@ describe('Test TagsContainer Component', () => {
onSelectionChange={onSelectionChange}
/>
);
const TagContainer = getByTestId(container, 'tag-conatiner');
const TagContainer = getByTestId(container, 'tag-container');
expect(TagContainer).toBeInTheDocument();
});

View File

@ -12,20 +12,10 @@
*/
import classNames from 'classnames';
import { debounce, isEmpty, isNull } from 'lodash';
import { EntityTags, FormatedGlossaryTermData, SearchResponse } from 'Models';
import React, {
FunctionComponent,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { searchData } from '../../axiosAPIs/miscAPI';
import { PAGE_SIZE } from '../../constants/constants';
import { SearchIndex } from '../../enums/search.enum';
import { isNull } from 'lodash';
import { EntityTags } from 'Models';
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
import { withLoader } from '../../hoc/withLoader';
import { formatSearchGlossaryTermResponse } from '../../utils/APIUtils';
import { Button } from '../buttons/Button/Button';
import DropDownList from '../dropdown/DropDownList';
import Tags from '../tags/tags';
@ -36,7 +26,6 @@ import { TagsContainerProps } from './tags-container.interface';
// const INPUT_AUTO = 'auto';
const TagsContainer: FunctionComponent<TagsContainerProps> = ({
allowGlossary,
children,
editable,
selectedTags,
@ -53,10 +42,6 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
const inputRef = useRef<HTMLInputElement>(null);
const node = useRef<HTMLDivElement>(null);
const [inputDomRect, setInputDomRect] = useState<DOMRect>();
const [glossaryList, setGlossaryList] = useState<FormatedGlossaryTermData[]>(
[]
);
const [glossaryLoading, setGlossaryLoading] = useState<boolean>(false);
// const [inputWidth, setInputWidth] = useState(INPUT_COLLAPED);
// const [inputMinWidth, setInputMinWidth] = useState(INPUT_AUTO);
@ -84,63 +69,20 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
}
}, [newTag]);
const fetchGlossaryResults = (
searchText: string
): Promise<FormatedGlossaryTermData[]> => {
return new Promise<FormatedGlossaryTermData[]>((resolve, reject) => {
if (!isEmpty(searchText)) {
searchData(searchText, 1, PAGE_SIZE, '', '', '', SearchIndex.GLOSSARY)
.then((res: SearchResponse) => {
const data = formatSearchGlossaryTermResponse(
res?.data?.hits?.hits || []
);
resolve(data);
})
.catch(() => reject());
}
});
};
const getGlossaryResults = useCallback(
(searchText: string) => {
if (allowGlossary) {
fetchGlossaryResults(searchText)
.then((res) => setGlossaryList(res))
.finally(() => {
setGlossaryLoading(false);
});
}
},
[allowGlossary]
);
const getTagList = () => {
const newTags = tagList
.filter((tag) => {
return !tags.some((selectedTag) => selectedTag.tagFQN === tag);
return !tags.some((selectedTag) => selectedTag.tagFQN === tag.fqn);
})
.filter((tag) => !tag.includes('Tier'))
.filter((tag) => !tag.fqn?.includes('Tier'))
.map((tag) => {
return {
name: tag,
value: tag,
name: tag.fqn,
value: tag.fqn,
};
});
const newGlossaries = glossaryList
.filter((glossary) => {
return !tags.some(
(selectedTag) => selectedTag.tagFQN === glossary.fqdn
);
})
.map((glossary) => {
return {
name: glossary.fqdn,
value: glossary.fqdn,
};
});
return allowGlossary ? [...newTags, ...newGlossaries] : newTags;
return newTags;
};
const handleTagSelection = (
@ -151,13 +93,9 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
event.stopPropagation();
if (selectedTag) {
setTags((arrTags) => {
const tag =
arrTags.filter((tag) => tag.tagFQN === selectedTag)[0] || {};
if (!isEmpty(tag)) {
return [...arrTags, { ...tag, tagFQN: selectedTag }];
} else {
return [...arrTags, { tagFQN: selectedTag }];
}
const source = tagList.find((tag) => tag.fqn === selectedTag)?.source;
return [...arrTags, { tagFQN: selectedTag, source }];
});
}
setNewTag('');
@ -206,26 +144,9 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
);
};
const debouncedOnSearch = useCallback(
(searchText: string): void => {
getGlossaryResults(searchText);
},
[getGlossaryResults]
);
const debounceOnSearch = useCallback(debounce(debouncedOnSearch, 500), [
debouncedOnSearch,
]);
const handleChange = (e: React.ChangeEvent<{ value: string }>): void => {
const searchText = e.target.value;
setNewTag(searchText);
if (allowGlossary && searchText) {
setGlossaryLoading(true);
debounceOnSearch(searchText);
} else {
setGlossaryLoading(false);
}
};
const handleClick = (e: MouseEvent) => {
@ -262,7 +183,7 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
{ 'tw-border-primary': hasFocus },
{ 'hover:tw-border-main': !hasFocus }
)}
data-testid="tag-conatiner"
data-testid="tag-container"
ref={node}
onClick={(event) => {
if (editable) {
@ -303,7 +224,6 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
domPosition={inputDomRect}
dropDownList={getTagList()}
horzPosRight={dropDownHorzPosRight}
isLoading={glossaryLoading}
searchString={newTag}
widthClass="tw-w-80"
onSelect={handleTagSelection}

View File

@ -31,7 +31,6 @@ const Tags: FunctionComponent<TagProps> = ({
const baseStyle = tagStyles.base;
const layoutStyles = tagStyles[type];
const textBaseStyle = tagStyles.text.base;
const textLayoutStyles = tagStyles.text[type] || tagStyles.text.default;
const textEditStyles = editable ? tagStyles.text.editable : '';
const getTagString = (tag: string) => {
@ -43,12 +42,7 @@ const Tags: FunctionComponent<TagProps> = ({
<span
className={classNames(baseStyle, layoutStyles, className)}
data-testid="tags">
<span
className={classNames(
textBaseStyle,
textLayoutStyles,
textEditStyles
)}>
<span className={classNames(textBaseStyle, textEditStyles)}>
{`${startWith}${tag}`}
</span>
{editable && isRemovable && (

View File

@ -209,7 +209,7 @@ declare module 'Models' {
entityType?: string;
};
export type FormatedUsersData = {
export type FormattedUsersData = {
name: string;
displayName: string;
email: string;
@ -217,7 +217,7 @@ declare module 'Models' {
id: string;
};
export type FormatedGlossaryTermData = {
export type FormattedGlossaryTermData = {
name: string;
displayName: string;
fqdn: string;
@ -226,7 +226,12 @@ declare module 'Models' {
description?: string;
};
export interface FormatedGlossarySuggestion {
export type TagOption = {
fqn: string;
source: string;
};
export interface FormattedGlossarySuggestion {
deleted: boolean;
description: string;
display_name: string;
@ -244,7 +249,7 @@ declare module 'Models' {
_type?: string;
_id?: string;
_score?: number;
_source: FormatedGlossarySuggestion;
_source: FormattedGlossarySuggestion;
}
export interface GlossaryTermAssets {

View File

@ -15,7 +15,7 @@ import { AxiosError, AxiosResponse } from 'axios';
import { compare } from 'fast-json-patch';
import { cloneDeep, extend } from 'lodash';
import {
FormatedGlossarySuggestion,
FormattedGlossarySuggestion,
GlossarySuggestionHit,
GlossaryTermAssets,
LoadingState,
@ -229,7 +229,7 @@ const GlossaryPageV1 = () => {
const getSearchedGlossaries = (
arrGlossaries: ModifiedGlossaryData[],
newGlossaries: string[],
searchedTerms: FormatedGlossarySuggestion[]
searchedTerms: FormattedGlossarySuggestion[]
) => {
if (newGlossaries.length) {
let arrNewData: ModifiedGlossaryData[] = [];
@ -269,7 +269,7 @@ const GlossaryPageV1 = () => {
SearchIndex.GLOSSARY
).then((res: AxiosResponse) => {
if (res.data) {
const searchedTerms: FormatedGlossarySuggestion[] =
const searchedTerms: FormattedGlossarySuggestion[] =
res.data.hits?.hits?.map(
(item: GlossarySuggestionHit) => item._source
) || [];

View File

@ -11,11 +11,20 @@
* limitations under the License.
*/
import { FormatedGlossarySuggestion } from 'Models';
import {
FormattedGlossarySuggestion,
FormattedGlossaryTermData,
SearchResponse,
} from 'Models';
import { DataNode } from 'rc-tree/lib/interface';
import { GlossaryTerm } from '../generated/entity/data/glossaryTerm';
import { ModifiedGlossaryData } from '../pages/GlossaryPage/GlossaryPageV1.component';
import { getNameFromFQN } from './CommonUtils';
import { searchData } from '../axiosAPIs/miscAPI';
import { SearchIndex } from '../enums/search.enum';
import { formatSearchGlossaryTermResponse } from './APIUtils';
import { WILD_CARD_CHAR } from '../constants/char.constants';
import { AxiosError } from 'axios';
export interface GlossaryTermTreeNode {
children?: GlossaryTermTreeNode[];
@ -23,6 +32,25 @@ export interface GlossaryTermTreeNode {
name: string;
}
export const fetchGlossaryTerms = (): Promise<FormattedGlossaryTermData[]> => {
return new Promise<FormattedGlossaryTermData[]>((resolve, reject) => {
searchData(WILD_CARD_CHAR, 1, 1000, '', '', '', SearchIndex.GLOSSARY)
.then((res: SearchResponse) => {
const data = formatSearchGlossaryTermResponse(
res?.data?.hits?.hits || []
);
resolve(data);
})
.catch((error: AxiosError) => reject(error.response));
});
};
export const getGlossaryTermlist = (
terms: Array<FormattedGlossaryTermData> = []
): Array<string> => {
return terms.map((term: FormattedGlossaryTermData) => term?.fqdn);
};
export const generateTreeData = (data: ModifiedGlossaryData[]): DataNode[] => {
return data.map((d) => {
return d.children?.length
@ -87,7 +115,7 @@ const optimiseGlossaryTermTree = (treeNodes?: GlossaryTermTreeNode[]) => {
};
export const getSearchedGlossaryTermTree = (
searchedTerms: FormatedGlossarySuggestion[]
searchedTerms: FormattedGlossarySuggestion[]
): GlossaryTermTreeNode[] => {
const termTree: GlossaryTermTreeNode[] = [];
for (const term of searchedTerms) {
@ -102,7 +130,7 @@ export const getSearchedGlossaryTermTree = (
export const updateGlossaryListBySearchedTerms = (
glossaries: ModifiedGlossaryData[],
searchedTerms: FormatedGlossarySuggestion[]
searchedTerms: FormattedGlossarySuggestion[]
) => {
const searchedTermTree = getSearchedGlossaryTermTree(searchedTerms);