Fix UI : #4124 Remove Save and Discard button from manage tab (#4273)

This commit is contained in:
Sachin Chaurasiya 2022-04-20 23:36:56 +05:30 committed by GitHub
parent a977aa90bc
commit 8942c2b6bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 345 additions and 198 deletions

View File

@ -16,6 +16,8 @@ import {
faCheck, faCheck,
faCheckCircle, faCheckCircle,
faCheckSquare, faCheckSquare,
faChevronDown,
faChevronRight,
faPlus, faPlus,
faSearch, faSearch,
faTimes, faTimes,
@ -28,7 +30,16 @@ import { AuthProvider } from './authentication/auth-provider/AuthProvider';
import AppRouter from './router/AppRouter'; import AppRouter from './router/AppRouter';
const App: FunctionComponent = () => { const App: FunctionComponent = () => {
library.add(faTimes, faCheck, faSearch, faPlus, faCheckSquare, faCheckCircle); library.add(
faTimes,
faCheck,
faSearch,
faPlus,
faCheckSquare,
faCheckCircle,
faChevronDown,
faChevronRight
);
return ( return (
<div className="main-container"> <div className="main-container">

View File

@ -626,30 +626,6 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
onUpdate={onColumnsUpdate} onUpdate={onColumnsUpdate}
/> />
</div> </div>
{threadLink ? (
<ActivityThreadPanel
createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)}
postFeedHandler={postFeedHandler}
threadLink={threadLink}
onCancel={onThreadPanelClose}
/>
) : null}
{selectedField ? (
<RequestDescriptionModal
createThread={createThread}
defaultValue={getDefaultValue(owner)}
header="Request description"
threadLink={getEntityFeedLink(
EntityType.TABLE,
datasetFQN,
selectedField
)}
onCancel={closeRequestModal}
/>
) : null}
</div> </div>
)} )}
{activeTab === 2 && ( {activeTab === 2 && (
@ -776,6 +752,29 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
{getLoader()} {getLoader()}
</div> </div>
</div> </div>
{threadLink ? (
<ActivityThreadPanel
createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)}
postFeedHandler={postFeedHandler}
threadLink={threadLink}
onCancel={onThreadPanelClose}
/>
) : null}
{selectedField ? (
<RequestDescriptionModal
createThread={createThread}
defaultValue={getDefaultValue(owner)}
header="Request description"
threadLink={getEntityFeedLink(
EntityType.TABLE,
datasetFQN,
selectedField
)}
onCancel={closeRequestModal}
/>
) : null}
</div> </div>
</div> </div>
</PageContainer> </PageContainer>

View File

@ -651,6 +651,7 @@ const EntityTable = ({
trigger="mouseenter"> trigger="mouseenter">
<SVGIcons <SVGIcons
alt="request-description" alt="request-description"
className="tw-mt-2.5"
icon={Icons.REQUEST} icon={Icons.REQUEST}
/> />
</PopOver> </PopOver>

View File

@ -12,19 +12,21 @@
*/ */
import classNames from 'classnames'; import classNames from 'classnames';
import React, { FunctionComponent } from 'react'; import React, { CSSProperties, FunctionComponent } from 'react';
import './Loader.css'; import './Loader.css';
type Props = { type Props = {
size?: 'default' | 'small'; size?: 'default' | 'small';
type?: 'default' | 'success' | 'error' | 'white'; type?: 'default' | 'success' | 'error' | 'white';
className?: string; className?: string;
style?: CSSProperties;
}; };
const Loader: FunctionComponent<Props> = ({ const Loader: FunctionComponent<Props> = ({
size = 'default', size = 'default',
type = 'default', type = 'default',
className = '', className = '',
style,
}: Props): JSX.Element => { }: Props): JSX.Element => {
let classes = 'loader'; let classes = 'loader';
switch (size) { switch (size) {
@ -54,7 +56,11 @@ const Loader: FunctionComponent<Props> = ({
} }
return ( return (
<div className={classNames(classes, className)} data-testid="loader" /> <div
className={classNames(classes, className)}
data-testid="loader"
style={style}
/>
); );
}; };

View File

@ -24,6 +24,7 @@ import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'
import { deleteEntity } from '../../axiosAPIs/miscAPI'; import { deleteEntity } from '../../axiosAPIs/miscAPI';
import { getCategory } from '../../axiosAPIs/tagAPI'; import { getCategory } from '../../axiosAPIs/tagAPI';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants';
import { ENTITY_DELETE_STATE } from '../../constants/entity.constants'; import { ENTITY_DELETE_STATE } from '../../constants/entity.constants';
import { Operation } from '../../generated/entity/policies/accessControl/rule'; import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { useAuth } from '../../hooks/authHooks'; import { useAuth } from '../../hooks/authHooks';
@ -38,7 +39,7 @@ import NonAdminAction from '../common/non-admin-action/NonAdminAction';
import DropDownList from '../dropdown/DropDownList'; import DropDownList from '../dropdown/DropDownList';
import Loader from '../Loader/Loader'; import Loader from '../Loader/Loader';
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal'; import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
import { ManageProps } from './ManageTab.interface'; import { ManageProps, Status } from './ManageTab.interface';
const ManageTab: FunctionComponent<ManageProps> = ({ const ManageTab: FunctionComponent<ManageProps> = ({
currentTier = '', currentTier = '',
@ -57,10 +58,8 @@ const ManageTab: FunctionComponent<ManageProps> = ({
const { userPermissions, isAdminUser } = useAuth(); const { userPermissions, isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext(); const { isAuthDisabled } = useAuthContext();
const [loading, setLoading] = useState<boolean>(false); const [statusOwner, setStatusOwner] = useState<Status>('initial');
const [status, setStatus] = useState<'initial' | 'waiting' | 'success'>( const [statusTier, setStatusTier] = useState<Status>('initial');
'initial'
);
const [activeTier, setActiveTier] = useState(currentTier); const [activeTier, setActiveTier] = useState(currentTier);
const [listVisible, setListVisible] = useState(false); const [listVisible, setListVisible] = useState(false);
const [teamJoinable, setTeamJoinable] = useState(isJoinable); const [teamJoinable, setTeamJoinable] = useState(isJoinable);
@ -80,6 +79,67 @@ const ManageTab: FunctionComponent<ManageProps> = ({
return allowTeamOwner ? ['Teams', 'Users'] : ['Users']; return allowTeamOwner ? ['Teams', 'Users'] : ['Users'];
}; };
const setInitialOwnerLoadingState = () => {
setStatusOwner('initial');
};
const setInitialTierLoadingState = () => {
setStatusTier('initial');
};
const prepareOwner = (updatedOwner: string) => {
return updatedOwner !== currentUser
? {
id: updatedOwner,
type: listOwners.find((item) => item.value === updatedOwner)?.type as
| 'user'
| 'team',
}
: undefined;
};
const prepareTier = (updatedTier: string) => {
return updatedTier !== currentTier ? updatedTier : undefined;
};
const handleOwnerSave = (updatedOwner: string, updatedTier: string) => {
setStatusOwner('waiting');
const newOwner: TableDetail['owner'] = prepareOwner(updatedOwner);
if (hideTier) {
if (newOwner || !isUndefined(teamJoinable)) {
onSave(newOwner, '', Boolean(teamJoinable)).catch(() => {
setInitialOwnerLoadingState();
});
} else {
setInitialOwnerLoadingState();
}
} else {
const newTier = prepareTier(updatedTier);
onSave(newOwner, newTier, Boolean(teamJoinable)).catch(() => {
setInitialOwnerLoadingState();
});
}
};
const handleTierSave = (updatedTier: string) => {
setStatusTier('waiting');
const newOwner: TableDetail['owner'] = prepareOwner(currentUser);
if (hideTier) {
if (newOwner || !isUndefined(teamJoinable)) {
onSave(newOwner, '', Boolean(teamJoinable)).catch(() => {
setInitialTierLoadingState();
});
} else {
setInitialTierLoadingState();
}
} else {
const newTier = prepareTier(updatedTier);
onSave(newOwner, newTier, Boolean(teamJoinable)).catch(() => {
setInitialTierLoadingState();
});
}
};
const handleOwnerSelection = ( const handleOwnerSelection = (
_e: React.MouseEvent<HTMLElement, MouseEvent>, _e: React.MouseEvent<HTMLElement, MouseEvent>,
value?: string value?: string
@ -88,6 +148,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
const newOwner = listOwners.find((item) => item.value === value); const newOwner = listOwners.find((item) => item.value === value);
if (newOwner) { if (newOwner) {
setOwner(newOwner.value); setOwner(newOwner.value);
handleOwnerSave(newOwner.value, activeTier);
} }
} }
setListVisible(false); setListVisible(false);
@ -97,45 +158,6 @@ const ManageTab: FunctionComponent<ManageProps> = ({
setActiveTier(cardId); setActiveTier(cardId);
}; };
const setInitialLoadingState = () => {
setStatus('initial');
setLoading(false);
};
const handleSave = () => {
setLoading(true);
setStatus('waiting');
// Save API call goes here...
const newOwner: TableDetail['owner'] =
owner !== currentUser
? {
id: owner,
type: listOwners.find((item) => item.value === owner)?.type as
| 'user'
| 'team',
}
: undefined;
if (hideTier) {
if (newOwner || !isUndefined(teamJoinable)) {
onSave(newOwner, '', Boolean(teamJoinable)).catch(() => {
setInitialLoadingState();
});
} else {
setInitialLoadingState();
}
} else {
const newTier = activeTier !== currentTier ? activeTier : undefined;
onSave(newOwner, newTier, Boolean(teamJoinable)).catch(() => {
setInitialLoadingState();
});
}
};
const handleCancel = () => {
setActiveTier(currentTier);
setOwner(currentUser);
};
const handleOnEntityDelete = () => { const handleOnEntityDelete = () => {
setEntityDeleteState((prev) => ({ ...prev, state: true })); setEntityDeleteState((prev) => ({ ...prev, state: true }));
}; };
@ -193,7 +215,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
const getDeleteEntityWidget = () => { const getDeleteEntityWidget = () => {
return allowDelete && entityId && entityName && entityType ? ( return allowDelete && entityId && entityName && entityType ? (
<div className="tw-mt-9" data-testid="danger-zone"> <div className="tw-mt-1" data-testid="danger-zone">
<hr className="tw-border-main tw-mb-4" /> <hr className="tw-border-main tw-mb-4" />
<div className="tw-border tw-border-error tw-px-4 tw-py-2 tw-flex tw-justify-between tw-rounded tw-mt-3 tw-shadow"> <div className="tw-border tw-border-error tw-px-4 tw-py-2 tw-flex tw-justify-between tw-rounded tw-mt-3 tw-shadow">
<div data-testid="danger-zone-text"> <div data-testid="danger-zone-text">
@ -204,9 +226,19 @@ const ManageTab: FunctionComponent<ManageProps> = ({
{`Once you delete this ${entityType}, it would be removed permanently`} {`Once you delete this ${entityType}, it would be removed permanently`}
</p> </p>
</div> </div>
<NonAdminAction
className="tw-self-center"
html={
<Fragment>
<p>{TITLE_FOR_NON_ADMIN_ACTION}</p>
</Fragment>
}
isOwner={isAdminUser}
position="left">
<Button <Button
className="tw-px-2 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-shadow" className="tw-px-2 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-shadow"
data-testid="delete-button" data-testid="delete-button"
disabled={!isAdminUser && !isAuthDisabled}
size="custom" size="custom"
theme="primary" theme="primary"
type="button" type="button"
@ -214,6 +246,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
onClick={handleOnEntityDelete}> onClick={handleOnEntityDelete}>
Delete this {entityType} Delete this {entityType}
</Button> </Button>
</NonAdminAction>
</div> </div>
</div> </div>
) : null; ) : null;
@ -240,6 +273,9 @@ const ManageTab: FunctionComponent<ManageProps> = ({
<CardListItem <CardListItem
card={card} card={card}
isActive={activeTier === card.id} isActive={activeTier === card.id}
isSelected={card.id === currentTier}
tierStatus={statusTier}
onSave={handleTierSave}
onSelect={handleCardSelection} onSelect={handleCardSelection}
/> />
</NonAdminAction> </NonAdminAction>
@ -297,10 +333,8 @@ const ManageTab: FunctionComponent<ManageProps> = ({
); );
setTierData(tierData); setTierData(tierData);
setIsLoadingTierData(false);
} else { } else {
setTierData([]); setTierData([]);
setIsLoadingTierData(false);
} }
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
@ -308,9 +342,29 @@ const ManageTab: FunctionComponent<ManageProps> = ({
err, err,
jsonData['api-error-messages']['fetch-tiers-error'] jsonData['api-error-messages']['fetch-tiers-error']
); );
})
.finally(() => {
setIsLoadingTierData(false);
}); });
}; };
const getOwnerUpdateLoader = () => {
return (
<span className="tw-ml-4">
{statusOwner === 'waiting' ? (
<Loader
className="tw-inline-block"
size="small"
style={{ marginBottom: '-4px' }}
type="default"
/>
) : statusOwner === 'success' ? (
<FontAwesomeIcon icon="check" />
) : null}
</span>
);
};
useEffect(() => { useEffect(() => {
if (!hideTier) { if (!hideTier) {
getTierData(); getTierData();
@ -326,16 +380,22 @@ const ManageTab: FunctionComponent<ManageProps> = ({
}, [currentUser]); }, [currentUser]);
useEffect(() => { useEffect(() => {
if (statusOwner === 'waiting') {
setStatusOwner('success');
setTimeout(() => { setTimeout(() => {
setLoading(false); setInitialOwnerLoadingState();
}, 1000);
if (status === 'waiting') {
setStatus('success');
setTimeout(() => {
setStatus('initial');
}, 3000); }, 3000);
} }
}, [currentTier, currentUser]); }, [currentUser]);
useEffect(() => {
if (statusTier === 'waiting') {
setStatusTier('success');
setTimeout(() => {
setInitialTierLoadingState();
}, 3000);
}
}, [currentTier]);
useEffect(() => { useEffect(() => {
setListOwners(getOwnerList()); setListOwners(getOwnerList());
@ -350,7 +410,10 @@ const ManageTab: FunctionComponent<ManageProps> = ({
className="tw-max-w-3xl tw-mx-auto" className="tw-max-w-3xl tw-mx-auto"
data-testid="manage-tab" data-testid="manage-tab"
id="manageTabDetails"> id="manageTabDetails">
<div className="tw-mt-2 tw-mb-4 tw-pb-4 tw-border-b tw-border-separator "> <div
className={classNames('tw-mt-2 tw-pb-4', {
'tw-border-b tw-border-separator tw-mb-4': !hideTier,
})}>
<div> <div>
<span className="tw-mr-2">Owner:</span> <span className="tw-mr-2">Owner:</span>
<span className="tw-relative"> <span className="tw-relative">
@ -410,49 +473,12 @@ const ManageTab: FunctionComponent<ManageProps> = ({
onSelect={handleOwnerSelection} onSelect={handleOwnerSelection}
/> />
)} )}
{getOwnerUpdateLoader()}
</span> </span>
</div> </div>
{getJoinableWidget()} {getJoinableWidget()}
</div> </div>
{getTierCards()} {getTierCards()}
<div className="tw-mt-6 tw-text-right" data-testid="buttons">
<Button
size="regular"
theme="primary"
variant="text"
onClick={handleCancel}>
Discard
</Button>
{loading ? (
<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>
) : status === '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="tw-w-16 tw-h-10"
data-testid="saveManageTab"
size="regular"
theme="primary"
variant="contained"
onClick={handleSave}>
Save
</Button>
)}
</div>
{getDeleteEntityWidget()} {getDeleteEntityWidget()}
{getDeleteModal()} {getDeleteModal()}
</div> </div>

View File

@ -30,3 +30,5 @@ export interface ManageProps {
entityType?: string; entityType?: string;
allowDelete?: boolean; allowDelete?: boolean;
} }
export type Status = 'initial' | 'waiting' | 'success';

View File

@ -14,7 +14,6 @@
import { import {
findAllByTestId, findAllByTestId,
findByTestId, findByTestId,
findByText,
fireEvent, fireEvent,
render, render,
} from '@testing-library/react'; } from '@testing-library/react';
@ -92,42 +91,6 @@ describe('Test Manage tab Component', () => {
expect(card.length).toBe(3); expect(card.length).toBe(3);
}); });
it('there should be 2 buttons', async () => {
const { container } = render(
<ManageTab hasEditAccess onSave={mockFunction} />
);
const buttons = await findByTestId(container, 'buttons');
expect(buttons.childElementCount).toBe(2);
});
it('Onclick of save, onSave function also called', async () => {
const { container } = render(
<ManageTab hasEditAccess onSave={mockFunction} />
);
const card = await findAllByTestId(container, 'card');
fireEvent.click(
card[1],
new MouseEvent('click', {
bubbles: true,
cancelable: true,
})
);
const save = await findByText(container, /Save/i);
fireEvent.click(
save,
new MouseEvent('click', {
bubbles: true,
cancelable: true,
})
);
expect(mockFunction).toBeCalledTimes(1);
});
it('Should render switch if isJoinable is present', async () => { it('Should render switch if isJoinable is present', async () => {
const { container } = render( const { container } = render(
<ManageTab hasEditAccess isJoinable onSave={mockFunction} /> <ManageTab hasEditAccess isJoinable onSave={mockFunction} />

View File

@ -23,7 +23,7 @@ import AddRoleModal from './AddRoleModal';
const mockCancel = jest.fn(); const mockCancel = jest.fn();
const mockSave = jest.fn(); const mockSave = jest.fn();
const mockForm = jest.fn().mockReturnValue(<p data-testid="form">data</p>); const mockForm = jest.fn().mockReturnValue(<p data-testid="form">data</p>);
const mockInitionalData = { const mockInitialData = {
name: '', name: '',
description: '', description: '',
}; };
@ -34,7 +34,7 @@ describe('Test AddRoleModal component', () => {
<AddRoleModal <AddRoleModal
form={mockForm} form={mockForm}
header="Adding new users" header="Adding new users"
initialData={mockInitionalData} initialData={mockInitialData}
onCancel={mockCancel} onCancel={mockCancel}
onSave={mockSave} onSave={mockSave}
/> />
@ -55,7 +55,7 @@ describe('Test AddRoleModal component', () => {
<AddRoleModal <AddRoleModal
form={mockForm} form={mockForm}
header="Adding new users" header="Adding new users"
initialData={mockInitionalData} initialData={mockInitialData}
onCancel={mockCancel} onCancel={mockCancel}
onSave={mockSave} onSave={mockSave}
/> />
@ -71,7 +71,7 @@ describe('Test AddRoleModal component', () => {
<AddRoleModal <AddRoleModal
form={mockForm} form={mockForm}
header="Adding new users" header="Adding new users"
initialData={mockInitionalData} initialData={mockInitialData}
onCancel={mockCancel} onCancel={mockCancel}
onSave={mockSave} onSave={mockSave}
/> />

View File

@ -516,6 +516,7 @@ const PipelineDetails = ({
trigger="mouseenter"> trigger="mouseenter">
<SVGIcons <SVGIcons
alt="request-description" alt="request-description"
className="tw-mt-2.5"
icon={Icons.REQUEST} icon={Icons.REQUEST}
/> />
</PopOver> </PopOver>

View File

@ -11,6 +11,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Status } from '../../ManageTab/ManageTab.interface';
export type CardWithListItems = { export type CardWithListItems = {
id: string; id: string;
description: string; description: string;
@ -21,5 +23,8 @@ export type CardWithListItems = {
export type Props = { export type Props = {
card: CardWithListItems; card: CardWithListItems;
isActive: boolean; isActive: boolean;
isSelected: boolean;
tierStatus: Status;
onSave: (updatedTier: string) => void;
onSelect: (cardId: string) => void; onSelect: (cardId: string) => void;
}; };

View File

@ -14,11 +14,13 @@
export const cardStyle = { export const cardStyle = {
base: 'tw-flex tw-flex-col tw-rounded-md tw-border tw-mb-4', base: 'tw-flex tw-flex-col tw-rounded-md tw-border tw-mb-4',
default: 'tw-border-main', default: 'tw-border-main',
active: 'tw-border-primary', active: 'tw-border-primary-lite',
selected: 'tw-border-primary',
header: { header: {
base: 'tw-flex tw-px-5 tw-py-3 tw-cursor-pointer tw-justify-between tw-items-center', base: 'tw-flex tw-px-5 tw-py-3 tw-cursor-pointer tw-justify-between tw-items-center',
default: 'tw-bg-badge', default: 'tw-bg-badge',
active: 'tw-bg-primary tw-rounded-t-md tw-text-white', active: 'tw-bg-primary-lite tw-rounded-t-md',
selected: 'tw-bg-primary tw-rounded-t-md tw-text-white',
title: 'tw-text-base tw-mb-0', title: 'tw-text-base tw-mb-0',
description: 'tw-font-medium tw-pr-2', description: 'tw-font-medium tw-pr-2',
}, },
@ -26,6 +28,7 @@ export const cardStyle = {
base: 'tw-py-5 tw-px-10', base: 'tw-py-5 tw-px-10',
default: 'tw-hidden', default: 'tw-hidden',
active: 'tw-block', active: 'tw-block',
selected: 'tw-block',
content: { content: {
withBorder: 'tw-py-3 tw-border-b tw-border-main', withBorder: 'tw-py-3 tw-border-b tw-border-main',
withoutBorder: 'tw-py-1', withoutBorder: 'tw-py-1',

View File

@ -15,7 +15,8 @@ import { fireEvent, render } from '@testing-library/react';
import React from 'react'; import React from 'react';
import CardListItem from './CardWithListItems'; import CardListItem from './CardWithListItems';
const mockFunction = jest.fn(); const mockSelectFunction = jest.fn();
const mockSaveFuntion = jest.fn();
const mockCard = { const mockCard = {
id: 'test1', id: 'test1',
title: 'card', title: 'card',
@ -30,7 +31,14 @@ jest.mock('../../common/rich-text-editor/RichTextEditorPreviewer', () => {
describe('Test CardWithListing Component', () => { describe('Test CardWithListing Component', () => {
it('Component should render', () => { it('Component should render', () => {
const { getByTestId } = render( const { getByTestId } = render(
<CardListItem card={mockCard} isActive={false} onSelect={mockFunction} /> <CardListItem
card={mockCard}
isActive={false}
isSelected={false}
tierStatus="initial"
onSave={mockSaveFuntion}
onSelect={mockSelectFunction}
/>
); );
const card = getByTestId('card-list'); const card = getByTestId('card-list');
@ -40,9 +48,16 @@ describe('Test CardWithListing Component', () => {
expect(getByTestId('icon')).toBeEmptyDOMElement(); expect(getByTestId('icon')).toBeEmptyDOMElement();
}); });
it('OnClick callback function should call', () => { it('OnClick onSelect function should call', () => {
const { getByTestId } = render( const { getByTestId } = render(
<CardListItem card={mockCard} isActive={false} onSelect={mockFunction} /> <CardListItem
card={mockCard}
isActive={false}
isSelected={false}
tierStatus="initial"
onSave={mockSaveFuntion}
onSelect={mockSelectFunction}
/>
); );
const card = getByTestId('card-list'); const card = getByTestId('card-list');
@ -54,6 +69,66 @@ describe('Test CardWithListing Component', () => {
}) })
); );
expect(mockFunction).toHaveBeenCalledTimes(1); expect(mockSelectFunction).toHaveBeenCalledTimes(1);
});
it('OnClick onSelect function should call and Select tier button should be visible', () => {
const { getByTestId } = render(
<CardListItem
isActive
card={mockCard}
isSelected={false}
tierStatus="initial"
onSave={mockSaveFuntion}
onSelect={mockSelectFunction}
/>
);
const card = getByTestId('card-list');
fireEvent(
card,
new MouseEvent('click', {
bubbles: true,
cancelable: true,
})
);
expect(mockSelectFunction).toHaveBeenCalledTimes(1);
const tierSelectButton = getByTestId('select-tier-buuton');
expect(tierSelectButton).toBeInTheDocument();
});
it('onClick of select tier button onSave function should call.', () => {
const { getByTestId } = render(
<CardListItem
isActive
card={mockCard}
isSelected={false}
tierStatus="initial"
onSave={mockSaveFuntion}
onSelect={mockSelectFunction}
/>
);
const card = getByTestId('card-list');
fireEvent(
card,
new MouseEvent('click', {
bubbles: true,
cancelable: true,
})
);
expect(mockSelectFunction).toHaveBeenCalledTimes(1);
const tierSelectButton = getByTestId('select-tier-buuton');
expect(tierSelectButton).toBeInTheDocument();
fireEvent.click(tierSelectButton);
expect(mockSaveFuntion).toHaveBeenCalledTimes(1);
}); });
}); });

View File

@ -14,39 +14,90 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from 'react';
import { Button } from '../../buttons/Button/Button';
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
import Loader from '../../Loader/Loader';
import { Props } from './CardWithListItems.interface'; import { Props } from './CardWithListItems.interface';
import { cardStyle } from './CardWithListItems.style'; import { cardStyle } from './CardWithListItems.style';
const CardListItem: FunctionComponent<Props> = ({ const CardListItem: FunctionComponent<Props> = ({
card, card,
isActive, isActive,
isSelected,
onSelect, onSelect,
onSave,
tierStatus,
}: Props) => { }: Props) => {
const getCardBodyStyle = () => {
return isSelected
? cardStyle.selected
: isActive
? cardStyle.active
: cardStyle.default;
};
const getCardHeaderStyle = () => {
return isSelected
? cardStyle.header.selected
: isActive
? cardStyle.header.active
: cardStyle.header.default;
};
const getTierSelectButton = (tier: string) => {
return tierStatus === 'waiting' ? (
<Loader
className="tw-inline-block"
size="small"
style={{ marginBottom: '-4px' }}
type="default"
/>
) : tierStatus === 'success' ? (
<FontAwesomeIcon icon="check" />
) : (
<Button
data-testid="select-tier-buuton"
size="small"
theme="primary"
onClick={() => onSave(tier)}>
Select
</Button>
);
};
const getCardIcon = (cardId: string) => {
if (isSelected && isActive) {
return <FontAwesomeIcon className="tw-text-h4" icon="check-circle" />;
} else if (isSelected) {
return <FontAwesomeIcon className="tw-text-h4" icon="check-circle" />;
} else if (isActive) {
return getTierSelectButton(cardId);
} else {
return null;
}
};
return ( return (
<div <div
className={classNames( className={classNames(cardStyle.base, getCardBodyStyle())}
cardStyle.base,
isActive ? cardStyle.active : cardStyle.default
)}
data-testid="card-list" data-testid="card-list"
onClick={() => onSelect(card.id)}> onClick={() => onSelect(card.id)}>
<div <div className={classNames(cardStyle.header.base, getCardHeaderStyle())}>
className={classNames( <div className="tw-flex">
cardStyle.header.base, <div className="tw-self-start tw-mr-2">
isActive ? cardStyle.header.active : cardStyle.header.default <FontAwesomeIcon
)}> className="tw-text-xs"
icon={isActive ? 'chevron-down' : 'chevron-right'}
/>
</div>
<div className="tw-flex tw-flex-col"> <div className="tw-flex tw-flex-col">
<h4 className={cardStyle.header.title}>{card.title}</h4> <h4 className={cardStyle.header.title}>{card.title}</h4>
<p className={cardStyle.header.description}> <p className={cardStyle.header.description}>
{card.description.replace(/\*/g, '')} {card.description.replace(/\*/g, '')}
</p> </p>
</div> </div>
<div data-testid="icon">
{isActive && (
<FontAwesomeIcon className="tw-text-h2" icon="check-circle" />
)}
</div> </div>
<div data-testid="icon">{getCardIcon(card.id)}</div>
</div> </div>
<div <div
className={classNames( className={classNames(

View File

@ -132,7 +132,11 @@ const Description = ({
position="top" position="top"
title="Request description" title="Request description"
trigger="mouseenter"> trigger="mouseenter">
<SVGIcons alt="request-description" icon={Icons.REQUEST} /> <SVGIcons
alt="request-description"
className="tw-mt-2"
icon={Icons.REQUEST}
/>
</PopOver> </PopOver>
</button> </button>
) : null} ) : null}