mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-13 01:38:13 +00:00
* UI Fixed: Styling of adding Related Terms and Synonyms is different #7671 * fixed unit test * fixed cypress config * Fixed issue Improve Assets listing of Glossary Term - P0 - 0.12.1 #7672
This commit is contained in:
parent
c6fcad2c1d
commit
51a7020b3b
@ -216,58 +216,17 @@ describe('Glossary page should work properly', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Updating data of glossary term should work properly', () => {
|
it('Updating data of glossary term should work properly', () => {
|
||||||
|
interceptURL('GET', '/api/v1/permissions/*/*', 'permissionApi');
|
||||||
|
interceptURL('GET', '/api/v1/search/query?*', 'glossaryAPI');
|
||||||
const term = NEW_GLOSSARY_TERMS.term_1.name;
|
const term = NEW_GLOSSARY_TERMS.term_1.name;
|
||||||
const uSynonyms = 'pick up,take,obtain';
|
const term2 = NEW_GLOSSARY_TERMS.term_2.name;
|
||||||
|
const uSynonyms = ['pick up', 'take', 'obtain'];
|
||||||
const newRef = { name: 'take', url: 'https://take.com' };
|
const newRef = { name: 'take', url: 'https://take.com' };
|
||||||
const newDescription = 'Updated description';
|
const newDescription = 'Updated description';
|
||||||
cy.get('#left-panelV1').should('be.visible').contains(term).click();
|
cy.get('#left-panelV1').should('be.visible').contains(term).click();
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
cy.get('[data-testid="inactive-link"]').contains(term).should('be.visible');
|
verifyResponseStatusCode('@permissionApi', 200);
|
||||||
|
verifyResponseStatusCode('@glossaryAPI', 200);
|
||||||
cy.get('[data-testid="section-synonyms"]')
|
|
||||||
.scrollIntoView()
|
|
||||||
.should('be.visible')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.get('[data-testid="synonyms"]')
|
|
||||||
.scrollIntoView()
|
|
||||||
.should('be.visible')
|
|
||||||
.as('synonyms');
|
|
||||||
cy.get('@synonyms').clear();
|
|
||||||
cy.get('@synonyms').type(uSynonyms);
|
|
||||||
|
|
||||||
cy.intercept({ method: 'PATCH', url: '/api/v1/glossaryTerms/*' }).as(
|
|
||||||
'getGlossary'
|
|
||||||
);
|
|
||||||
|
|
||||||
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
|
|
||||||
cy.wait(100);
|
|
||||||
cy.get('[data-testid="synonyms-container"]')
|
|
||||||
.as('synonyms-container')
|
|
||||||
.should('be.visible');
|
|
||||||
|
|
||||||
uSynonyms.split(',').forEach((synonym) => {
|
|
||||||
cy.get('@synonyms-container').contains(synonym).should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.wait('@getGlossary').its('response.statusCode').should('eq', 200);
|
|
||||||
|
|
||||||
// updating References
|
|
||||||
cy.get('[data-testid="section-references"] [data-testid="add-button"]')
|
|
||||||
.should('exist')
|
|
||||||
.click();
|
|
||||||
cy.get('.tw-modal-container').should('be.visible');
|
|
||||||
cy.get('[data-testid="references"] .button-comp')
|
|
||||||
.should('be.visible')
|
|
||||||
.click();
|
|
||||||
cy.get('#name-1').should('be.visible').type(newRef.name);
|
|
||||||
cy.get('#url-1').should('be.visible').type(newRef.url);
|
|
||||||
cy.get('[data-testid="saveButton"]').should('be.visible').click();
|
|
||||||
cy.get('[data-testid="references-container"]')
|
|
||||||
.contains(newRef.name)
|
|
||||||
.should('be.visible')
|
|
||||||
.invoke('attr', 'href')
|
|
||||||
.should('eq', newRef.url);
|
|
||||||
|
|
||||||
// updating tags
|
// updating tags
|
||||||
cy.get('[data-testid="tag-container"]')
|
cy.get('[data-testid="tag-container"]')
|
||||||
@ -299,37 +258,88 @@ describe('Glossary page should work properly', () => {
|
|||||||
cy.get('@description').clear();
|
cy.get('@description').clear();
|
||||||
cy.get('@description').type(newDescription);
|
cy.get('@description').type(newDescription);
|
||||||
cy.get('[data-testid="save"]').click();
|
cy.get('[data-testid="save"]').click();
|
||||||
|
|
||||||
verifyResponseStatusCode('@saveData', 200);
|
verifyResponseStatusCode('@saveData', 200);
|
||||||
|
|
||||||
cy.get('.tw-modal-container').should('not.exist');
|
cy.get('.tw-modal-container').should('not.exist');
|
||||||
|
|
||||||
cy.get('[data-testid="viewer-container"]')
|
cy.get('[data-testid="viewer-container"]')
|
||||||
.contains(newDescription)
|
.contains(newDescription)
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
});
|
|
||||||
// Todo: skipping for now as it flaky on CI
|
|
||||||
it.skip('Releted Terms should work properly', () => {
|
|
||||||
const term = NEW_GLOSSARY_TERMS.term_1.name;
|
|
||||||
const term2 = NEW_GLOSSARY_TERMS.term_2.name;
|
|
||||||
cy.get('#left-panelV1').should('be.visible').contains(term).click();
|
|
||||||
cy.wait(500);
|
|
||||||
cy.get('[data-testid="inactive-link"]').contains(term).should('be.visible');
|
cy.get('[data-testid="inactive-link"]').contains(term).should('be.visible');
|
||||||
|
|
||||||
// add releted term
|
// updating synonyms
|
||||||
cy.get('[data-testid="add-related-term-button"]')
|
cy.get('[data-testid="section-synonyms"]')
|
||||||
|
.scrollIntoView()
|
||||||
|
.should('be.visible');
|
||||||
|
|
||||||
|
cy.get('[data-testid="section-synonyms"] [data-testid="edit-button"]')
|
||||||
|
.scrollIntoView()
|
||||||
|
.should('be.visible')
|
||||||
|
.should('not.be.disabled')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('.ant-select-selector').should('be.visible');
|
||||||
|
cy.get('.ant-select-clear > .anticon > svg')
|
||||||
|
.should('exist')
|
||||||
|
.click({ force: true });
|
||||||
|
|
||||||
|
cy.get('.ant-select-selection-overflow')
|
||||||
|
.should('exist')
|
||||||
|
.type(uSynonyms.join('{enter}'));
|
||||||
|
|
||||||
|
interceptURL('PATCH', '/api/v1/glossaryTerms/*', 'getGlossary');
|
||||||
|
cy.get('[data-testid="save-btn"]').should('be.visible').click();
|
||||||
|
verifyResponseStatusCode('@getGlossary', 200);
|
||||||
|
|
||||||
|
cy.get('[data-testid="synonyms-container"]')
|
||||||
|
.as('synonyms-container')
|
||||||
|
.should('be.visible');
|
||||||
|
|
||||||
|
uSynonyms.forEach((synonym) => {
|
||||||
|
cy.get('@synonyms-container').contains(synonym).should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
// updating References
|
||||||
|
cy.get('[data-testid="section-references"] [data-testid="edit-button"]')
|
||||||
|
.should('exist')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-testid="add-button"]').should('be.visible').click();
|
||||||
|
cy.get('#references_1_name').should('be.visible').type(newRef.name);
|
||||||
|
cy.get('#references_1_endpoint').should('be.visible').type(newRef.url);
|
||||||
|
cy.get('[data-testid="save-btn"]').should('be.visible').click();
|
||||||
|
verifyResponseStatusCode('@getGlossary', 200);
|
||||||
|
cy.get('[data-testid="references-container"]')
|
||||||
|
.contains(newRef.name)
|
||||||
|
.should('be.visible')
|
||||||
|
.invoke('attr', 'href')
|
||||||
|
.should('eq', newRef.url);
|
||||||
|
|
||||||
|
// add relented term
|
||||||
|
cy.get('[data-testid="section-related-terms"]')
|
||||||
|
.scrollIntoView()
|
||||||
|
.should('be.visible');
|
||||||
|
cy.get('[data-testid="section-related-terms"] [data-testid="edit-button"]')
|
||||||
|
.scrollIntoView()
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.click();
|
.click();
|
||||||
cy.get('.tw-modal-container').should('be.visible');
|
interceptURL(
|
||||||
cy.wait(500);
|
'GET',
|
||||||
cy.get('[data-testid="user-card-container"]')
|
'/api/v1/search/query?q=*&from=0&size=10&index=glossary_search_index',
|
||||||
.first()
|
'getGlossaryTerm'
|
||||||
|
);
|
||||||
|
cy.get('.ant-select-selection-overflow').should('be.visible').click();
|
||||||
|
verifyResponseStatusCode('@getGlossaryTerm', 200);
|
||||||
|
cy.get('.ant-select-item-option-content')
|
||||||
|
.contains(term2)
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.find('[data-testid="checkboxAddUser"]')
|
.click();
|
||||||
.check();
|
|
||||||
cy.get('[data-testid="saveButton"]').should('be.visible').click();
|
interceptURL('PATCH', '/api/v1/glossaryTerms/*', 'getGlossary');
|
||||||
cy.get('.tw-modal-container').should('not.exist');
|
cy.get('[data-testid="save-btn"]').should('be.visible').click();
|
||||||
cy.get('[data-testid="related terms-card-container"]')
|
verifyResponseStatusCode('@getGlossary', 200);
|
||||||
|
|
||||||
|
cy.get('[data-testid="related-term-container"]')
|
||||||
.contains(term2)
|
.contains(term2)
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
});
|
});
|
||||||
@ -339,10 +349,8 @@ describe('Glossary page should work properly', () => {
|
|||||||
const term = NEW_GLOSSARY_TERMS.term_1.name;
|
const term = NEW_GLOSSARY_TERMS.term_1.name;
|
||||||
const entity = SEARCH_ENTITY_TABLE.table_3.term;
|
const entity = SEARCH_ENTITY_TABLE.table_3.term;
|
||||||
goToAssetsTab(term);
|
goToAssetsTab(term);
|
||||||
cy.get('.tableBody-cell')
|
cy.contains('No assets available.').should('be.visible');
|
||||||
.contains('No assets available.')
|
cy.get('[data-testid="no-data-image"]').should('be.visible');
|
||||||
.should('be.visible');
|
|
||||||
|
|
||||||
searchEntity(entity);
|
searchEntity(entity);
|
||||||
|
|
||||||
interceptURL('GET', '/api/v1/feed*', 'getEntityDetails');
|
interceptURL('GET', '/api/v1/feed*', 'getEntityDetails');
|
||||||
@ -374,15 +382,14 @@ describe('Glossary page should work properly', () => {
|
|||||||
.contains(term);
|
.contains(term);
|
||||||
|
|
||||||
//Add tag to schema table
|
//Add tag to schema table
|
||||||
cy.get('[data-testid="tag-container"] [data-testid="tags"]')
|
cy.get('[data-row-key="comments"] [data-testid="tags-wrapper"]')
|
||||||
.eq(0)
|
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.click();
|
.click();
|
||||||
cy.get('[class*="-control"]').should('be.visible').type(term);
|
cy.get('[class*="-control"]').should('be.visible').type(term);
|
||||||
cy.get('[id*="-option-0"]').should('contain', term);
|
cy.get('[id*="-option-0"]').should('contain', term);
|
||||||
cy.get('[id*="-option-0"]').should('be.visible').click();
|
cy.get('[id*="-option-0"]').should('be.visible').click();
|
||||||
cy.get(
|
cy.get(
|
||||||
'[data-testid="tags-wrapper"] [data-testid="tag-container"]'
|
'[data-row-key="comments"] [data-testid="tags-wrapper"] [data-testid="tag-container"]'
|
||||||
).contains(term);
|
).contains(term);
|
||||||
|
|
||||||
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
|
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
|
||||||
@ -398,9 +405,7 @@ describe('Glossary page should work properly', () => {
|
|||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
|
|
||||||
goToAssetsTab(term);
|
goToAssetsTab(term);
|
||||||
cy.get('[data-testid="column"] > :nth-child(1)')
|
cy.get('[data-testid="table-link"]').contains(entity).should('be.visible');
|
||||||
.contains(entity)
|
|
||||||
.should('be.visible');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Remove Glossary term from entity should work properly', () => {
|
it('Remove Glossary term from entity should work properly', () => {
|
||||||
@ -413,7 +418,7 @@ describe('Glossary page should work properly', () => {
|
|||||||
verifyResponseStatusCode('@assetTab', 200);
|
verifyResponseStatusCode('@assetTab', 200);
|
||||||
|
|
||||||
interceptURL('GET', '/api/v1/feed*', 'entityDetails');
|
interceptURL('GET', '/api/v1/feed*', 'entityDetails');
|
||||||
cy.get('[data-testid="column"] > :nth-child(1) > a')
|
cy.get('[data-testid="table-link"]')
|
||||||
.contains(entity)
|
.contains(entity)
|
||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.click();
|
.click();
|
||||||
@ -443,9 +448,8 @@ describe('Glossary page should work properly', () => {
|
|||||||
|
|
||||||
cy.wait(500);
|
cy.wait(500);
|
||||||
goToAssetsTab(term);
|
goToAssetsTab(term);
|
||||||
cy.get('.tableBody-cell')
|
cy.contains('No assets available.').should('be.visible');
|
||||||
.contains('No assets available.')
|
cy.get('[data-testid="no-data-image"]').should('be.visible');
|
||||||
.should('be.visible');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Delete glossary term should work properly', () => {
|
it('Delete glossary term should work properly', () => {
|
||||||
|
@ -124,6 +124,15 @@ jest.mock('antd', () => ({
|
|||||||
jest.mock('./SummaryDetail', () =>
|
jest.mock('./SummaryDetail', () =>
|
||||||
jest.fn().mockReturnValue(<div>SummaryDetails</div>)
|
jest.fn().mockReturnValue(<div>SummaryDetails</div>)
|
||||||
);
|
);
|
||||||
|
jest.mock('./tabs/RelatedTerms', () =>
|
||||||
|
jest.fn().mockReturnValue(<div>RelatedTermsComponent</div>)
|
||||||
|
);
|
||||||
|
jest.mock('./tabs/GlossaryTermSynonyms', () =>
|
||||||
|
jest.fn().mockReturnValue(<div>GlossaryTermSynonymsComponent</div>)
|
||||||
|
);
|
||||||
|
jest.mock('./tabs/GlossaryTermReferences', () =>
|
||||||
|
jest.fn().mockReturnValue(<div>GlossaryTermReferencesComponent</div>)
|
||||||
|
);
|
||||||
|
|
||||||
const mockProps = {
|
const mockProps = {
|
||||||
assetData: mockedAssetData,
|
assetData: mockedAssetData,
|
||||||
|
@ -12,32 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import {
|
import { Button, Card, Col, Divider, Row, Tooltip, Typography } from 'antd';
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Col,
|
|
||||||
Divider,
|
|
||||||
Input,
|
|
||||||
Row,
|
|
||||||
Space,
|
|
||||||
Tooltip,
|
|
||||||
Typography,
|
|
||||||
} from 'antd';
|
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { cloneDeep, includes, isEmpty, isEqual } from 'lodash';
|
import { cloneDeep, includes, isEqual } from 'lodash';
|
||||||
import {
|
import { EntityTags, FormattedUsersData, GlossaryTermAssets } from 'Models';
|
||||||
EntityTags,
|
import React, { useEffect, useState } from 'react';
|
||||||
FormattedGlossaryTermData,
|
|
||||||
FormattedUsersData,
|
|
||||||
GlossaryTermAssets,
|
|
||||||
} from 'Models';
|
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
|
||||||
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
||||||
import {
|
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
|
||||||
GlossaryTerm,
|
|
||||||
TermReference,
|
|
||||||
} from '../../generated/entity/data/glossaryTerm';
|
|
||||||
import { LabelType, State, TagSource } from '../../generated/type/tagLabel';
|
import { LabelType, State, TagSource } from '../../generated/type/tagLabel';
|
||||||
import jsonData from '../../jsons/en';
|
import jsonData from '../../jsons/en';
|
||||||
import { getEntityName } from '../../utils/CommonUtils';
|
import { getEntityName } from '../../utils/CommonUtils';
|
||||||
@ -51,15 +33,15 @@ import { showErrorToast } from '../../utils/ToastUtils';
|
|||||||
import DescriptionV1 from '../common/description/DescriptionV1';
|
import DescriptionV1 from '../common/description/DescriptionV1';
|
||||||
import ProfilePicture from '../common/ProfilePicture/ProfilePicture';
|
import ProfilePicture from '../common/ProfilePicture/ProfilePicture';
|
||||||
import TabsPane from '../common/TabsPane/TabsPane';
|
import TabsPane from '../common/TabsPane/TabsPane';
|
||||||
import GlossaryReferenceModal from '../Modals/GlossaryReferenceModal/GlossaryReferenceModal';
|
|
||||||
import RelatedTermsModal from '../Modals/RelatedTermsModal/RelatedTermsModal';
|
|
||||||
import ReviewerModal from '../Modals/ReviewerModal/ReviewerModal.component';
|
import ReviewerModal from '../Modals/ReviewerModal/ReviewerModal.component';
|
||||||
import { OperationPermission } from '../PermissionProvider/PermissionProvider.interface';
|
import { OperationPermission } from '../PermissionProvider/PermissionProvider.interface';
|
||||||
import TagsContainer from '../tags-container/tags-container';
|
import TagsContainer from '../tags-container/tags-container';
|
||||||
import TagsViewer from '../tags-viewer/tags-viewer';
|
import TagsViewer from '../tags-viewer/tags-viewer';
|
||||||
import Tags from '../tags/tags';
|
import Tags from '../tags/tags';
|
||||||
import SummaryDetail from './SummaryDetail';
|
|
||||||
import AssetsTabs from './tabs/AssetsTabs.component';
|
import AssetsTabs from './tabs/AssetsTabs.component';
|
||||||
|
import GlossaryTermReferences from './tabs/GlossaryTermReferences';
|
||||||
|
import GlossaryTermSynonyms from './tabs/GlossaryTermSynonyms';
|
||||||
|
import RelatedTerms from './tabs/RelatedTerms';
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -89,21 +71,7 @@ const GlossaryTermsV1 = ({
|
|||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [activeTab, setActiveTab] = useState<number>(1);
|
const [activeTab, setActiveTab] = useState<number>(1);
|
||||||
const [showRevieweModal, setShowRevieweModal] = useState<boolean>(false);
|
const [showRevieweModal, setShowRevieweModal] = useState<boolean>(false);
|
||||||
const [showRelatedTermsModal, setShowRelatedTermsModal] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
const [isSynonymsEditing, setIsSynonymsEditing] = useState<boolean>(false);
|
|
||||||
const [isReferencesEditing, setIsReferencesEditing] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
const [synonyms, setSynonyms] = useState<string>(
|
|
||||||
glossaryTerm.synonyms?.join(',') || ''
|
|
||||||
);
|
|
||||||
const [references, setReferences] = useState<TermReference[]>(
|
|
||||||
glossaryTerm.references || []
|
|
||||||
);
|
|
||||||
const [reviewer, setReviewer] = useState<Array<FormattedUsersData>>([]);
|
const [reviewer, setReviewer] = useState<Array<FormattedUsersData>>([]);
|
||||||
const [relatedTerms, setRelatedTerms] = useState<FormattedGlossaryTermData[]>(
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
@ -118,32 +86,6 @@ const GlossaryTermsV1 = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const onRelatedTermsModalCancel = () => {
|
|
||||||
setShowRelatedTermsModal(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRelatedTermsSave = (terms: Array<FormattedGlossaryTermData>) => {
|
|
||||||
if (!isEqual(terms, relatedTerms)) {
|
|
||||||
let updatedGlossaryTerm = cloneDeep(glossaryTerm);
|
|
||||||
const oldTerms = terms.filter((d) => includes(relatedTerms, d));
|
|
||||||
const newTerms = terms
|
|
||||||
.filter((d) => !includes(relatedTerms, d))
|
|
||||||
.map((d) => ({
|
|
||||||
id: d.id,
|
|
||||||
type: d.type,
|
|
||||||
displayName: d.displayName,
|
|
||||||
name: d.name,
|
|
||||||
}));
|
|
||||||
updatedGlossaryTerm = {
|
|
||||||
...updatedGlossaryTerm,
|
|
||||||
relatedTerms: [...oldTerms, ...newTerms],
|
|
||||||
};
|
|
||||||
setRelatedTerms(terms);
|
|
||||||
handleGlossaryTermUpdate(updatedGlossaryTerm);
|
|
||||||
}
|
|
||||||
onRelatedTermsModalCancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onReviewerModalCancel = () => {
|
const onReviewerModalCancel = () => {
|
||||||
setShowRevieweModal(false);
|
setShowRevieweModal(false);
|
||||||
};
|
};
|
||||||
@ -255,48 +197,6 @@ const GlossaryTermsV1 = ({
|
|||||||
handleGlossaryTermUpdate(updatedGlossaryTerm);
|
handleGlossaryTermUpdate(updatedGlossaryTerm);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSynonymsSave = () => {
|
|
||||||
if (synonyms !== glossaryTerm.synonyms?.join(',')) {
|
|
||||||
let updatedGlossaryTerm = cloneDeep(glossaryTerm);
|
|
||||||
updatedGlossaryTerm = {
|
|
||||||
...updatedGlossaryTerm,
|
|
||||||
synonyms: synonyms.split(','),
|
|
||||||
};
|
|
||||||
|
|
||||||
handleGlossaryTermUpdate(updatedGlossaryTerm);
|
|
||||||
}
|
|
||||||
setIsSynonymsEditing(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReferencesSave = (data: TermReference[]) => {
|
|
||||||
if (!isEqual(data, references)) {
|
|
||||||
let updatedGlossaryTerm = cloneDeep(glossaryTerm);
|
|
||||||
updatedGlossaryTerm = {
|
|
||||||
...updatedGlossaryTerm,
|
|
||||||
references: data,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleGlossaryTermUpdate(updatedGlossaryTerm);
|
|
||||||
setReferences(data);
|
|
||||||
}
|
|
||||||
setIsReferencesEditing(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleValidation = (
|
|
||||||
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
|
||||||
) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
const eleName = event.target.name;
|
|
||||||
|
|
||||||
switch (eleName) {
|
|
||||||
case 'synonyms': {
|
|
||||||
setSynonyms(value);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTagContainerClick = () => {
|
const handleTagContainerClick = () => {
|
||||||
if (!isTagEditable) {
|
if (!isTagEditable) {
|
||||||
fetchTags();
|
fetchTags();
|
||||||
@ -317,12 +217,6 @@ const GlossaryTermsV1 = ({
|
|||||||
}
|
}
|
||||||
}, [glossaryTerm.reviewers]);
|
}, [glossaryTerm.reviewers]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (glossaryTerm.relatedTerms?.length) {
|
|
||||||
setRelatedTerms(glossaryTerm.relatedTerms as FormattedGlossaryTermData[]);
|
|
||||||
}
|
|
||||||
}, [glossaryTerm.relatedTerms]);
|
|
||||||
|
|
||||||
const addReviewerButton = () => {
|
const addReviewerButton = () => {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -398,19 +292,6 @@ const GlossaryTermsV1 = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSynonyms = (synonymsList: string) => {
|
|
||||||
return !isEmpty(synonymsList) ? (
|
|
||||||
synonymsList.split(',').map((synonym, index) => (
|
|
||||||
<>
|
|
||||||
{index > 0 ? <span className="tw-mr-2">,</span> : null}
|
|
||||||
<span>{synonym}</span>
|
|
||||||
</>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SummaryTab = () => {
|
const SummaryTab = () => {
|
||||||
return (
|
return (
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
@ -426,115 +307,27 @@ const GlossaryTermsV1 = ({
|
|||||||
onDescriptionEdit={onDescriptionEdit}
|
onDescriptionEdit={onDescriptionEdit}
|
||||||
onDescriptionUpdate={onDescriptionUpdate}
|
onDescriptionUpdate={onDescriptionUpdate}
|
||||||
/>
|
/>
|
||||||
<Divider className="m-r-1" />
|
<Divider className="m-r-1 m-b-sm m-t-0" />
|
||||||
<SummaryDetail
|
<RelatedTerms
|
||||||
data={relatedTerms}
|
glossaryTerm={glossaryTerm || ({} as GlossaryTerm)}
|
||||||
hasAccess={permissions.EditAll}
|
permissions={permissions}
|
||||||
key="related_term"
|
onGlossaryTermUpdate={handleGlossaryTermUpdate}
|
||||||
setShow={setShowRelatedTermsModal}
|
onRelatedTermClick={onRelatedTermClick}
|
||||||
title="Related Terms">
|
/>
|
||||||
<>
|
<Divider className="m-r-1 m-y-sm" />
|
||||||
{relatedTerms.map((d, i) => (
|
|
||||||
<Fragment key={i}>
|
|
||||||
{i > 0 && <span className="tw-mr-2">,</span>}
|
|
||||||
<span
|
|
||||||
className="link-text-info tw-flex"
|
|
||||||
data-testid={`related-term-${d?.name}`}
|
|
||||||
onClick={() => {
|
|
||||||
onRelatedTermClick?.(d.fullyQualifiedName);
|
|
||||||
}}>
|
|
||||||
<span
|
|
||||||
className={classNames('tw-inline-block tw-truncate', {
|
|
||||||
'tw-w-52': (d?.name as string).length > 32,
|
|
||||||
})}
|
|
||||||
title={d?.name as string}>
|
|
||||||
{d?.name}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
</SummaryDetail>
|
|
||||||
<Divider className="m-r-1" />
|
|
||||||
|
|
||||||
<SummaryDetail
|
<GlossaryTermSynonyms
|
||||||
hasAccess={permissions.EditAll}
|
glossaryTerm={glossaryTerm}
|
||||||
key="synonyms"
|
permissions={permissions}
|
||||||
setShow={setIsSynonymsEditing}
|
onGlossaryTermUpdate={handleGlossaryTermUpdate}
|
||||||
title="Synonyms">
|
/>
|
||||||
<>
|
<Divider className="m-r-1 m-y-sm" />
|
||||||
{isSynonymsEditing ? (
|
|
||||||
<Space>
|
|
||||||
<Input
|
|
||||||
autoFocus
|
|
||||||
data-testid="synonyms"
|
|
||||||
id="synonyms"
|
|
||||||
key="synonym-input"
|
|
||||||
name="synonyms"
|
|
||||||
placeholder="Enter comma separated term"
|
|
||||||
value={synonyms}
|
|
||||||
onChange={handleValidation}
|
|
||||||
/>
|
|
||||||
<Space data-testid="buttons">
|
|
||||||
<Button
|
|
||||||
data-testid="cancelAssociatedTag"
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
onMouseDown={() => setIsSynonymsEditing(false)}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
className="tw-w-3.5 tw-h-3.5"
|
|
||||||
icon="times"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
data-testid="saveAssociatedTag"
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
onMouseDown={handleSynonymsSave}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
className="tw-w-3.5 tw-h-3.5"
|
|
||||||
icon="check"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
) : (
|
|
||||||
<>{getSynonyms(synonyms)}</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
</SummaryDetail>
|
|
||||||
<Divider className="m-r-1" />
|
|
||||||
|
|
||||||
<SummaryDetail
|
<GlossaryTermReferences
|
||||||
data={references}
|
glossaryTerm={glossaryTerm}
|
||||||
hasAccess={permissions.EditAll}
|
permissions={permissions}
|
||||||
key="references"
|
onGlossaryTermUpdate={handleGlossaryTermUpdate}
|
||||||
setShow={setIsReferencesEditing}
|
/>
|
||||||
title="References">
|
|
||||||
<>
|
|
||||||
{references &&
|
|
||||||
references.length > 0 &&
|
|
||||||
references.map((d, i) => (
|
|
||||||
<Fragment key={i}>
|
|
||||||
{i > 0 && <span className="tw-mr-2">,</span>}
|
|
||||||
<a
|
|
||||||
className="link-text-info tw-flex"
|
|
||||||
data-testid="owner-link"
|
|
||||||
href={d?.endpoint}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank">
|
|
||||||
<span
|
|
||||||
className={classNames('tw-inline-block tw-truncate', {
|
|
||||||
'tw-w-52': (d?.name as string).length > 32,
|
|
||||||
})}
|
|
||||||
title={d?.name as string}>
|
|
||||||
{d?.name}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
</SummaryDetail>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col className="tw-px-10" flex="25%">
|
<Col className="tw-px-10" flex="25%">
|
||||||
@ -640,15 +433,6 @@ const GlossaryTermsV1 = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showRelatedTermsModal && (
|
|
||||||
<RelatedTermsModal
|
|
||||||
glossaryTermFQN={glossaryTerm.fullyQualifiedName}
|
|
||||||
header="Add Related Terms"
|
|
||||||
relatedTerms={relatedTerms}
|
|
||||||
onCancel={onRelatedTermsModalCancel}
|
|
||||||
onSave={handleRelatedTermsSave}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{showRevieweModal && (
|
{showRevieweModal && (
|
||||||
<ReviewerModal
|
<ReviewerModal
|
||||||
header="Add Reviewer"
|
header="Add Reviewer"
|
||||||
@ -657,14 +441,6 @@ const GlossaryTermsV1 = ({
|
|||||||
onSave={handleReviewerSave}
|
onSave={handleReviewerSave}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isReferencesEditing && (
|
|
||||||
<GlossaryReferenceModal
|
|
||||||
header={`Edit References for ${glossaryTerm.name}`}
|
|
||||||
referenceList={references}
|
|
||||||
onCancel={() => setIsReferencesEditing(false)}
|
|
||||||
onSave={handleReferencesSave}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,55 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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, Tooltip, Typography } from 'antd';
|
import { Button, Space, Tooltip, Typography } from 'antd';
|
||||||
import { isString, isUndefined, kebabCase } from 'lodash';
|
import { kebabCase } from 'lodash';
|
||||||
import { FormattedGlossaryTermData } from 'Models';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
||||||
import { TermReference } from '../../generated/entity/data/glossaryTerm';
|
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||||
import SVGIcons from '../../utils/SvgUtils';
|
|
||||||
|
|
||||||
interface SummaryDetailsProps {
|
interface SummaryDetailsProps {
|
||||||
title: string;
|
title: string;
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
hasAccess: boolean;
|
hasAccess: boolean;
|
||||||
|
showIcon?: boolean;
|
||||||
|
showAddIcon?: boolean;
|
||||||
setShow?: (value: React.SetStateAction<boolean>) => void;
|
setShow?: (value: React.SetStateAction<boolean>) => void;
|
||||||
data?: FormattedGlossaryTermData[] | TermReference[] | string;
|
onSave?: () => void;
|
||||||
|
onAddClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummaryDetail = ({
|
const SummaryDetail = ({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
setShow,
|
setShow,
|
||||||
data,
|
showIcon,
|
||||||
|
showAddIcon = false,
|
||||||
hasAccess,
|
hasAccess,
|
||||||
|
onSave,
|
||||||
|
onAddClick,
|
||||||
...props
|
...props
|
||||||
}: SummaryDetailsProps) => {
|
}: SummaryDetailsProps) => {
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" {...props}>
|
<Space className="w-full" direction="vertical" {...props}>
|
||||||
<Space>
|
<Space
|
||||||
<Typography.Text type="secondary">{title}</Typography.Text>
|
className="w-full justify-between"
|
||||||
<div data-testid={`section-${kebabCase(title)}`}>
|
data-testid={`section-${kebabCase(title)}`}>
|
||||||
<Tooltip title={hasAccess ? 'Add' : NO_PERMISSION_FOR_ACTION}>
|
<div className="flex-center">
|
||||||
|
<Typography.Text type="secondary">{title}</Typography.Text>
|
||||||
|
{showAddIcon && (
|
||||||
<Button
|
<Button
|
||||||
className="tw-cursor-pointer"
|
className="cursor-pointer m--t-xss"
|
||||||
data-testid="add-button"
|
data-testid="add-button"
|
||||||
disabled={!hasAccess}
|
disabled={!hasAccess}
|
||||||
|
icon={
|
||||||
|
<SVGIcons
|
||||||
|
alt="icon-plus-primary"
|
||||||
|
icon="icon-plus-primary-outlined"
|
||||||
|
width="16px"
|
||||||
|
/>
|
||||||
|
}
|
||||||
size="small"
|
size="small"
|
||||||
type="text"
|
type="text"
|
||||||
onClick={() => setShow && setShow(true)}>
|
onClick={onAddClick}
|
||||||
<SVGIcons
|
/>
|
||||||
alt="icon-plus-primary"
|
)}
|
||||||
icon="icon-plus-primary-outlined"
|
</div>
|
||||||
/>
|
{showIcon ? (
|
||||||
</Button>
|
<Tooltip title={hasAccess ? 'Edit' : NO_PERMISSION_FOR_ACTION}>
|
||||||
|
<Button
|
||||||
|
className="cursor-pointer m--t-xss"
|
||||||
|
data-testid="edit-button"
|
||||||
|
disabled={!hasAccess}
|
||||||
|
icon={
|
||||||
|
<SVGIcons
|
||||||
|
alt="edit"
|
||||||
|
icon={Icons.IC_EDIT_PRIMARY}
|
||||||
|
width="16px"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
onClick={() => setShow && setShow(true)}
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
) : (
|
||||||
|
<Button
|
||||||
|
data-testid="save-btn"
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
onClick={onSave}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
{!isString(data) && !isUndefined(data) && data.length > 0 ? (
|
{children}
|
||||||
<div className="tw-flex" data-testid={`${kebabCase(title)}-container`}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div data-testid={`${kebabCase(title)}-container`}>{children}</div>
|
|
||||||
)}
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import classNames from 'classnames';
|
|
||||||
import { GlossaryTermAssets } from 'Models';
|
import { GlossaryTermAssets } from 'Models';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
||||||
import { PAGE_SIZE } from '../../../constants/constants';
|
import { PAGE_SIZE } from '../../../constants/constants';
|
||||||
import { EntityType } from '../../../enums/entity.enum';
|
|
||||||
import { SearchIndex } from '../../../enums/search.enum';
|
|
||||||
import { Paging } from '../../../generated/type/paging';
|
import { Paging } from '../../../generated/type/paging';
|
||||||
import { isEven } from '../../../utils/CommonUtils';
|
import { getTierFromSearchTableTags } from '../../../utils/TableUtils';
|
||||||
import { getEntityLink } from '../../../utils/TableUtils';
|
import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder';
|
||||||
import NextPrevious from '../../common/next-previous/NextPrevious';
|
import NextPrevious from '../../common/next-previous/NextPrevious';
|
||||||
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
|
import TableDataCard from '../../common/table-data-card/TableDataCard';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assetData: GlossaryTermAssets;
|
assetData: GlossaryTermAssets;
|
||||||
@ -18,93 +15,50 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AssetsTabs = ({ assetData, onAssetPaginate, currentPage }: Props) => {
|
const AssetsTabs = ({ assetData, onAssetPaginate, currentPage }: Props) => {
|
||||||
const getLinkForFqn = (fqn: string, entityType?: EntityType) => {
|
|
||||||
switch (entityType) {
|
|
||||||
case EntityType.TOPIC:
|
|
||||||
return getEntityLink(SearchIndex.TOPIC, fqn);
|
|
||||||
|
|
||||||
case EntityType.DASHBOARD:
|
|
||||||
return getEntityLink(SearchIndex.DASHBOARD, fqn);
|
|
||||||
|
|
||||||
case EntityType.PIPELINE:
|
|
||||||
return getEntityLink(SearchIndex.PIPELINE, fqn);
|
|
||||||
|
|
||||||
case EntityType.MLMODEL:
|
|
||||||
return getEntityLink(SearchIndex.MLMODEL, fqn);
|
|
||||||
|
|
||||||
case EntityType.TABLE:
|
|
||||||
default:
|
|
||||||
return getEntityLink(SearchIndex.TABLE, fqn);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div data-testid="table-container">
|
||||||
<div className="tw-table-container" data-testid="table-container">
|
{assetData.data.length ? (
|
||||||
<table
|
<>
|
||||||
className="tw-bg-white tw-w-full tw-mb-4"
|
{assetData.data.map((entity, index) => (
|
||||||
data-testid="database-tables">
|
<div className="m-b-sm" key={index}>
|
||||||
<thead>
|
<TableDataCard
|
||||||
<tr className="tableHead-row">
|
database={entity.database}
|
||||||
<th className="tableHead-cell">Name</th>
|
databaseSchema={entity.databaseSchema}
|
||||||
<th className="tableHead-cell">Description</th>
|
deleted={entity.deleted}
|
||||||
<th className="tableHead-cell">Owner</th>
|
description={entity.description}
|
||||||
</tr>
|
fullyQualifiedName={entity.fullyQualifiedName}
|
||||||
</thead>
|
id={`tabledatacard${index}`}
|
||||||
<tbody className="tableBody">
|
indexType={entity.index}
|
||||||
{assetData.data.length > 0 ? (
|
name={entity.name}
|
||||||
assetData.data.map((dataObj, index) => (
|
owner={entity.owner}
|
||||||
<tr
|
service={entity.service}
|
||||||
className={classNames(
|
serviceType={entity.serviceType || '--'}
|
||||||
'tableBody-row',
|
tags={entity.tags}
|
||||||
!isEven(index + 1) ? 'odd-row' : null
|
tier={
|
||||||
)}
|
(
|
||||||
data-testid="column"
|
entity.tier?.tagFQN ||
|
||||||
key={index}>
|
getTierFromSearchTableTags(
|
||||||
<td className="tableBody-cell">
|
(entity.tags || []).map((tag) => tag.tagFQN)
|
||||||
<Link
|
)
|
||||||
to={getLinkForFqn(
|
)?.split(FQN_SEPARATOR_CHAR)[1]
|
||||||
dataObj.fullyQualifiedName || '',
|
}
|
||||||
dataObj.entityType as EntityType
|
usage={entity.weeklyPercentileRank}
|
||||||
)}>
|
/>
|
||||||
{dataObj.name}
|
</div>
|
||||||
</Link>
|
))}
|
||||||
</td>
|
{assetData.total > PAGE_SIZE && assetData.data.length > 0 && (
|
||||||
<td className="tableBody-cell">
|
<NextPrevious
|
||||||
{dataObj.description ? (
|
isNumberBased
|
||||||
<RichTextEditorPreviewer markdown={dataObj.description} />
|
currentPage={currentPage}
|
||||||
) : (
|
pageSize={PAGE_SIZE}
|
||||||
<span className="tw-no-description">No description</span>
|
paging={{} as Paging}
|
||||||
)}
|
pagingHandler={onAssetPaginate}
|
||||||
</td>
|
totalCount={assetData.total}
|
||||||
<td className="tableBody-cell">
|
/>
|
||||||
<p>
|
)}
|
||||||
{dataObj.owner?.displayName ||
|
</>
|
||||||
dataObj.owner?.name ||
|
) : (
|
||||||
'--'}
|
<ErrorPlaceHolder>No assets available.</ErrorPlaceHolder>
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr className="tableBody-row">
|
|
||||||
<td className="tableBody-cell tw-text-center" colSpan={4}>
|
|
||||||
No assets available.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{assetData.total > PAGE_SIZE && assetData.data.length > 0 && (
|
|
||||||
<NextPrevious
|
|
||||||
isNumberBased
|
|
||||||
currentPage={currentPage}
|
|
||||||
pageSize={PAGE_SIZE}
|
|
||||||
paging={{} as Paging}
|
|
||||||
pagingHandler={onAssetPaginate}
|
|
||||||
totalCount={assetData.total}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
import { Button, Col, Form, Input, Row, Typography } from 'antd';
|
||||||
|
import { cloneDeep, isEqual } from 'lodash';
|
||||||
|
import React, { Fragment, useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
GlossaryTerm,
|
||||||
|
TermReference,
|
||||||
|
} from '../../../generated/entity/data/glossaryTerm';
|
||||||
|
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||||
|
import { OperationPermission } from '../../PermissionProvider/PermissionProvider.interface';
|
||||||
|
import SummaryDetail from '../SummaryDetail';
|
||||||
|
|
||||||
|
interface GlossaryTermReferences {
|
||||||
|
glossaryTerm: GlossaryTerm;
|
||||||
|
permissions: OperationPermission;
|
||||||
|
onGlossaryTermUpdate: (glossaryTerm: GlossaryTerm) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GlossaryTermReferences = ({
|
||||||
|
glossaryTerm,
|
||||||
|
permissions,
|
||||||
|
onGlossaryTermUpdate,
|
||||||
|
}: GlossaryTermReferences) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [references, setReferences] = useState<TermReference[]>([]);
|
||||||
|
const [isViewMode, setIsViewMode] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const handleReferencesSave = async () => {
|
||||||
|
try {
|
||||||
|
const updatedRef = references.filter((ref) => ref.endpoint && ref.name);
|
||||||
|
|
||||||
|
setReferences(updatedRef);
|
||||||
|
await form.validateFields();
|
||||||
|
form.resetFields(['references']);
|
||||||
|
if (!isEqual(updatedRef, glossaryTerm.references)) {
|
||||||
|
let updatedGlossaryTerm = cloneDeep(glossaryTerm);
|
||||||
|
updatedGlossaryTerm = {
|
||||||
|
...updatedGlossaryTerm,
|
||||||
|
references: updatedRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
onGlossaryTermUpdate(updatedGlossaryTerm);
|
||||||
|
}
|
||||||
|
setIsViewMode(true);
|
||||||
|
} catch (error) {
|
||||||
|
// Added catch block to prevent uncaught promise
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (glossaryTerm.references?.length) {
|
||||||
|
setReferences(glossaryTerm.references);
|
||||||
|
}
|
||||||
|
}, [glossaryTerm]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="references-container">
|
||||||
|
{isViewMode ? (
|
||||||
|
<SummaryDetail
|
||||||
|
hasAccess={permissions.EditAll}
|
||||||
|
key="references"
|
||||||
|
setShow={() => setIsViewMode(false)}
|
||||||
|
showIcon={isViewMode}
|
||||||
|
title="References">
|
||||||
|
<div className="flex">
|
||||||
|
{references.length > 0 ? (
|
||||||
|
references.map((ref, i) => (
|
||||||
|
<Fragment key={i}>
|
||||||
|
{i > 0 && <span className="m-r-xs">,</span>}
|
||||||
|
<a
|
||||||
|
className="flex"
|
||||||
|
data-testid="owner-link"
|
||||||
|
href={ref?.endpoint}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank">
|
||||||
|
<Typography.Text
|
||||||
|
className="link-text-info"
|
||||||
|
ellipsis={{ tooltip: ref?.name }}
|
||||||
|
style={{ maxWidth: 200 }}>
|
||||||
|
{ref?.name}
|
||||||
|
</Typography.Text>
|
||||||
|
</a>
|
||||||
|
</Fragment>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
No references available.
|
||||||
|
</Typography.Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SummaryDetail>
|
||||||
|
) : (
|
||||||
|
<Form
|
||||||
|
className="reference-edit-form"
|
||||||
|
form={form}
|
||||||
|
onValuesChange={(_, values) => setReferences(values.references)}>
|
||||||
|
<Form.List
|
||||||
|
initialValue={
|
||||||
|
references.length
|
||||||
|
? references
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
endpoint: '',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
name="references">
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<SummaryDetail
|
||||||
|
showAddIcon
|
||||||
|
hasAccess={permissions.EditAll}
|
||||||
|
key="references"
|
||||||
|
setShow={() => setIsViewMode(false)}
|
||||||
|
showIcon={isViewMode}
|
||||||
|
title="References"
|
||||||
|
onAddClick={() => add()}
|
||||||
|
onSave={handleReferencesSave}>
|
||||||
|
<>
|
||||||
|
{fields.map(({ key, name, ...restField }) => (
|
||||||
|
<Row gutter={8} key={key}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
className="w-full"
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'name']}>
|
||||||
|
<Input placeholder="Name" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={11}>
|
||||||
|
<Form.Item
|
||||||
|
className="w-full"
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'endpoint']}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
type: 'url',
|
||||||
|
message: 'Endpoint should be valid URL.',
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Input placeholder="End point" />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={1}>
|
||||||
|
<Button
|
||||||
|
icon={
|
||||||
|
<SVGIcons
|
||||||
|
alt="delete"
|
||||||
|
icon={Icons.DELETE}
|
||||||
|
width="16px"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
onClick={() => remove(name)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
</SummaryDetail>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlossaryTermReferences;
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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 { Select, Typography } from 'antd';
|
||||||
|
import { cloneDeep, isEmpty, isEqual } from 'lodash';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
|
||||||
|
import { OperationPermission } from '../../PermissionProvider/PermissionProvider.interface';
|
||||||
|
import SummaryDetail from '../SummaryDetail';
|
||||||
|
|
||||||
|
interface GlossaryTermSynonymsProps {
|
||||||
|
permissions: OperationPermission;
|
||||||
|
glossaryTerm: GlossaryTerm;
|
||||||
|
onGlossaryTermUpdate: (glossaryTerm: GlossaryTerm) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GlossaryTermSynonyms = ({
|
||||||
|
permissions,
|
||||||
|
glossaryTerm,
|
||||||
|
onGlossaryTermUpdate,
|
||||||
|
}: GlossaryTermSynonymsProps) => {
|
||||||
|
const [isViewMode, setIsViewMode] = useState<boolean>(true);
|
||||||
|
const [synonyms, setSynonyms] = useState<string[]>([]);
|
||||||
|
const getSynonyms = () => {
|
||||||
|
return !isEmpty(synonyms) ? (
|
||||||
|
synonyms.map((synonym, index) => (
|
||||||
|
<span key={index}>
|
||||||
|
{index > 0 ? <span className="tw-mr-2">,</span> : null}
|
||||||
|
<span>{synonym}</span>
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Typography.Text type="secondary">No synonyms available.</Typography.Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSynonymsSave = () => {
|
||||||
|
if (!isEqual(synonyms, glossaryTerm.synonyms)) {
|
||||||
|
let updatedGlossaryTerm = cloneDeep(glossaryTerm);
|
||||||
|
updatedGlossaryTerm = {
|
||||||
|
...updatedGlossaryTerm,
|
||||||
|
synonyms,
|
||||||
|
};
|
||||||
|
|
||||||
|
onGlossaryTermUpdate(updatedGlossaryTerm);
|
||||||
|
}
|
||||||
|
setIsViewMode(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (glossaryTerm.synonyms?.length) {
|
||||||
|
setSynonyms(glossaryTerm.synonyms);
|
||||||
|
}
|
||||||
|
}, [glossaryTerm]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SummaryDetail
|
||||||
|
hasAccess={permissions.EditAll}
|
||||||
|
key="synonyms"
|
||||||
|
setShow={() => setIsViewMode(false)}
|
||||||
|
showIcon={isViewMode}
|
||||||
|
title="Synonyms"
|
||||||
|
onSave={handleSynonymsSave}>
|
||||||
|
<div className="flex" data-testid="synonyms-container">
|
||||||
|
{isViewMode ? (
|
||||||
|
getSynonyms()
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
id="synonyms-select"
|
||||||
|
mode="tags"
|
||||||
|
placeholder="Add Synonyms"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
value={synonyms}
|
||||||
|
onChange={(value) => setSynonyms(value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SummaryDetail>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlossaryTermSynonyms;
|
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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 { Select, Spin, Typography } from 'antd';
|
||||||
|
import { cloneDeep, debounce, includes } from 'lodash';
|
||||||
|
import { EntityReference, SearchResponse } from 'Models';
|
||||||
|
import React, { Fragment, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { searchData } from '../../../axiosAPIs/miscAPI';
|
||||||
|
import { PAGE_SIZE } from '../../../constants/constants';
|
||||||
|
import { SearchIndex } from '../../../enums/search.enum';
|
||||||
|
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
|
||||||
|
import { formatSearchGlossaryTermResponse } from '../../../utils/APIUtils';
|
||||||
|
import { OperationPermission } from '../../PermissionProvider/PermissionProvider.interface';
|
||||||
|
import SummaryDetail from '../SummaryDetail';
|
||||||
|
|
||||||
|
interface RelatedTermsProps {
|
||||||
|
permissions: OperationPermission;
|
||||||
|
glossaryTerm: GlossaryTerm;
|
||||||
|
onRelatedTermClick?: (fqn: string) => void;
|
||||||
|
onGlossaryTermUpdate: (data: GlossaryTerm) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RelatedTerms = ({
|
||||||
|
glossaryTerm,
|
||||||
|
permissions,
|
||||||
|
onRelatedTermClick,
|
||||||
|
onGlossaryTermUpdate,
|
||||||
|
}: RelatedTermsProps) => {
|
||||||
|
const [isIconVisible, setIsIconVisible] = useState<boolean>(true);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [options, setOptions] = useState<EntityReference[]>([]);
|
||||||
|
const [selectedOption, setSelectedOption] = useState<EntityReference[]>([]);
|
||||||
|
|
||||||
|
const getSearchedTerms = (searchedData: EntityReference[]) => {
|
||||||
|
const currOptions = selectedOption.map(
|
||||||
|
(item) => item.fullyQualifiedName || item.name
|
||||||
|
);
|
||||||
|
const data = searchedData.filter((item: EntityReference) => {
|
||||||
|
return !currOptions.includes(item.fullyQualifiedName);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...selectedOption, ...data];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRelatedTermsSave = () => {
|
||||||
|
let updatedGlossaryTerm = cloneDeep(glossaryTerm);
|
||||||
|
const oldTerms = selectedOption.filter((d) =>
|
||||||
|
includes(glossaryTerm.relatedTerms, d)
|
||||||
|
);
|
||||||
|
const newTerms = selectedOption
|
||||||
|
.filter((d) => !includes(glossaryTerm.relatedTerms, d))
|
||||||
|
.map((d) => ({
|
||||||
|
id: d.id,
|
||||||
|
type: d.type,
|
||||||
|
displayName: d.displayName,
|
||||||
|
name: d.name,
|
||||||
|
}));
|
||||||
|
updatedGlossaryTerm = {
|
||||||
|
...updatedGlossaryTerm,
|
||||||
|
relatedTerms: [...oldTerms, ...newTerms],
|
||||||
|
};
|
||||||
|
|
||||||
|
onGlossaryTermUpdate(updatedGlossaryTerm);
|
||||||
|
setIsIconVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const suggestionSearch = (searchText = '') => {
|
||||||
|
setIsLoading(true);
|
||||||
|
searchData(searchText, 1, PAGE_SIZE, '', '', '', SearchIndex.GLOSSARY)
|
||||||
|
.then((res: SearchResponse) => {
|
||||||
|
const termResult = (
|
||||||
|
formatSearchGlossaryTermResponse(
|
||||||
|
res?.data?.hits?.hits || []
|
||||||
|
) as EntityReference[]
|
||||||
|
).filter((item) => {
|
||||||
|
return item.fullyQualifiedName !== glossaryTerm.fullyQualifiedName;
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = searchText ? getSearchedTerms(termResult) : termResult;
|
||||||
|
setOptions(data);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setOptions(selectedOption);
|
||||||
|
})
|
||||||
|
.finally(() => setIsLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const debounceOnSearch = useCallback(debounce(suggestionSearch, 250), []);
|
||||||
|
|
||||||
|
const formatOptions = (data: EntityReference[]) => {
|
||||||
|
return data.map((value) => ({
|
||||||
|
...value,
|
||||||
|
value: value.id,
|
||||||
|
label: value.displayName,
|
||||||
|
key: value.id,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (glossaryTerm.relatedTerms?.length) {
|
||||||
|
setOptions(glossaryTerm.relatedTerms);
|
||||||
|
setSelectedOption(formatOptions(glossaryTerm.relatedTerms));
|
||||||
|
}
|
||||||
|
}, [glossaryTerm]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SummaryDetail
|
||||||
|
hasAccess={permissions.EditAll}
|
||||||
|
key="related_term"
|
||||||
|
setShow={() => setIsIconVisible(false)}
|
||||||
|
showIcon={isIconVisible}
|
||||||
|
title="Related Terms"
|
||||||
|
onSave={handleRelatedTermsSave}>
|
||||||
|
<div className="flex" data-testid="related-term-container">
|
||||||
|
{isIconVisible ? (
|
||||||
|
selectedOption.length ? (
|
||||||
|
selectedOption.map((term, i) => (
|
||||||
|
<Fragment key={i}>
|
||||||
|
{i > 0 && <span className="m-r-xs">,</span>}
|
||||||
|
<span
|
||||||
|
className="flex"
|
||||||
|
data-testid={`related-term-${term?.name}`}
|
||||||
|
onClick={() => {
|
||||||
|
onRelatedTermClick?.(term.fullyQualifiedName || '');
|
||||||
|
}}>
|
||||||
|
<Typography.Text
|
||||||
|
className="link-text-info"
|
||||||
|
ellipsis={{ tooltip: term?.name }}
|
||||||
|
style={{ maxWidth: 200 }}>
|
||||||
|
{term?.name}
|
||||||
|
</Typography.Text>
|
||||||
|
</span>
|
||||||
|
</Fragment>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
No related terms available.
|
||||||
|
</Typography.Text>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
filterOption={false}
|
||||||
|
mode="multiple"
|
||||||
|
notFoundContent={isLoading ? <Spin size="small" /> : null}
|
||||||
|
options={formatOptions(options)}
|
||||||
|
placeholder="Add Related Terms"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
value={selectedOption}
|
||||||
|
onChange={(_, data) => {
|
||||||
|
setSelectedOption(data as EntityReference[]);
|
||||||
|
}}
|
||||||
|
onFocus={() => suggestionSearch()}
|
||||||
|
onSearch={debounceOnSearch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SummaryDetail>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RelatedTerms;
|
@ -25,9 +25,15 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.justify-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
.break-word {
|
.break-word {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
@ -180,6 +186,12 @@
|
|||||||
.error-text {
|
.error-text {
|
||||||
color: #ff4c3b;
|
color: #ff4c3b;
|
||||||
}
|
}
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.gap-2 {
|
||||||
|
gap: 0.5rem /* 8px */;
|
||||||
|
}
|
||||||
|
|
||||||
.text-base {
|
.text-base {
|
||||||
font-size: 1rem /* 16px */;
|
font-size: 1rem /* 16px */;
|
||||||
|
@ -62,3 +62,9 @@
|
|||||||
padding: 0.37rem 0.13rem;
|
padding: 0.37rem 0.13rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reference-edit-form {
|
||||||
|
.ant-form-item {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -107,6 +107,9 @@
|
|||||||
.m-t-xs {
|
.m-t-xs {
|
||||||
margin-top: @margin-xs;
|
margin-top: @margin-xs;
|
||||||
}
|
}
|
||||||
|
.m--t-xss {
|
||||||
|
margin-top: -@margin-xss;
|
||||||
|
}
|
||||||
.m-t-sm {
|
.m-t-sm {
|
||||||
margin-top: @margin-sm;
|
margin-top: @margin-sm;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user