mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-08 15:04:29 +00:00
parent
a977aa90bc
commit
8942c2b6bc
@ -16,6 +16,8 @@ import {
|
||||
faCheck,
|
||||
faCheckCircle,
|
||||
faCheckSquare,
|
||||
faChevronDown,
|
||||
faChevronRight,
|
||||
faPlus,
|
||||
faSearch,
|
||||
faTimes,
|
||||
@ -28,7 +30,16 @@ import { AuthProvider } from './authentication/auth-provider/AuthProvider';
|
||||
import AppRouter from './router/AppRouter';
|
||||
|
||||
const App: FunctionComponent = () => {
|
||||
library.add(faTimes, faCheck, faSearch, faPlus, faCheckSquare, faCheckCircle);
|
||||
library.add(
|
||||
faTimes,
|
||||
faCheck,
|
||||
faSearch,
|
||||
faPlus,
|
||||
faCheckSquare,
|
||||
faCheckCircle,
|
||||
faChevronDown,
|
||||
faChevronRight
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="main-container">
|
||||
|
||||
@ -626,30 +626,6 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
onUpdate={onColumnsUpdate}
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
{activeTab === 2 && (
|
||||
@ -776,6 +752,29 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
{getLoader()}
|
||||
</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>
|
||||
</PageContainer>
|
||||
|
||||
@ -651,6 +651,7 @@ const EntityTable = ({
|
||||
trigger="mouseenter">
|
||||
<SVGIcons
|
||||
alt="request-description"
|
||||
className="tw-mt-2.5"
|
||||
icon={Icons.REQUEST}
|
||||
/>
|
||||
</PopOver>
|
||||
|
||||
@ -12,19 +12,21 @@
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import React, { CSSProperties, FunctionComponent } from 'react';
|
||||
import './Loader.css';
|
||||
|
||||
type Props = {
|
||||
size?: 'default' | 'small';
|
||||
type?: 'default' | 'success' | 'error' | 'white';
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
const Loader: FunctionComponent<Props> = ({
|
||||
size = 'default',
|
||||
type = 'default',
|
||||
className = '',
|
||||
style,
|
||||
}: Props): JSX.Element => {
|
||||
let classes = 'loader';
|
||||
switch (size) {
|
||||
@ -54,7 +56,11 @@ const Loader: FunctionComponent<Props> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(classes, className)} data-testid="loader" />
|
||||
<div
|
||||
className={classNames(classes, className)}
|
||||
data-testid="loader"
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'
|
||||
import { deleteEntity } from '../../axiosAPIs/miscAPI';
|
||||
import { getCategory } from '../../axiosAPIs/tagAPI';
|
||||
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 { Operation } from '../../generated/entity/policies/accessControl/rule';
|
||||
import { useAuth } from '../../hooks/authHooks';
|
||||
@ -38,7 +39,7 @@ import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||
import DropDownList from '../dropdown/DropDownList';
|
||||
import Loader from '../Loader/Loader';
|
||||
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
|
||||
import { ManageProps } from './ManageTab.interface';
|
||||
import { ManageProps, Status } from './ManageTab.interface';
|
||||
|
||||
const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
currentTier = '',
|
||||
@ -57,10 +58,8 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
const { userPermissions, isAdminUser } = useAuth();
|
||||
const { isAuthDisabled } = useAuthContext();
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [status, setStatus] = useState<'initial' | 'waiting' | 'success'>(
|
||||
'initial'
|
||||
);
|
||||
const [statusOwner, setStatusOwner] = useState<Status>('initial');
|
||||
const [statusTier, setStatusTier] = useState<Status>('initial');
|
||||
const [activeTier, setActiveTier] = useState(currentTier);
|
||||
const [listVisible, setListVisible] = useState(false);
|
||||
const [teamJoinable, setTeamJoinable] = useState(isJoinable);
|
||||
@ -80,6 +79,67 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
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 = (
|
||||
_e: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||
value?: string
|
||||
@ -88,6 +148,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
const newOwner = listOwners.find((item) => item.value === value);
|
||||
if (newOwner) {
|
||||
setOwner(newOwner.value);
|
||||
handleOwnerSave(newOwner.value, activeTier);
|
||||
}
|
||||
}
|
||||
setListVisible(false);
|
||||
@ -97,45 +158,6 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
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 = () => {
|
||||
setEntityDeleteState((prev) => ({ ...prev, state: true }));
|
||||
};
|
||||
@ -193,7 +215,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
|
||||
const getDeleteEntityWidget = () => {
|
||||
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" />
|
||||
<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">
|
||||
@ -204,9 +226,19 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
{`Once you delete this ${entityType}, it would be removed permanently`}
|
||||
</p>
|
||||
</div>
|
||||
<NonAdminAction
|
||||
className="tw-self-center"
|
||||
html={
|
||||
<Fragment>
|
||||
<p>{TITLE_FOR_NON_ADMIN_ACTION}</p>
|
||||
</Fragment>
|
||||
}
|
||||
isOwner={isAdminUser}
|
||||
position="left">
|
||||
<Button
|
||||
className="tw-px-2 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-shadow"
|
||||
data-testid="delete-button"
|
||||
disabled={!isAdminUser && !isAuthDisabled}
|
||||
size="custom"
|
||||
theme="primary"
|
||||
type="button"
|
||||
@ -214,6 +246,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
onClick={handleOnEntityDelete}>
|
||||
Delete this {entityType}
|
||||
</Button>
|
||||
</NonAdminAction>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
@ -240,6 +273,9 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
<CardListItem
|
||||
card={card}
|
||||
isActive={activeTier === card.id}
|
||||
isSelected={card.id === currentTier}
|
||||
tierStatus={statusTier}
|
||||
onSave={handleTierSave}
|
||||
onSelect={handleCardSelection}
|
||||
/>
|
||||
</NonAdminAction>
|
||||
@ -297,10 +333,8 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
);
|
||||
|
||||
setTierData(tierData);
|
||||
setIsLoadingTierData(false);
|
||||
} else {
|
||||
setTierData([]);
|
||||
setIsLoadingTierData(false);
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
@ -308,9 +342,29 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
err,
|
||||
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(() => {
|
||||
if (!hideTier) {
|
||||
getTierData();
|
||||
@ -326,16 +380,22 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
}, [currentUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusOwner === 'waiting') {
|
||||
setStatusOwner('success');
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
if (status === 'waiting') {
|
||||
setStatus('success');
|
||||
setTimeout(() => {
|
||||
setStatus('initial');
|
||||
setInitialOwnerLoadingState();
|
||||
}, 3000);
|
||||
}
|
||||
}, [currentTier, currentUser]);
|
||||
}, [currentUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (statusTier === 'waiting') {
|
||||
setStatusTier('success');
|
||||
setTimeout(() => {
|
||||
setInitialTierLoadingState();
|
||||
}, 3000);
|
||||
}
|
||||
}, [currentTier]);
|
||||
|
||||
useEffect(() => {
|
||||
setListOwners(getOwnerList());
|
||||
@ -350,7 +410,10 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
className="tw-max-w-3xl tw-mx-auto"
|
||||
data-testid="manage-tab"
|
||||
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>
|
||||
<span className="tw-mr-2">Owner:</span>
|
||||
<span className="tw-relative">
|
||||
@ -410,49 +473,12 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
||||
onSelect={handleOwnerSelection}
|
||||
/>
|
||||
)}
|
||||
{getOwnerUpdateLoader()}
|
||||
</span>
|
||||
</div>
|
||||
{getJoinableWidget()}
|
||||
</div>
|
||||
{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()}
|
||||
{getDeleteModal()}
|
||||
</div>
|
||||
|
||||
@ -30,3 +30,5 @@ export interface ManageProps {
|
||||
entityType?: string;
|
||||
allowDelete?: boolean;
|
||||
}
|
||||
|
||||
export type Status = 'initial' | 'waiting' | 'success';
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
import {
|
||||
findAllByTestId,
|
||||
findByTestId,
|
||||
findByText,
|
||||
fireEvent,
|
||||
render,
|
||||
} from '@testing-library/react';
|
||||
@ -92,42 +91,6 @@ describe('Test Manage tab Component', () => {
|
||||
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 () => {
|
||||
const { container } = render(
|
||||
<ManageTab hasEditAccess isJoinable onSave={mockFunction} />
|
||||
|
||||
@ -23,7 +23,7 @@ import AddRoleModal from './AddRoleModal';
|
||||
const mockCancel = jest.fn();
|
||||
const mockSave = jest.fn();
|
||||
const mockForm = jest.fn().mockReturnValue(<p data-testid="form">data</p>);
|
||||
const mockInitionalData = {
|
||||
const mockInitialData = {
|
||||
name: '',
|
||||
description: '',
|
||||
};
|
||||
@ -34,7 +34,7 @@ describe('Test AddRoleModal component', () => {
|
||||
<AddRoleModal
|
||||
form={mockForm}
|
||||
header="Adding new users"
|
||||
initialData={mockInitionalData}
|
||||
initialData={mockInitialData}
|
||||
onCancel={mockCancel}
|
||||
onSave={mockSave}
|
||||
/>
|
||||
@ -55,7 +55,7 @@ describe('Test AddRoleModal component', () => {
|
||||
<AddRoleModal
|
||||
form={mockForm}
|
||||
header="Adding new users"
|
||||
initialData={mockInitionalData}
|
||||
initialData={mockInitialData}
|
||||
onCancel={mockCancel}
|
||||
onSave={mockSave}
|
||||
/>
|
||||
@ -71,7 +71,7 @@ describe('Test AddRoleModal component', () => {
|
||||
<AddRoleModal
|
||||
form={mockForm}
|
||||
header="Adding new users"
|
||||
initialData={mockInitionalData}
|
||||
initialData={mockInitialData}
|
||||
onCancel={mockCancel}
|
||||
onSave={mockSave}
|
||||
/>
|
||||
@ -516,6 +516,7 @@ const PipelineDetails = ({
|
||||
trigger="mouseenter">
|
||||
<SVGIcons
|
||||
alt="request-description"
|
||||
className="tw-mt-2.5"
|
||||
icon={Icons.REQUEST}
|
||||
/>
|
||||
</PopOver>
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Status } from '../../ManageTab/ManageTab.interface';
|
||||
|
||||
export type CardWithListItems = {
|
||||
id: string;
|
||||
description: string;
|
||||
@ -21,5 +23,8 @@ export type CardWithListItems = {
|
||||
export type Props = {
|
||||
card: CardWithListItems;
|
||||
isActive: boolean;
|
||||
isSelected: boolean;
|
||||
tierStatus: Status;
|
||||
onSave: (updatedTier: string) => void;
|
||||
onSelect: (cardId: string) => void;
|
||||
};
|
||||
|
||||
@ -14,11 +14,13 @@
|
||||
export const cardStyle = {
|
||||
base: 'tw-flex tw-flex-col tw-rounded-md tw-border tw-mb-4',
|
||||
default: 'tw-border-main',
|
||||
active: 'tw-border-primary',
|
||||
active: 'tw-border-primary-lite',
|
||||
selected: 'tw-border-primary',
|
||||
header: {
|
||||
base: 'tw-flex tw-px-5 tw-py-3 tw-cursor-pointer tw-justify-between tw-items-center',
|
||||
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',
|
||||
description: 'tw-font-medium tw-pr-2',
|
||||
},
|
||||
@ -26,6 +28,7 @@ export const cardStyle = {
|
||||
base: 'tw-py-5 tw-px-10',
|
||||
default: 'tw-hidden',
|
||||
active: 'tw-block',
|
||||
selected: 'tw-block',
|
||||
content: {
|
||||
withBorder: 'tw-py-3 tw-border-b tw-border-main',
|
||||
withoutBorder: 'tw-py-1',
|
||||
|
||||
@ -15,7 +15,8 @@ import { fireEvent, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import CardListItem from './CardWithListItems';
|
||||
|
||||
const mockFunction = jest.fn();
|
||||
const mockSelectFunction = jest.fn();
|
||||
const mockSaveFuntion = jest.fn();
|
||||
const mockCard = {
|
||||
id: 'test1',
|
||||
title: 'card',
|
||||
@ -30,7 +31,14 @@ jest.mock('../../common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
describe('Test CardWithListing Component', () => {
|
||||
it('Component should 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');
|
||||
@ -40,9 +48,16 @@ describe('Test CardWithListing Component', () => {
|
||||
expect(getByTestId('icon')).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('OnClick callback function should call', () => {
|
||||
it('OnClick onSelect function should call', () => {
|
||||
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');
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -14,39 +14,90 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import Loader from '../../Loader/Loader';
|
||||
import { Props } from './CardWithListItems.interface';
|
||||
import { cardStyle } from './CardWithListItems.style';
|
||||
|
||||
const CardListItem: FunctionComponent<Props> = ({
|
||||
card,
|
||||
isActive,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onSave,
|
||||
tierStatus,
|
||||
}: 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 (
|
||||
<div
|
||||
className={classNames(
|
||||
cardStyle.base,
|
||||
isActive ? cardStyle.active : cardStyle.default
|
||||
)}
|
||||
className={classNames(cardStyle.base, getCardBodyStyle())}
|
||||
data-testid="card-list"
|
||||
onClick={() => onSelect(card.id)}>
|
||||
<div
|
||||
className={classNames(
|
||||
cardStyle.header.base,
|
||||
isActive ? cardStyle.header.active : cardStyle.header.default
|
||||
)}>
|
||||
<div className={classNames(cardStyle.header.base, getCardHeaderStyle())}>
|
||||
<div className="tw-flex">
|
||||
<div className="tw-self-start tw-mr-2">
|
||||
<FontAwesomeIcon
|
||||
className="tw-text-xs"
|
||||
icon={isActive ? 'chevron-down' : 'chevron-right'}
|
||||
/>
|
||||
</div>
|
||||
<div className="tw-flex tw-flex-col">
|
||||
<h4 className={cardStyle.header.title}>{card.title}</h4>
|
||||
<p className={cardStyle.header.description}>
|
||||
{card.description.replace(/\*/g, '')}
|
||||
</p>
|
||||
</div>
|
||||
<div data-testid="icon">
|
||||
{isActive && (
|
||||
<FontAwesomeIcon className="tw-text-h2" icon="check-circle" />
|
||||
)}
|
||||
</div>
|
||||
<div data-testid="icon">{getCardIcon(card.id)}</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
|
||||
@ -132,7 +132,11 @@ const Description = ({
|
||||
position="top"
|
||||
title="Request description"
|
||||
trigger="mouseenter">
|
||||
<SVGIcons alt="request-description" icon={Icons.REQUEST} />
|
||||
<SVGIcons
|
||||
alt="request-description"
|
||||
className="tw-mt-2"
|
||||
icon={Icons.REQUEST}
|
||||
/>
|
||||
</PopOver>
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user