Fix #8846 : Replace Reviewer and deploy ingestion modal with antd modal in the UI -2 (#9076)

* Fix #8846 : Replace Reviewer and deploy ingestion with antd modal in the UI

* Fix : Failing cypress tests

* Address comments

* Addressing comments

* fixed failing cypress

* fixing glossary test

Co-authored-by: Shailesh Parmar <shailesh.parmar.webdev@gmail.com>
This commit is contained in:
Sachin Chaurasiya 2022-12-02 21:45:35 +05:30 committed by GitHub
parent 7241c3a975
commit 0306bc6d69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 362 additions and 301 deletions

View File

@ -82,6 +82,7 @@ it('Update table description and verify description after re-run', () => {
it('Add Usage ingestion', () => {
interceptURL('GET', 'api/v1/teams/name/Organization?fields=*', 'getSettingsPage');
interceptURL("POST", "/api/v1/services/ingestionPipelines/deploy/*", "deployIngestion");
cy.get('[data-testid="appbar-item-settings"]').should('be.visible').click({ force: true });
verifyResponseStatusCode('@getSettingsPage', 200);
// Services page
@ -118,16 +119,15 @@ it('Add Usage ingestion', () => {
scheduleIngestion();
// wait for ingestion to run
cy.clock();
cy.wait(10000);
cy.get('[data-testid="view-service-button"]')
.scrollIntoView()
.should('be.visible')
.click();
cy.wait("@deployIngestion").then(() => {
cy.get('[data-testid="view-service-button"]')
.scrollIntoView()
.should('be.visible')
.click();
handleIngestionRetry('database', true, 0, 'usage');
handleIngestionRetry('database', true, 0, 'usage');
});
});
it('Verify if usage is ingested properly',() => {

View File

@ -104,7 +104,7 @@ describe('Data Quality and Profiler should work properly', () => {
.scrollIntoView()
.contains('Profiler Ingestion')
.click();
cy.get('[data-testid="profileSample"]').should('be.visible').type(10);
cy.get('[data-testid="profileSample"]').should('be.visible').and('not.be.disabled').type(10);
cy.get('[data-testid="next-button"]')
.scrollIntoView()
.should('be.visible')
@ -112,10 +112,6 @@ describe('Data Quality and Profiler should work properly', () => {
scheduleIngestion();
// wait for ingestion to run
cy.clock();
cy.wait(10000);
cy.wait("@deployIngestion").then(() => {
cy.get('[data-testid="view-service-button"]')
.scrollIntoView()

View File

@ -24,8 +24,9 @@ import {
} from '../../constants/constants';
const createGlossaryTerm = (term) => {
cy.get('[data-testid="header"]')
.should('be.visible')
cy.get('[data-testid="breadcrumb-link"]')
.should('exist')
.and('be.visible')
.contains(NEW_GLOSSARY.name)
.should('exist');
cy.get('[data-testid="add-new-tag-button"]').should('be.visible').click();
@ -62,12 +63,12 @@ const createGlossaryTerm = (term) => {
.click();
verifyResponseStatusCode('@createGlossaryTerms', 201);
cy.get('#left-panelV1').contains(term.name).should('be.visible');
cy.get('[data-testid="glossary-left-panel"]').contains(term.name).should('be.visible');
};
const deleteGlossary = ({ name }) => {
verifyResponseStatusCode('@getGlossaryTerms', 200);
cy.get('#left-panelV1').contains(name).should('be.visible').click();
cy.get('[data-testid="glossary-left-panel"]').contains(name).should('be.visible').click();
cy.wait(500);
cy.get('[data-testid="inactive-link"]').contains(name).should('be.visible');
@ -90,11 +91,11 @@ const deleteGlossary = ({ name }) => {
toastNotification('Glossary term deleted successfully!')
cy.get('.tw-modal-container').should('not.exist');
cy.get('#left-panelV1').should('be.visible').should('not.contain', name)
cy.get('[data-testid="glossary-left-panel"]').should('be.visible').should('not.contain', name)
};
const goToAssetsTab = (term) => {
cy.get('#left-panelV1').should('be.visible').contains(term).click();
cy.get('[data-testid="glossary-left-panel"]').should('be.visible').contains(term).click();
cy.wait(500);
cy.get('[data-testid="inactive-link"]').contains(term).should('be.visible');
cy.get('[data-testid="Assets"]').should('be.visible').click();
@ -112,7 +113,8 @@ describe('Glossary page should work properly', () => {
.click({ animationDistanceThreshold: 10 });
//Clicking on Glossary
cy.get('[data-testid="appbar-item-glossary"]')
.should('be.visible')
.should('exist')
.and('be.visible')
.click();
// Todo: need to remove below uncaught exception once tree-view error resolves
@ -124,6 +126,8 @@ describe('Glossary page should work properly', () => {
});
it('Create new glossary flow should work properly', () => {
interceptURL('POST', '/api/v1/glossaries', 'createGlossary');
// check for no data placeholder
cy.get('[data-testid="add-new-glossary"]').should('be.visible').as('addNewGlossary');
@ -148,10 +152,12 @@ describe('Glossary page should work properly', () => {
.should('be.visible')
.click();
cy.get('.tw-modal-container').should('be.visible');
cy.get('[data-testid="confirmation-modal"]').should('exist').within(() => {
cy.get('[role="dialog"]').should('be.visible');
});
//Change this once issue related to suggestion API is fixed.
cy.get('.tw-grid > [data-testid="user-card-container"]')
cy.get('[data-testid="user-card-container"]')
.first()
.should('be.visible')
.as('reviewer');
@ -161,8 +167,8 @@ describe('Glossary page should work properly', () => {
.should('be.visible')
.check();
cy.get('[data-testid="saveButton"]').should('be.visible').click();
cy.get('.tw-modal-container').should('not.exist');
cy.get('[data-testid="save-button"]').should('exist').and('be.visible').click();
cy.get('[data-testid="delete-confirmation-modal"]').should('not.exist');
cy.get('[data-testid="reviewers-container"]')
.children()
.should('have.length', 1);
@ -172,12 +178,15 @@ describe('Glossary page should work properly', () => {
.should('be.visible')
.click();
cy.get('[data-testid="inactive-link"]')
.should('be.visible')
.invoke('text')
.then((text) => {
expect(text).to.equal(NEW_GLOSSARY.name);
});
cy.wait("@createGlossary").then(() => {
cy.url().should('include', '/glossary/')
cy.get('[data-testid="breadcrumb-link"]')
.should('exist')
.and('be.visible')
.within(() => {
cy.contains(NEW_GLOSSARY.name);
})
});
});
it('Verify added glossary details', () => {
@ -205,10 +214,12 @@ describe('Glossary page should work properly', () => {
const newDescription = 'Updated description';
// updating tags
cy.get('[data-testid="tag-container"]')
.scrollIntoView()
.should('be.visible')
.click();
.should('exist')
.and('be.visible')
.within(() => {
cy.get('[data-testid="add-tag"]').should('exist').and('be.visible').click()
});
cy.get('[class*="-control"]')
.scrollIntoView()
.should('be.visible')
@ -221,7 +232,7 @@ describe('Glossary page should work properly', () => {
.scrollIntoView()
.contains('PersonalData.Personal')
.should('be.visible');
// updating description
cy.get('[data-testid="edit-description"]').should('be.visible').click();
cy.get('.tw-modal-container').should('be.visible');
@ -250,7 +261,7 @@ describe('Glossary page should work properly', () => {
const uSynonyms = ['pick up', 'take', 'obtain'];
const newRef = { name: 'take', url: 'https://take.com' };
const newDescription = 'Updated description';
cy.get('#left-panelV1').should('be.visible').contains(term).click();
cy.get('[data-testid="glossary-left-panel"]').should('be.visible').contains(term).click();
verifyResponseStatusCode('@permissionApi', 200);
verifyResponseStatusCode('@glossaryAPI', 200);
@ -423,7 +434,7 @@ describe('Glossary page should work properly', () => {
cy.get('[data-testid="governance"]')
.should('exist')
.should('be.visible')
.and('be.visible')
.click();
cy.get('[data-testid="appbar-item-glossary"]')
.should('exist')
@ -513,7 +524,7 @@ describe('Glossary page should work properly', () => {
.should('be.visible')
.click();
cy.get('.tw-modal-container').should('be.visible');
cy.get('[data-testid="delete-confirmation-modal"]').should('be.visible');
cy.get('[data-testid="modal-header"]').should('be.visible').should('contain', `Delete ${NEW_GLOSSARY.name}`);
cy.get('[data-testid="confirmation-text-input"]')
.should('be.visible')

View File

@ -248,16 +248,14 @@ const TestSuiteIngestion: React.FC<TestSuiteIngestionProps> = ({
/>
)}
</Col>
{showDeployModal && (
<DeployIngestionLoaderModal
action={ingestionAction}
ingestionName={ingestionData?.name || ''}
isDeployed={isIngestionDeployed}
isIngestionCreated={isIngestionCreated}
progress={ingestionProgress}
/>
)}
<DeployIngestionLoaderModal
action={ingestionAction}
ingestionName={ingestionData?.name || ''}
isDeployed={isIngestionDeployed}
isIngestionCreated={isIngestionCreated}
progress={ingestionProgress}
visible={showDeployModal}
/>
</Row>
);
};

View File

@ -14,6 +14,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Space } from 'antd';
import classNames from 'classnames';
import { t } from 'i18next';
import { cloneDeep } from 'lodash';
import { EditorContentRef, EntityTags } from 'Models';
import React, { useRef, useState } from 'react';
@ -58,7 +59,7 @@ const AddGlossary = ({
const [name, setName] = useState('');
const [description] = useState<string>('');
const [showRevieweModal, setShowRevieweModal] = useState(false);
const [showReviewerModal, setShowReviewerModal] = useState(false);
const [tags, setTags] = useState<EntityTags[]>([]);
const [reviewer, setReviewer] = useState<Array<EntityReference>>([]);
@ -67,7 +68,7 @@ const AddGlossary = ({
};
const onReviewerModalCancel = () => {
setShowRevieweModal(false);
setShowReviewerModal(false);
};
const handleReviewerSave = (reviewer: Array<EntityReference>) => {
@ -255,7 +256,7 @@ const AddGlossary = ({
size="x-small"
theme="primary"
variant="contained"
onClick={() => setShowRevieweModal(true)}>
onClick={() => setShowReviewerModal(true)}>
<FontAwesomeIcon icon="plus" />
</Button>
</div>
@ -289,15 +290,13 @@ const AddGlossary = ({
{getSaveButton()}
</div>
</div>
{showRevieweModal && (
<ReviewerModal
header="Add Reviewer"
reviewer={reviewer}
onCancel={onReviewerModalCancel}
onSave={handleReviewerSave}
/>
)}
<ReviewerModal
header={t('label.add-reviewer')}
reviewer={reviewer}
visible={showReviewerModal}
onCancel={onReviewerModalCancel}
onSave={handleReviewerSave}
/>
</div>
</PageLayout>
);

View File

@ -14,6 +14,7 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Space } from 'antd';
import classNames from 'classnames';
import { t } from 'i18next';
import { cloneDeep, isEmpty, isUndefined } from 'lodash';
import { EditorContentRef, EntityTags } from 'Models';
import React, { useEffect, useRef, useState } from 'react';
@ -72,7 +73,7 @@ const AddGlossaryTerm = ({
const [name, setName] = useState('');
const [description] = useState<string>('');
const [showRevieweModal, setShowRevieweModal] = useState(false);
const [showReviewerModal, setShowReviewerModal] = useState(false);
const [showRelatedTermsModal, setShowRelatedTermsModal] = useState(false);
const [reviewer, setReviewer] = useState<Array<EntityReference>>([]);
const [tags, setTags] = useState<EntityTags[]>([]);
@ -100,7 +101,7 @@ const AddGlossaryTerm = ({
};
const onReviewerModalCancel = () => {
setShowRevieweModal(false);
setShowReviewerModal(false);
};
const handleReviewerSave = (reviewer: Array<EntityReference>) => {
@ -478,7 +479,7 @@ const AddGlossaryTerm = ({
size="x-small"
theme="primary"
variant="contained"
onClick={() => setShowRevieweModal(true)}>
onClick={() => setShowReviewerModal(true)}>
<FontAwesomeIcon icon="plus" />
</Button>
</div>
@ -513,23 +514,21 @@ const AddGlossaryTerm = ({
</Field>
</div>
{showRelatedTermsModal && (
<RelatedTermsModal
header="Add Related Terms"
relatedTerms={relatedTerms}
onCancel={onRelatedTermsModalCancel}
onSave={handleRelatedTermsSave}
/>
)}
<RelatedTermsModal
header={t('label.add-related-terms')}
relatedTerms={relatedTerms}
visible={showRelatedTermsModal}
onCancel={onRelatedTermsModalCancel}
onSave={handleRelatedTermsSave}
/>
{showRevieweModal && (
<ReviewerModal
header="Add Reviewers"
reviewer={reviewer}
onCancel={onReviewerModalCancel}
onSave={handleReviewerSave}
/>
)}
<ReviewerModal
header={t('label.add-reviewers')}
reviewer={reviewer}
visible={showReviewerModal}
onCancel={onReviewerModalCancel}
onSave={handleReviewerSave}
/>
</div>
</PageLayout>
);

View File

@ -832,15 +832,14 @@ const AddIngestion = ({
/>
)}
{showDeployModal && (
<DeployIngestionLoaderModal
action={ingestionAction}
ingestionName={ingestionName}
isDeployed={isIngestionDeployed}
isIngestionCreated={isIngestionCreated}
progress={ingestionProgress}
/>
)}
<DeployIngestionLoaderModal
action={ingestionAction}
ingestionName={ingestionName}
isDeployed={isIngestionDeployed}
isIngestionCreated={isIngestionCreated}
progress={ingestionProgress}
visible={showDeployModal}
/>
</div>
</div>
);

View File

@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button as ButtonAntd, Card as AntdCard, Tooltip } from 'antd';
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { t } from 'i18next';
import { cloneDeep, debounce, includes, isEqual } from 'lodash';
import { EntityTags } from 'Models';
import React, { useCallback, useEffect, useState } from 'react';
@ -523,14 +524,13 @@ const GlossaryDetails = ({ permissions, glossary, updateGlossary }: props) => {
</div>
</div>
{showRevieweModal && (
<ReviewerModal
header="Add Reviewer"
reviewer={reviewer}
onCancel={onReviewerModalCancel}
onSave={handleReviewerSave}
/>
)}
<ReviewerModal
header={t('label.add-reviewer')}
reviewer={reviewer}
visible={showRevieweModal}
onCancel={onReviewerModalCancel}
onSave={handleReviewerSave}
/>
</div>
);
};

View File

@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, Card, Col, Divider, Row, Tooltip, Typography } from 'antd';
import { AxiosError } from 'axios';
import classNames from 'classnames';
import { t } from 'i18next';
import { cloneDeep, includes, isEqual } from 'lodash';
import { AssetsDataType, EntityTags } from 'Models';
import React, { useEffect, useState } from 'react';
@ -429,14 +430,13 @@ const GlossaryTermsV1 = ({
)}
</div>
{showRevieweModal && (
<ReviewerModal
header="Add Reviewer"
reviewer={reviewer}
onCancel={onReviewerModalCancel}
onSave={handleReviewerSave}
/>
)}
<ReviewerModal
header={t('label.add-reviewer')}
reviewer={reviewer}
visible={showRevieweModal}
onCancel={onReviewerModalCancel}
onSave={handleReviewerSave}
/>
</div>
</div>
);

View File

@ -0,0 +1,22 @@
/*
* 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.
*/
export type DeployIngestionLoaderModalProps = {
className?: string;
ingestionName: string;
action: string;
progress: number;
isIngestionCreated: boolean;
isDeployed: boolean;
visible: boolean;
};

View File

@ -15,6 +15,7 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router';
import DeployIngestionLoaderModal from './DeployIngestionLoaderModal';
import { DeployIngestionLoaderModalProps } from './DeployIngestionLoaderModal.interface';
const deployIngestionLoaderModalProps = {
ingestionName: 'test_metadata',
@ -22,7 +23,8 @@ const deployIngestionLoaderModalProps = {
progress: 0,
isIngestionCreated: false,
isDeployed: false,
};
visible: true,
} as DeployIngestionLoaderModalProps;
describe('Test DeployIngestionLoaderModal component', () => {
it('Component should render properly', async () => {

View File

@ -11,18 +11,14 @@
* limitations under the License.
*/
import { Typography } from 'antd';
import Modal from 'antd/lib/modal/Modal';
import classNames from 'classnames';
import React, { Fragment } from 'react';
import { LITE_GRAY_COLOR, PRIMERY_COLOR } from '../../../constants/constants';
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
type Props = {
className?: string;
ingestionName: string;
action: string;
progress: number;
isIngestionCreated: boolean;
isDeployed: boolean;
};
import { DeployIngestionLoaderModalProps } from './DeployIngestionLoaderModal.interface';
const DeployIngestionLoaderModal = ({
className,
ingestionName,
@ -30,67 +26,63 @@ const DeployIngestionLoaderModal = ({
progress,
isIngestionCreated,
isDeployed,
}: Props) => {
visible,
}: DeployIngestionLoaderModalProps) => {
const isActive = (value: boolean) => {
return value ? PRIMERY_COLOR : LITE_GRAY_COLOR;
};
return (
<dialog
className={classNames('tw-modal', className)}
data-testid="deploy-modal">
<div className="tw-modal-backdrop" />
<div className="tw-modal-container tw-w-120">
<div className="tw-modal-body tw-h-40" data-testid="body-text">
<div
className={classNames('ingestion-content tw-relative', className)}>
<Fragment>
<span
className={classNames('ingestion-deploy-line')}
style={{
background: `linear-gradient(to right, ${PRIMERY_COLOR} ${progress}%, ${LITE_GRAY_COLOR} ${progress}%)`,
}}
/>
<Modal
centered
destroyOnClose
className={classNames('h-40', className)}
closable={false}
data-testid="deploy-modal"
footer={null}
visible={visible}>
<div className="p-y-lg flex flex-col" data-testid="body-text">
<div className={classNames('ingestion-content relative', className)}>
<Fragment>
<Typography.Text
className={classNames('ingestion-deploy-line')}
style={{
background: `linear-gradient(to right, ${PRIMERY_COLOR} ${progress}%, ${LITE_GRAY_COLOR} ${progress}%)`,
}}
/>
<div
className="ingestion-wrapper tw-absolute"
style={{ left: '16%' }}>
<span
className={classNames(
'ingestion-deploy-rounder tw-self-center'
)}
style={{
background: isActive(isIngestionCreated),
}}>
<span className="tw-flex-center tw-h-full">
<SVGIcons alt="" icon={Icons.CREATE_INGESTION} />
</span>
</span>
</div>
<div
className="ingestion-wrapper tw-absolute"
style={{ left: '72%' }}>
<span
className={classNames(
'ingestion-deploy-rounder tw-self-center'
)}
style={{
background: isActive(isDeployed),
}}>
<span className="tw-flex-center tw-h-full">
<SVGIcons alt="" icon={Icons.DEPLOY_INGESTION} />
</span>
</span>
</div>
</Fragment>
</div>
<p className="tw-text-center tw-mt-24">
{action}
<span className="tw-font-semibold tw-ml-1">{ingestionName}</span>
</p>
<div className="ingestion-wrappe absolute" style={{ left: '16%' }}>
<Typography.Text
className={classNames('ingestion-deploy-rounder self-center')}
style={{
background: isActive(isIngestionCreated),
}}>
<Typography.Text className="flex-center h-full">
<SVGIcons alt="" icon={Icons.CREATE_INGESTION} />
</Typography.Text>
</Typography.Text>
</div>
<div className="ingestion-wrapper absolute" style={{ left: '72%' }}>
<Typography.Text
className={classNames('ingestion-deploy-rounder self-center')}
style={{
background: isActive(isDeployed),
}}>
<Typography.Text className="flex-center h-full">
<SVGIcons alt="" icon={Icons.DEPLOY_INGESTION} />
</Typography.Text>
</Typography.Text>
</div>
</Fragment>
</div>
<Typography.Text className="text-center mt-24">
{action}
<Typography.Text className="font-semibold m-l-xss">
{ingestionName}
</Typography.Text>
</Typography.Text>
</div>
</dialog>
</Modal>
);
};

View File

@ -0,0 +1,23 @@
/*
* 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 { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
export type RelatedTermsModalProp = {
glossaryTermFQN?: string;
relatedTerms?: Array<GlossaryTerm>;
onCancel: () => void;
onSave: (terms: Array<GlossaryTerm>) => void;
header: string;
visible: boolean;
};

View File

@ -11,8 +11,9 @@
* limitations under the License.
*/
import { isUndefined } from 'lodash';
import { Button, Col, Modal, Row, Typography } from 'antd';
import { t } from 'i18next';
import { isUndefined, uniqueId } from 'lodash';
import React, { useEffect, useState } from 'react';
import { searchData } from '../../../axiosAPIs/miscAPI';
import { PAGE_SIZE } from '../../../constants/constants';
@ -20,17 +21,9 @@ import { SearchIndex } from '../../../enums/search.enum';
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
import CheckboxUserCard from '../../../pages/teams/CheckboxUserCard';
import { formatSearchGlossaryTermResponse } from '../../../utils/APIUtils';
import { Button } from '../../buttons/Button/Button';
import Searchbar from '../../common/searchbar/Searchbar';
import Loader from '../../Loader/Loader';
type RelatedTermsModalProp = {
glossaryTermFQN?: string;
relatedTerms?: Array<GlossaryTerm>;
onCancel: () => void;
onSave: (terms: Array<GlossaryTerm>) => void;
header: string;
};
import { RelatedTermsModalProp } from './RelatedTermsModal.interface';
const RelatedTermsModal = ({
glossaryTermFQN = '',
@ -38,6 +31,7 @@ const RelatedTermsModal = ({
onCancel,
onSave,
header,
visible,
}: RelatedTermsModalProp) => {
const [searchText, setSearchText] = useState('');
const [isLoading, setIsLoading] = useState(true);
@ -98,24 +92,6 @@ const RelatedTermsModal = ({
}
};
const getUserCards = () => {
return options.map((d) => (
<CheckboxUserCard
isActionVisible
isCheckBoxes
item={{
name: '',
displayName: d.displayName || d.name,
id: d.id,
type: 'tag',
isChecked: isIncludeInOptions(d.id),
}}
key={d.id}
onSelect={selectionHandler}
/>
));
};
useEffect(() => {
if (!isUndefined(relatedTerms) && relatedTerms.length) {
setOptions(relatedTerms);
@ -124,57 +100,77 @@ const RelatedTermsModal = ({
}, []);
return (
<dialog className="tw-modal" data-testid="modal-container">
<div className="tw-modal-backdrop" onClick={() => onCancel()} />
<div className="tw-modal-container tw-overflow-y-auto tw-max-w-3xl tw-max-h-screen">
<div className="tw-modal-header">
<p className="tw-modal-title tw-text-grey-body" data-testid="header">
{header}
</p>
</div>
<div className="tw-modal-body">
<Searchbar
placeholder="Search for a term..."
searchValue={searchText}
typingInterval={500}
onSearch={handleSearchAction}
/>
<div className="tw-min-h-256">
{isLoading ? (
<Loader />
) : options.length > 0 ? (
<div className="tw-grid tw-grid-cols-3 tw-gap-4">
{getUserCards()}
</div>
) : (
<p className="tw-text-center tw-mt-10 tw-text-grey-muted tw-text-base">
{searchText
? `No terms found for "${searchText}"`
: 'No terms found'}
</p>
)}
</div>
</div>
<div className="tw-modal-footer" data-testid="cta-container">
<Modal
centered
destroyOnClose
closable={false}
data-testid="confirmation-modal"
footer={
<div data-testid="cta-container">
<Button
size="regular"
theme="primary"
variant="link"
data-testid="cancelButton"
key="remove-edge-btn"
type="text"
onClick={onCancel}>
Cancel
{t('label.cancel')}
</Button>
<Button
data-testid="saveButton"
size="regular"
theme="primary"
type="submit"
variant="contained"
key="save-btn"
type="primary"
onClick={() => onSave(selectedOption)}>
Save
{t('label.save')}
</Button>
</div>
}
title={
<Typography.Text strong data-testid="header">
{header}
</Typography.Text>
}
visible={visible}
width={800}>
<div className="h-full">
<Searchbar
placeholder={`${t('label.search-for-user')}...`}
searchValue={searchText}
typingInterval={500}
onSearch={handleSearchAction}
/>
{isLoading ? (
<Loader />
) : options.length > 0 ? (
<Row gutter={[16, 16]}>
{options.map((d) => (
<Col key={uniqueId()} span={8}>
<CheckboxUserCard
isActionVisible
isCheckBoxes
item={{
name: '',
displayName: d.displayName || d.name,
id: d.id,
type: 'tag',
isChecked: isIncludeInOptions(d.id),
}}
key={d.id}
onSelect={selectionHandler}
/>
</Col>
))}
</Row>
) : (
<Typography.Text className="flex justify-center mt-10 text-grey-muted text-base">
{searchText
? t('label.no-terms-found-for-searchText', {
searchText,
})
: t('label.no-terms-found')}
</Typography.Text>
)}
</div>
</dialog>
</Modal>
);
};

View File

@ -11,15 +11,15 @@
* limitations under the License.
*/
import { Button } from 'antd';
import { isUndefined } from 'lodash';
import { Button, Col, Row, Typography } from 'antd';
import Modal from 'antd/lib/modal/Modal';
import { isUndefined, uniqueId } from 'lodash';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { getSuggestedUsers, searchData } from '../../../axiosAPIs/miscAPI';
import { WILD_CARD_CHAR } from '../../../constants/char.constants';
import { SearchIndex } from '../../../enums/search.enum';
import { User } from '../../../generated/entity/teams/user';
import { EntityReference } from '../../../generated/type/entityReference';
import { SearchResponse } from '../../../interface/search.interface';
import CheckboxUserCard from '../../../pages/teams/CheckboxUserCard';
import { formatUsersResponse } from '../../../utils/APIUtils';
@ -29,19 +29,14 @@ import {
getEntityReferenceFromUser,
getUserFromEntityReference,
} from '../../Users/Users.util';
type ReviewerModalProp = {
reviewer?: Array<EntityReference>;
onCancel: () => void;
onSave: (reviewer: Array<EntityReference>) => void;
header: string;
};
import { ReviewerModalProp } from './ReviewerModal.interface';
const ReviewerModal = ({
reviewer,
onCancel,
onSave,
header,
visible,
}: ReviewerModalProp) => {
const [searchText, setSearchText] = useState('');
const [isLoading, setIsLoading] = useState(true);
@ -116,26 +111,6 @@ const ReviewerModal = ({
}
};
const getUserCards = () => {
return options.map((d) => (
<CheckboxUserCard
isActionVisible
isCheckBoxes
isIconVisible
item={{
name: d.name,
displayName: d.displayName || d.name,
email: d.email,
id: d.id,
isChecked: isIncludeInOptions(d.id),
type: 'user',
}}
key={d.id}
onSelect={selectionHandler}
/>
));
};
useEffect(() => {
if (!isUndefined(reviewer) && reviewer.length) {
setOptions(reviewer.map(getUserFromEntityReference));
@ -144,39 +119,23 @@ const ReviewerModal = ({
}, []);
return (
<dialog className="tw-modal" data-testid="modal-container">
<div className="tw-modal-backdrop" onClick={() => onCancel()} />
<div className="tw-modal-container tw-overflow-y-auto tw-max-w-3xl tw-max-h-screen">
<div className="tw-modal-header">
<p className="tw-modal-title tw-text-grey-body" data-testid="header">
{header}
</p>
</div>
<div className="tw-modal-body">
<Searchbar
placeholder="Search for user..."
searchValue={searchText}
typingInterval={500}
onSearch={handleSearchAction}
/>
<div className="tw-min-h-256">
{isLoading ? (
<Loader />
) : options.length > 0 ? (
<div className="tw-grid tw-grid-cols-3 tw-gap-4">
{getUserCards()}
</div>
) : (
<p className="tw-text-center tw-mt-10 tw-text-grey-muted tw-text-base">
No user available
</p>
)}
</div>
</div>
<div className="tw-modal-footer" data-testid="cta-container">
<Button onClick={onCancel}>{t('label.cancel')}</Button>
<Modal
centered
destroyOnClose
closable={false}
data-testid="confirmation-modal"
footer={
<div data-testid="cta-container">
<Button
data-testid="saveButton"
data-testid="cancel"
key="remove-edge-btn"
type="text"
onClick={onCancel}>
{t('label.cancel')}
</Button>
<Button
data-testid="save-button"
key="save-btn"
type="primary"
onClick={() =>
onSave(selectedOption.map(getEntityReferenceFromUser))
@ -184,8 +143,52 @@ const ReviewerModal = ({
{t('label.save')}
</Button>
</div>
</div>
</dialog>
}
title={
<Typography.Text strong data-testid="header">
{header}
</Typography.Text>
}
visible={visible}
width={800}>
<>
<Searchbar
placeholder={`${t('label.search-for-user')}...`}
searchValue={searchText}
typingInterval={500}
onSearch={handleSearchAction}
/>
{isLoading ? (
<Loader />
) : options.length > 0 ? (
<Row gutter={[16, 16]}>
{options.map((d) => (
<Col key={uniqueId()} span={8}>
<CheckboxUserCard
isActionVisible
isCheckBoxes
isIconVisible
item={{
name: d.name,
displayName: d.displayName || d.name,
email: d.email,
id: d.id,
isChecked: isIncludeInOptions(d.id),
type: 'user',
}}
key={d.id}
onSelect={selectionHandler}
/>
</Col>
))}
</Row>
) : (
<Typography.Text className="flex justify-center mt-10 text-grey-muted text-base">
{t('label.no-user-available')}
</Typography.Text>
)}
</>
</Modal>
);
};

View File

@ -0,0 +1,21 @@
/*
* 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 { EntityReference } from '../../../generated/type/entityReference';
export type ReviewerModalProp = {
reviewer?: Array<EntityReference>;
onCancel: () => void;
onSave: (reviewer: Array<EntityReference>) => void;
header: string;
visible: boolean;
};