mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-30 20:06:19 +00:00
feat: allow adding owner on glossary and term creation (#11039)
* feat: allow adding owner on glossary and term creation * fix: glossary feedback * fix: glossary button styling * fix: glossary button styling * fix: added on cancel for dialog --------- Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
parent
d9564ef0d5
commit
49edea5fdc
@ -14,11 +14,14 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Space, Switch, Typography } from 'antd';
|
||||
import { UserSelectableList } from 'components/common/UserSelectableList/UserSelectableList.component';
|
||||
import Tags from 'components/Tag/Tags/tags';
|
||||
import { UserTag } from 'components/common/UserTag/UserTag.component';
|
||||
import { UserTagSize } from 'components/common/UserTag/UserTag.interface';
|
||||
import { UserTeamSelectableList } from 'components/common/UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import { cloneDeep, toString } from 'lodash';
|
||||
import { EntityTags } from 'Models';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import { ADD_GLOSSARY_ERROR } from '../../constants/Glossary.constant';
|
||||
import { allowedNameRegEx } from '../../constants/regex.constants';
|
||||
import { PageLayoutType } from '../../enums/layout.enum';
|
||||
@ -60,6 +63,7 @@ const AddGlossary = ({
|
||||
const [tags, setTags] = useState<EntityTags[]>([]);
|
||||
const [mutuallyExclusive, setMutuallyExclusive] = useState(false);
|
||||
const [reviewer, setReviewer] = useState<Array<EntityReference>>([]);
|
||||
const [owner, setOwner] = useState<EntityReference | undefined>();
|
||||
|
||||
const getDescription = () => {
|
||||
return markdownRef.current?.getEditorContent() || '';
|
||||
@ -68,6 +72,9 @@ const AddGlossary = ({
|
||||
const handleReviewerSave = (reviewer: EntityReference[]) => {
|
||||
setReviewer(reviewer);
|
||||
};
|
||||
const handleUpdatedOwner = async (owner: EntityReference | undefined) => {
|
||||
setOwner(owner);
|
||||
};
|
||||
|
||||
const handleValidation = (
|
||||
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||
@ -93,13 +100,6 @@ const AddGlossary = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handleReviewerRemove = (
|
||||
_event: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||
removedTag: string
|
||||
) => {
|
||||
setReviewer((pre) => pre.filter((option) => option.name !== removedTag));
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
const errMsg = {
|
||||
name: !name.trim(),
|
||||
@ -113,6 +113,10 @@ const AddGlossary = ({
|
||||
|
||||
const handleSave = () => {
|
||||
if (validateForm()) {
|
||||
const selectedOwner = owner || {
|
||||
id: getCurrentUserId(),
|
||||
type: 'user',
|
||||
};
|
||||
const data: CreateGlossary = {
|
||||
name: name.trim(),
|
||||
displayName: (displayName || name).trim(),
|
||||
@ -120,10 +124,7 @@ const AddGlossary = ({
|
||||
reviewers:
|
||||
reviewer.map((d) => toString(d.fullyQualifiedName)).filter(Boolean) ??
|
||||
[],
|
||||
owner: {
|
||||
id: getCurrentUserId(),
|
||||
type: 'user',
|
||||
},
|
||||
owner: selectedOwner,
|
||||
tags: tags,
|
||||
mutuallyExclusive,
|
||||
};
|
||||
@ -225,9 +226,9 @@ const AddGlossary = ({
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Space align="end">
|
||||
<Space align="end" size={12}>
|
||||
<label
|
||||
className="tw-form-label m-b-0 tw-mb-1"
|
||||
className="glossary-form-label tw-form-label m-b-0 tw-mb-1"
|
||||
data-testid="mutually-exclusive-label"
|
||||
htmlFor="mutuallyExclusive">
|
||||
{t('label.mutually-exclusive')}
|
||||
@ -243,34 +244,71 @@ const AddGlossary = ({
|
||||
|
||||
<div>
|
||||
<div className="tw-flex tw-items-center tw-mt-4">
|
||||
<span className="w-form-label tw-mr-3">
|
||||
<span className="glossary-form-label w-form-label tw-mr-3">
|
||||
{`${t('label.owner')}:`}
|
||||
</span>
|
||||
<UserTeamSelectableList
|
||||
hasPermission
|
||||
owner={owner}
|
||||
onUpdate={handleUpdatedOwner}>
|
||||
<Button
|
||||
data-testid="add-owner"
|
||||
icon={
|
||||
<PlusOutlined
|
||||
style={{ color: 'white', fontSize: '12px' }}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
type="primary"
|
||||
/>
|
||||
</UserTeamSelectableList>
|
||||
</div>
|
||||
<div className="tw-my-2" data-testid="owner-container">
|
||||
{owner && (
|
||||
<UserTag
|
||||
id={owner.id}
|
||||
name={getEntityName(owner)}
|
||||
size={UserTagSize.small}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="tw-flex tw-items-center tw-mt-4">
|
||||
<span className="glossary-form-label w-form-label tw-mr-3">
|
||||
{`${t('label.reviewer-plural')}:`}
|
||||
</span>
|
||||
<UserSelectableList
|
||||
hasPermission
|
||||
selectedUsers={reviewer ?? []}
|
||||
onUpdate={handleReviewerSave}>
|
||||
<Button data-testid="add-reviewers" size="small" type="primary">
|
||||
<PlusOutlined style={{ color: 'white' }} />
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="add-reviewers"
|
||||
icon={
|
||||
<PlusOutlined
|
||||
style={{ color: 'white', fontSize: '12px' }}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
type="primary"
|
||||
/>
|
||||
</UserSelectableList>
|
||||
</div>
|
||||
<div className="tw-my-4" data-testid="reviewers-container">
|
||||
<Space
|
||||
wrap
|
||||
className="tw-my-2"
|
||||
data-testid="reviewers-container"
|
||||
size={[8, 8]}>
|
||||
{Boolean(reviewer.length) &&
|
||||
reviewer.map((d, index) => {
|
||||
return (
|
||||
<Tags
|
||||
editable
|
||||
isRemovable
|
||||
className="tw-bg-gray-200"
|
||||
<UserTag
|
||||
id={d.id}
|
||||
key={index}
|
||||
removeTag={handleReviewerRemove}
|
||||
tag={d.name ?? ''}
|
||||
type="contained"
|
||||
name={getEntityName(d)}
|
||||
size={UserTagSize.small}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
|
@ -17,6 +17,7 @@ import classNames from 'classnames';
|
||||
import { UserSelectableList } from 'components/common/UserSelectableList/UserSelectableList.component';
|
||||
import { UserTag } from 'components/common/UserTag/UserTag.component';
|
||||
import { UserTagSize } from 'components/common/UserTag/UserTag.interface';
|
||||
import { UserTeamSelectableList } from 'components/common/UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import Tags from 'components/Tag/Tags/tags';
|
||||
import { t } from 'i18next';
|
||||
import { cloneDeep, isEmpty, isUndefined } from 'lodash';
|
||||
@ -31,7 +32,12 @@ import {
|
||||
TermReference,
|
||||
} from '../../generated/entity/data/glossaryTerm';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { errorMsg, isValidUrl, requiredField } from '../../utils/CommonUtils';
|
||||
import {
|
||||
errorMsg,
|
||||
getCurrentUserId,
|
||||
isValidUrl,
|
||||
requiredField,
|
||||
} from '../../utils/CommonUtils';
|
||||
import SVGIcons from '../../utils/SvgUtils';
|
||||
import { AddTags } from '../AddTags/add-tags.component';
|
||||
import RichTextEditor from '../common/rich-text-editor/RichTextEditor';
|
||||
@ -80,6 +86,7 @@ const AddGlossaryTerm = ({
|
||||
const [synonyms, setSynonyms] = useState('');
|
||||
const [mutuallyExclusive, setMutuallyExclusive] = useState(false);
|
||||
const [references, setReferences] = useState<TermReference[]>([]);
|
||||
const [owner, setOwner] = useState<EntityReference | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (glossaryData?.reviewers && glossaryData?.reviewers.length) {
|
||||
@ -104,8 +111,8 @@ const AddGlossaryTerm = ({
|
||||
setReviewer(reviewer);
|
||||
};
|
||||
|
||||
const handleReviewerRemove = (removedTag: string) => {
|
||||
setReviewer((pre) => pre.filter((option) => option.name !== removedTag));
|
||||
const handleUpdatedOwner = async (owner: EntityReference | undefined) => {
|
||||
setOwner(owner);
|
||||
};
|
||||
|
||||
const handleTermRemove = (
|
||||
@ -212,6 +219,10 @@ const AddGlossaryTerm = ({
|
||||
|
||||
if (validateForm(updatedReference)) {
|
||||
const updatedName = name.trim();
|
||||
const selectedOwner = owner || {
|
||||
id: getCurrentUserId(),
|
||||
type: 'user',
|
||||
};
|
||||
const data: CreateGlossaryTerm = {
|
||||
name: updatedName,
|
||||
displayName: (displayName || updatedName).trim(),
|
||||
@ -226,6 +237,7 @@ const AddGlossaryTerm = ({
|
||||
mutuallyExclusive,
|
||||
glossary: glossaryData.name,
|
||||
tags: tags,
|
||||
owner: selectedOwner,
|
||||
};
|
||||
|
||||
onSave(data);
|
||||
@ -361,9 +373,9 @@ const AddGlossaryTerm = ({
|
||||
|
||||
<div className="m-t-lg">
|
||||
<Field>
|
||||
<Space align="end">
|
||||
<Space align="end" size={12}>
|
||||
<label
|
||||
className="tw-form-label m-b-0"
|
||||
className="glossary-form-label tw-form-label m-b-0"
|
||||
data-testid="mutually-exclusive-label"
|
||||
htmlFor="mutuallyExclusive">
|
||||
{t('label.mutually-exclusive')}
|
||||
@ -378,14 +390,18 @@ const AddGlossaryTerm = ({
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Space align="end" data-testid="references">
|
||||
<label className="tw-form-label m-b-0">
|
||||
<Space align="end" data-testid="references" size={12}>
|
||||
<label className="glossary-form-label tw-form-label m-b-0">
|
||||
{t('label.reference-plural')}
|
||||
</label>
|
||||
<Button
|
||||
className="tw-h-5 tw-px-2"
|
||||
data-testid="add-reference"
|
||||
icon={<PlusOutlined />}
|
||||
icon={
|
||||
<PlusOutlined
|
||||
style={{ color: 'white', fontSize: '12px' }}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={addReferenceFields}
|
||||
@ -449,13 +465,15 @@ const AddGlossaryTerm = ({
|
||||
|
||||
<Field>
|
||||
<div className="tw-flex tw-items-center tw-mt-4">
|
||||
<p className="w-form-label tw-mr-3">
|
||||
<p className="glossary-form-label w-form-label tw-mr-3">
|
||||
{t('label.related-term-plural')}
|
||||
</p>
|
||||
<Button
|
||||
className="tw-h-5 tw-px-2"
|
||||
data-testid="add-related-terms"
|
||||
icon={<PlusOutlined />}
|
||||
icon={
|
||||
<PlusOutlined style={{ color: 'white', fontSize: '12px' }} />
|
||||
}
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={() => setShowRelatedTermsModal(true)}
|
||||
@ -478,9 +496,42 @@ const AddGlossaryTerm = ({
|
||||
})}
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<div className="tw-flex tw-items-center tw-mt-4">
|
||||
<p className="w-form-label tw-mr-3">
|
||||
<p className="glossary-form-label w-form-label tw-mr-3">{`${t(
|
||||
'label.owner'
|
||||
)}`}</p>
|
||||
<UserTeamSelectableList
|
||||
hasPermission
|
||||
owner={owner}
|
||||
onUpdate={handleUpdatedOwner}>
|
||||
<Button
|
||||
className="tw-h-5 tw-px-2"
|
||||
data-testid="add-owner"
|
||||
icon={
|
||||
<PlusOutlined
|
||||
style={{ color: 'white', fontSize: '12px' }}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
type="primary"
|
||||
/>
|
||||
</UserTeamSelectableList>
|
||||
</div>
|
||||
<div className="tw-my-2" data-testid="owner-container">
|
||||
{owner && (
|
||||
<UserTag
|
||||
id={owner.id}
|
||||
name={getEntityName(owner)}
|
||||
size={UserTagSize.small}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Field>
|
||||
<Field>
|
||||
<div className="tw-flex tw-items-center tw-mt-4">
|
||||
<p className="glossary-form-label w-form-label tw-mr-3">
|
||||
{t('label.reviewer-plural')}
|
||||
</p>
|
||||
<UserSelectableList
|
||||
@ -491,24 +542,25 @@ const AddGlossaryTerm = ({
|
||||
<Button
|
||||
className="tw-h-5 tw-px-2"
|
||||
data-testid="add-reviewers"
|
||||
icon={<PlusOutlined />}
|
||||
icon={
|
||||
<PlusOutlined
|
||||
style={{ color: 'white', fontSize: '12px' }}
|
||||
/>
|
||||
}
|
||||
size="small"
|
||||
type="primary"
|
||||
/>
|
||||
</UserSelectableList>
|
||||
</div>
|
||||
<Space wrap className="tw-my-4" size={[8, 8]}>
|
||||
<Space wrap className="tw-my-2" size={[8, 8]}>
|
||||
{Boolean(reviewer.length) &&
|
||||
reviewer.map((d, index) => {
|
||||
return (
|
||||
<UserTag
|
||||
bordered
|
||||
closable
|
||||
id={d.id}
|
||||
key={index}
|
||||
name={getEntityName(d)}
|
||||
size={UserTagSize.small}
|
||||
onRemove={() => d.name && handleReviewerRemove(d.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -18,7 +18,11 @@ import { UserSelectableList } from 'components/common/UserSelectableList/UserSel
|
||||
import { UserTeamSelectableList } from 'components/common/UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import TagButton from 'components/TagButton/TagButton.component';
|
||||
import TagsInput from 'components/TagsInput/TagsInput.component';
|
||||
import { DE_ACTIVE_COLOR, getUserPath } from 'constants/constants';
|
||||
import {
|
||||
DE_ACTIVE_COLOR,
|
||||
getTeamAndUserDetailsPath,
|
||||
getUserPath,
|
||||
} from 'constants/constants';
|
||||
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
|
||||
import { EntityReference } from 'generated/type/entityReference';
|
||||
import { t } from 'i18next';
|
||||
@ -119,7 +123,12 @@ const GlossaryDetailsRightPanel = ({
|
||||
textClass="text-xs"
|
||||
width="20"
|
||||
/>
|
||||
<Link to={getUserPath(selectedData.owner.name ?? '')}>
|
||||
<Link
|
||||
to={
|
||||
selectedData.owner.type === 'team'
|
||||
? getTeamAndUserDetailsPath(selectedData.owner.name ?? '')
|
||||
: getUserPath(selectedData.owner.name ?? '')
|
||||
}>
|
||||
{getEntityName(selectedData.owner)}
|
||||
</Link>
|
||||
</Space>
|
||||
|
@ -43,7 +43,18 @@ const GlossaryTermReferencesModal = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
isVisible ? form.setFieldValue('references', references) : null;
|
||||
if (isVisible) {
|
||||
const newRefs =
|
||||
references.length > 0
|
||||
? references
|
||||
: [
|
||||
{
|
||||
name: '',
|
||||
endpoint: '',
|
||||
},
|
||||
];
|
||||
form.setFieldValue('references', newRefs);
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
return (
|
||||
|
@ -47,6 +47,7 @@ const EntityNameModal: React.FC<Props> = ({
|
||||
return (
|
||||
<Modal
|
||||
destroyOnClose
|
||||
closable={false}
|
||||
footer={[
|
||||
<Button key="cancel-btn" type="link" onClick={onCancel}>
|
||||
{t('label.cancel')}
|
||||
@ -59,13 +60,15 @@ const EntityNameModal: React.FC<Props> = ({
|
||||
{t('label.save')}
|
||||
</Button>,
|
||||
]}
|
||||
maskClosable={false}
|
||||
okText={t('label.save')}
|
||||
open={visible}
|
||||
title={
|
||||
<Typography.Text strong data-testid="header">
|
||||
{t('label.edit-glossary-name')}
|
||||
</Typography.Text>
|
||||
}>
|
||||
}
|
||||
onCancel={onCancel}>
|
||||
<Form form={form} layout="vertical" onFinish={handleSave}>
|
||||
<Form.Item
|
||||
label={`${t('label.name')}:`}
|
||||
|
@ -168,6 +168,8 @@ export const UserTeamSelectableList = ({
|
||||
: {
|
||||
id: updateItems[0].id,
|
||||
type: activeTab === 'teams' ? EntityType.TEAM : EntityType.USER,
|
||||
name: updateItems[0].name,
|
||||
displayName: updateItems[0].displayName,
|
||||
}
|
||||
);
|
||||
setPopupVisible(false);
|
||||
|
@ -125,3 +125,7 @@
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.glossary-form-label {
|
||||
width: 125px;
|
||||
}
|
||||
|
@ -95,4 +95,7 @@
|
||||
background: @active-color;
|
||||
}
|
||||
}
|
||||
.expand-cell-empty-icon-container {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
@ -468,7 +468,7 @@ export function getTableExpandableConfig<T>(
|
||||
<>
|
||||
<SVGIcons
|
||||
alt="icon"
|
||||
className="m-r-xs"
|
||||
className="m-r-xs drag-icon"
|
||||
height={8}
|
||||
icon={Icons.DRAG}
|
||||
width={8}
|
||||
|
Loading…
x
Reference in New Issue
Block a user