mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-31 12:39:01 +00:00
Co-authored-by: darth-coder00 <86726556+darth-coder00@users.noreply.github.com>
This commit is contained in:
parent
f0dd85b9f7
commit
3c89a11aa2
@ -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();
|
||||
};
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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 &&
|
||||
|
@ -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',
|
||||
}))
|
||||
);
|
||||
|
@ -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',
|
||||
}))
|
||||
);
|
||||
|
@ -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]);
|
||||
}
|
||||
};
|
||||
|
@ -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]);
|
||||
}
|
||||
};
|
||||
|
@ -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()}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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}
|
||||
|
@ -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 && (
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
) || [];
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user