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,
|
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">
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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,16 +226,27 @@ 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>
|
||||||
<Button
|
<NonAdminAction
|
||||||
className="tw-px-2 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-shadow"
|
className="tw-self-center"
|
||||||
data-testid="delete-button"
|
html={
|
||||||
size="custom"
|
<Fragment>
|
||||||
theme="primary"
|
<p>{TITLE_FOR_NON_ADMIN_ACTION}</p>
|
||||||
type="button"
|
</Fragment>
|
||||||
variant="outlined"
|
}
|
||||||
onClick={handleOnEntityDelete}>
|
isOwner={isAdminUser}
|
||||||
Delete this {entityType}
|
position="left">
|
||||||
</Button>
|
<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"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleOnEntityDelete}>
|
||||||
|
Delete this {entityType}
|
||||||
|
</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(() => {
|
||||||
setTimeout(() => {
|
if (statusOwner === 'waiting') {
|
||||||
setLoading(false);
|
setStatusOwner('success');
|
||||||
}, 1000);
|
|
||||||
if (status === 'waiting') {
|
|
||||||
setStatus('success');
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setStatus('initial');
|
setInitialOwnerLoadingState();
|
||||||
}, 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>
|
||||||
|
|||||||
@ -30,3 +30,5 @@ export interface ManageProps {
|
|||||||
entityType?: string;
|
entityType?: string;
|
||||||
allowDelete?: boolean;
|
allowDelete?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Status = 'initial' | 'waiting' | 'success';
|
||||||
|
|||||||
@ -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} />
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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"
|
||||||
<div className="tw-flex tw-flex-col">
|
icon={isActive ? 'chevron-down' : 'chevron-right'}
|
||||||
<h4 className={cardStyle.header.title}>{card.title}</h4>
|
/>
|
||||||
<p className={cardStyle.header.description}>
|
</div>
|
||||||
{card.description.replace(/\*/g, '')}
|
<div className="tw-flex tw-flex-col">
|
||||||
</p>
|
<h4 className={cardStyle.header.title}>{card.title}</h4>
|
||||||
</div>
|
<p className={cardStyle.header.description}>
|
||||||
<div data-testid="icon">
|
{card.description.replace(/\*/g, '')}
|
||||||
{isActive && (
|
</p>
|
||||||
<FontAwesomeIcon className="tw-text-h2" icon="check-circle" />
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div data-testid="icon">{getCardIcon(card.id)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user