mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-01 13:13:10 +00:00
287 lines
8.4 KiB
TypeScript
287 lines
8.4 KiB
TypeScript
/*
|
|
* 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 { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import classNames from 'classnames';
|
|
import { cloneDeep } from 'lodash';
|
|
import { EditorContentRef, FormattedUsersData } from 'Models';
|
|
import React, { useRef, useState } from 'react';
|
|
import { UrlEntityCharRegEx } from '../../constants/regex.constants';
|
|
import { PageLayoutType } from '../../enums/layout.enum';
|
|
import { CreateGlossary } from '../../generated/api/data/createGlossary';
|
|
import {
|
|
errorMsg,
|
|
getCurrentUserId,
|
|
requiredField,
|
|
} from '../../utils/CommonUtils';
|
|
import { Button } from '../buttons/Button/Button';
|
|
import RichTextEditor from '../common/rich-text-editor/RichTextEditor';
|
|
import PageLayout from '../containers/PageLayout';
|
|
import Loader from '../Loader/Loader';
|
|
import ReviewerModal from '../Modals/ReviewerModal/ReviewerModal.component';
|
|
import Tags from '../tags/tags';
|
|
import { AddGlossaryProps } from './AddGlossary.interface';
|
|
|
|
const Field = ({ children }: { children: React.ReactNode }) => {
|
|
return <div className="tw-mt-4">{children}</div>;
|
|
};
|
|
|
|
const AddGlossary = ({
|
|
header,
|
|
allowAccess = true,
|
|
saveState = 'initial',
|
|
onCancel,
|
|
onSave,
|
|
}: AddGlossaryProps) => {
|
|
const markdownRef = useRef<EditorContentRef>();
|
|
|
|
const [showErrorMsg, setShowErrorMsg] = useState<{ [key: string]: boolean }>({
|
|
name: false,
|
|
invalidName: false,
|
|
description: false,
|
|
});
|
|
|
|
const [name, setName] = useState('');
|
|
const [description] = useState<string>('');
|
|
const [showRevieweModal, setShowRevieweModal] = useState(false);
|
|
const [reviewer, setReviewer] = useState<Array<FormattedUsersData>>([]);
|
|
|
|
const getDescription = () => {
|
|
return markdownRef.current?.getEditorContent() || '';
|
|
};
|
|
|
|
const onReviewerModalCancel = () => {
|
|
setShowRevieweModal(false);
|
|
};
|
|
|
|
const handleReviewerSave = (reviewer: Array<FormattedUsersData>) => {
|
|
setReviewer(reviewer);
|
|
onReviewerModalCancel();
|
|
};
|
|
|
|
const handleValidation = (
|
|
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
|
) => {
|
|
if (!allowAccess) {
|
|
return;
|
|
}
|
|
const value = event.target.value;
|
|
const eleName = event.target.name;
|
|
let { name, invalidName } = cloneDeep(showErrorMsg);
|
|
|
|
switch (eleName) {
|
|
case 'name': {
|
|
setName(value);
|
|
name = false;
|
|
invalidName = false;
|
|
|
|
break;
|
|
}
|
|
}
|
|
setShowErrorMsg((prev) => {
|
|
return { ...prev, name, invalidName };
|
|
});
|
|
};
|
|
|
|
const handleReviewerRemove = (
|
|
_event: React.MouseEvent<HTMLElement, MouseEvent>,
|
|
removedTag: string
|
|
) => {
|
|
setReviewer((pre) => pre.filter((option) => option.name !== removedTag));
|
|
};
|
|
|
|
const validateForm = () => {
|
|
const errMsg = {
|
|
name: !name.trim(),
|
|
invalidName: UrlEntityCharRegEx.test(name.trim()),
|
|
description: !getDescription()?.trim(),
|
|
};
|
|
setShowErrorMsg(errMsg);
|
|
|
|
return !Object.values(errMsg).includes(true);
|
|
};
|
|
|
|
const handleSave = () => {
|
|
if (validateForm()) {
|
|
const data: CreateGlossary = {
|
|
name,
|
|
displayName: name,
|
|
description: getDescription(),
|
|
reviewers: reviewer.map((d) => ({ id: d.id, type: d.type })),
|
|
owner: {
|
|
id: getCurrentUserId(),
|
|
type: 'user',
|
|
},
|
|
};
|
|
|
|
onSave(data);
|
|
}
|
|
};
|
|
|
|
const getSaveButton = () => {
|
|
return allowAccess ? (
|
|
<>
|
|
{saveState === 'waiting' ? (
|
|
<Button
|
|
disabled
|
|
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
|
|
size="regular"
|
|
theme="primary"
|
|
variant="contained">
|
|
<Loader size="small" type="white" />
|
|
</Button>
|
|
) : saveState === 'success' ? (
|
|
<Button
|
|
disabled
|
|
className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
|
|
size="regular"
|
|
theme="primary"
|
|
variant="contained">
|
|
<FontAwesomeIcon icon="check" />
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
className={classNames('tw-w-16 tw-h-10', {
|
|
'tw-opacity-40': !allowAccess,
|
|
})}
|
|
data-testid="save-glossary"
|
|
size="regular"
|
|
theme="primary"
|
|
variant="contained"
|
|
onClick={handleSave}>
|
|
Save
|
|
</Button>
|
|
)}
|
|
</>
|
|
) : null;
|
|
};
|
|
|
|
const fetchRightPanel = () => {
|
|
return (
|
|
<>
|
|
<h6 className="tw-heading tw-text-base">Configure Glossary</h6>
|
|
<div className="tw-mb-5">
|
|
A Glossary is a controlled vocabulary used to define the concepts and
|
|
terminology in an organization. Glossaries can be specific to a
|
|
certain domain (for e.g., Business Glossary, Technical Glossary). In
|
|
the glossary, the standard terms and concepts can be defined along
|
|
with the synonyms, and related terms. Control can be established over
|
|
how and who can add the terms in the glossary.
|
|
</div>
|
|
{/* {getDocButton('Read Glossary Doc', '', 'glossary-doc')} */}
|
|
</>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<PageLayout
|
|
classes="tw-max-w-full-hd tw-h-full tw-bg-white tw-pt-4"
|
|
layout={PageLayoutType['2ColRTL']}
|
|
rightPanel={fetchRightPanel()}>
|
|
<h6 className="tw-heading tw-text-base">{header}</h6>
|
|
<div className="tw-pb-3" data-testid="add-glossary">
|
|
<Field>
|
|
<label className="tw-block tw-form-label" htmlFor="name">
|
|
{requiredField('Name:')}
|
|
</label>
|
|
|
|
<input
|
|
className="tw-form-inputs tw-form-inputs-padding"
|
|
data-testid="name"
|
|
id="name"
|
|
name="name"
|
|
placeholder="Name"
|
|
type="text"
|
|
value={name}
|
|
onChange={handleValidation}
|
|
/>
|
|
|
|
{showErrorMsg.name
|
|
? errorMsg('Glossary name is required.')
|
|
: showErrorMsg.invalidName
|
|
? errorMsg('Glossary name is invalid.')
|
|
: null}
|
|
</Field>
|
|
<Field>
|
|
<label
|
|
className="tw-block tw-form-label tw-mb-0"
|
|
htmlFor="description">
|
|
{requiredField('Description:')}
|
|
</label>
|
|
<RichTextEditor
|
|
data-testid="description"
|
|
initialValue={description}
|
|
readonly={!allowAccess}
|
|
ref={markdownRef}
|
|
/>
|
|
{showErrorMsg.description && errorMsg('Description is required.')}
|
|
</Field>
|
|
|
|
<div>
|
|
<div className="tw-flex tw-items-center tw-mt-4">
|
|
<p className="w-form-label tw-mr-3">Reviewers: </p>
|
|
<Button
|
|
className="tw-h-5 tw-px-2"
|
|
data-testid="add-reviewers"
|
|
size="x-small"
|
|
theme="primary"
|
|
variant="contained"
|
|
onClick={() => setShowRevieweModal(true)}>
|
|
<FontAwesomeIcon icon="plus" />
|
|
</Button>
|
|
</div>
|
|
<div className="tw-my-4">
|
|
{Boolean(reviewer.length) &&
|
|
reviewer.map((d, index) => {
|
|
return (
|
|
<Tags
|
|
editable
|
|
isRemovable
|
|
className="tw-bg-gray-200"
|
|
key={index}
|
|
removeTag={handleReviewerRemove}
|
|
tag={d.name}
|
|
type="contained"
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="tw-flex tw-justify-end">
|
|
<Button
|
|
data-testid="cancel-glossary"
|
|
size="regular"
|
|
theme="primary"
|
|
variant="text"
|
|
onClick={onCancel}>
|
|
Cancel
|
|
</Button>
|
|
{getSaveButton()}
|
|
</div>
|
|
</div>
|
|
|
|
{showRevieweModal && (
|
|
<ReviewerModal
|
|
header="Add Reviewer"
|
|
reviewer={reviewer}
|
|
onCancel={onReviewerModalCancel}
|
|
onSave={handleReviewerSave}
|
|
/>
|
|
)}
|
|
</PageLayout>
|
|
);
|
|
};
|
|
|
|
export default AddGlossary;
|