mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-22 16:08:13 +00:00
Work on manage tab to improve UI (#4366)
This commit is contained in:
parent
1c4bea4613
commit
8c60b22180
@ -1,52 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants';
|
|
||||||
import { Button } from '../buttons/Button/Button';
|
|
||||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
|
||||||
|
|
||||||
type DeleteWidgetBodyProps = {
|
|
||||||
header: string;
|
|
||||||
description: string;
|
|
||||||
buttonText: string;
|
|
||||||
isOwner?: boolean;
|
|
||||||
hasPermission: boolean;
|
|
||||||
onClick: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DeleteWidgetBody = ({
|
|
||||||
header,
|
|
||||||
description,
|
|
||||||
buttonText,
|
|
||||||
isOwner,
|
|
||||||
hasPermission,
|
|
||||||
onClick,
|
|
||||||
}: DeleteWidgetBodyProps) => {
|
|
||||||
return (
|
|
||||||
<div className="tw-flex tw-justify-between tw-px-4 tw-py-2">
|
|
||||||
<div data-testid="danger-zone-text">
|
|
||||||
<h4 className="tw-text-base" data-testid="danger-zone-text-title">
|
|
||||||
{header}
|
|
||||||
</h4>
|
|
||||||
<p data-testid="danger-zone-text-para">{description}</p>
|
|
||||||
</div>
|
|
||||||
<NonAdminAction
|
|
||||||
className="tw-self-center"
|
|
||||||
html={<p>{TITLE_FOR_NON_ADMIN_ACTION}</p>}
|
|
||||||
isOwner={isOwner}
|
|
||||||
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={!hasPermission}
|
|
||||||
size="custom"
|
|
||||||
theme="primary"
|
|
||||||
type="button"
|
|
||||||
variant="outlined"
|
|
||||||
onClick={onClick}>
|
|
||||||
{buttonText}
|
|
||||||
</Button>
|
|
||||||
</NonAdminAction>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DeleteWidgetBody;
|
|
@ -11,36 +11,27 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { AxiosError, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isUndefined } from 'lodash';
|
import { isUndefined } from 'lodash';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { TableDetail } from 'Models';
|
import { TableDetail } from 'Models';
|
||||||
import React, { Fragment, FunctionComponent, useEffect, useState } from 'react';
|
import React, { Fragment, FunctionComponent, useEffect, useState } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import appState from '../../AppState';
|
import appState from '../../AppState';
|
||||||
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
|
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
|
||||||
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 { ENTITY_DELETE_STATE } from '../../constants/entity.constants';
|
|
||||||
import { EntityType } from '../../enums/entity.enum';
|
|
||||||
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';
|
||||||
import jsonData from '../../jsons/en';
|
import jsonData from '../../jsons/en';
|
||||||
import { getOwnerList } from '../../utils/ManageUtils';
|
import { getOwnerList } from '../../utils/ManageUtils';
|
||||||
import SVGIcons from '../../utils/SvgUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
|
||||||
import { Button } from '../buttons/Button/Button';
|
|
||||||
import CardListItem from '../card-list/CardListItem/CardWithListItems';
|
import CardListItem from '../card-list/CardListItem/CardWithListItems';
|
||||||
import { CardWithListItems } from '../card-list/CardListItem/CardWithListItems.interface';
|
import { CardWithListItems } from '../card-list/CardListItem/CardWithListItems.interface';
|
||||||
|
import DeleteWidget from '../common/DeleteWidget/DeleteWidget';
|
||||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||||
import ToggleSwitchV1 from '../common/toggle-switch/ToggleSwitchV1';
|
import OwnerWidget from '../common/OwnerWidget/OwnerWidget';
|
||||||
import DropDownList from '../dropdown/DropDownList';
|
|
||||||
import Loader from '../Loader/Loader';
|
import Loader from '../Loader/Loader';
|
||||||
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
|
|
||||||
import DeleteWidgetBody from './DeleteWidgetBody';
|
|
||||||
import { ManageProps, Status } from './ManageTab.interface';
|
import { ManageProps, Status } from './ManageTab.interface';
|
||||||
|
|
||||||
const ManageTab: FunctionComponent<ManageProps> = ({
|
const ManageTab: FunctionComponent<ManageProps> = ({
|
||||||
@ -58,9 +49,9 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
|||||||
allowSoftDelete,
|
allowSoftDelete,
|
||||||
isRecursiveDelete,
|
isRecursiveDelete,
|
||||||
deletEntityMessage,
|
deletEntityMessage,
|
||||||
|
manageSectionType,
|
||||||
handleIsJoinable,
|
handleIsJoinable,
|
||||||
}: ManageProps) => {
|
}: ManageProps) => {
|
||||||
const history = useHistory();
|
|
||||||
const { userPermissions, isAdminUser } = useAuth();
|
const { userPermissions, isAdminUser } = useAuth();
|
||||||
const { isAuthDisabled } = useAuthContext();
|
const { isAuthDisabled } = useAuthContext();
|
||||||
|
|
||||||
@ -74,17 +65,11 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
|||||||
const [listOwners, setListOwners] = useState(getOwnerList());
|
const [listOwners, setListOwners] = useState(getOwnerList());
|
||||||
const [owner, setOwner] = useState(currentUser);
|
const [owner, setOwner] = useState(currentUser);
|
||||||
const [isLoadingTierData, setIsLoadingTierData] = useState<boolean>(false);
|
const [isLoadingTierData, setIsLoadingTierData] = useState<boolean>(false);
|
||||||
const [entityDeleteState, setEntityDeleteState] =
|
|
||||||
useState<typeof ENTITY_DELETE_STATE>(ENTITY_DELETE_STATE);
|
|
||||||
|
|
||||||
const getOwnerById = (): string => {
|
const getOwnerById = (): string => {
|
||||||
return listOwners.find((item) => item.value === owner)?.name || '';
|
return listOwners.find((item) => item.value === owner)?.name || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOwnerGroup = () => {
|
|
||||||
return allowTeamOwner ? ['Teams', 'Users'] : ['Users'];
|
|
||||||
};
|
|
||||||
|
|
||||||
const setInitialOwnerLoadingState = () => {
|
const setInitialOwnerLoadingState = () => {
|
||||||
setStatusOwner('initial');
|
setStatusOwner('initial');
|
||||||
};
|
};
|
||||||
@ -164,114 +149,19 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
|||||||
setActiveTier(cardId);
|
setActiveTier(cardId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnEntityDelete = (softDelete = false) => {
|
|
||||||
setEntityDeleteState((prev) => ({ ...prev, state: true, softDelete }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnEntityDeleteCancel = () => {
|
|
||||||
setEntityDeleteState(ENTITY_DELETE_STATE);
|
|
||||||
};
|
|
||||||
|
|
||||||
const prepareEntityType = () => {
|
|
||||||
const services = [
|
|
||||||
EntityType.DASHBOARD_SERVICE,
|
|
||||||
EntityType.DATABASE_SERVICE,
|
|
||||||
EntityType.MESSAGING_SERVICE,
|
|
||||||
EntityType.PIPELINE_SERVICE,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (services.includes((entityType || '') as EntityType)) {
|
|
||||||
return `services/${entityType}s`;
|
|
||||||
} else {
|
|
||||||
return `${entityType}s`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const prepareDeleteMessage = () => {
|
|
||||||
return `Once you delete this ${entityType}, it will be removed permanently`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnEntityDeleteConfirm = () => {
|
|
||||||
setEntityDeleteState((prev) => ({ ...prev, loading: 'waiting' }));
|
|
||||||
deleteEntity(
|
|
||||||
prepareEntityType(),
|
|
||||||
entityId,
|
|
||||||
isRecursiveDelete,
|
|
||||||
allowSoftDelete
|
|
||||||
)
|
|
||||||
.then((res: AxiosResponse) => {
|
|
||||||
if (res.status === 200) {
|
|
||||||
setTimeout(() => {
|
|
||||||
handleOnEntityDeleteCancel();
|
|
||||||
showSuccessToast(
|
|
||||||
jsonData['api-success-messages']['delete-entity-success']
|
|
||||||
);
|
|
||||||
setTimeout(() => {
|
|
||||||
history.push('/');
|
|
||||||
}, 500);
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
showErrorToast(
|
|
||||||
jsonData['api-error-messages']['unexpected-server-response']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error: AxiosError) => {
|
|
||||||
showErrorToast(
|
|
||||||
error,
|
|
||||||
jsonData['api-error-messages']['delete-entity-error']
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
handleOnEntityDeleteCancel();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDeleteModal = () => {
|
|
||||||
if (allowDelete && entityDeleteState.state) {
|
|
||||||
return (
|
|
||||||
<EntityDeleteModal
|
|
||||||
bodyText={deletEntityMessage || prepareDeleteMessage()}
|
|
||||||
entityName={entityName as string}
|
|
||||||
entityType={entityType as string}
|
|
||||||
loadingState={entityDeleteState.loading}
|
|
||||||
softDelete={entityDeleteState.softDelete}
|
|
||||||
onCancel={handleOnEntityDeleteCancel}
|
|
||||||
onConfirm={handleOnEntityDeleteConfirm}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDeleteEntityWidget = () => {
|
const getDeleteEntityWidget = () => {
|
||||||
return allowDelete && entityId && entityName && entityType ? (
|
return allowDelete && entityId && entityName && entityType ? (
|
||||||
<div className="tw-mt-1" data-testid="danger-zone">
|
<div className="tw-mt-1" data-testid="danger-zone">
|
||||||
<hr className="tw-border-main tw-mb-4" />
|
<DeleteWidget
|
||||||
<div className="tw-border tw-border-error tw-rounded tw-mt-3 tw-shadow">
|
allowSoftDelete={allowSoftDelete}
|
||||||
{allowSoftDelete && (
|
deletEntityMessage={deletEntityMessage}
|
||||||
<div className="tw-border-b tw-border-error">
|
entityId={entityId}
|
||||||
<DeleteWidgetBody
|
entityName={entityName}
|
||||||
buttonText={`Soft delete this ${entityType}`}
|
entityType={entityType}
|
||||||
description={prepareDeleteMessage()}
|
hasPermission={isAdminUser || isAuthDisabled}
|
||||||
hasPermission={isAdminUser || isAuthDisabled}
|
isAdminUser={isAdminUser}
|
||||||
header={`Soft delete ${entityType} ${entityName}`}
|
isRecursiveDelete={isRecursiveDelete}
|
||||||
isOwner={isAdminUser}
|
/>
|
||||||
onClick={() => handleOnEntityDelete(true)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DeleteWidgetBody
|
|
||||||
buttonText={`Delete this ${entityType}`}
|
|
||||||
description={prepareDeleteMessage()}
|
|
||||||
hasPermission={isAdminUser || isAuthDisabled}
|
|
||||||
header={`Delete ${entityType} ${entityName}`}
|
|
||||||
isOwner={isAdminUser}
|
|
||||||
onClick={handleOnEntityDelete}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
@ -311,29 +201,13 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getJoinableWidget = () => {
|
const isJoinableActionAllowed = () => {
|
||||||
const isActionAllowed =
|
return (
|
||||||
isAdminUser ||
|
isAdminUser ||
|
||||||
isAuthDisabled ||
|
isAuthDisabled ||
|
||||||
userPermissions[Operation.UpdateTeam] ||
|
userPermissions[Operation.UpdateTeam] ||
|
||||||
!hasEditAccess;
|
!hasEditAccess
|
||||||
|
);
|
||||||
const joinableSwitch =
|
|
||||||
isActionAllowed && !isUndefined(teamJoinable) ? (
|
|
||||||
<div className="tw-flex">
|
|
||||||
<label htmlFor="join-team">Open to join</label>
|
|
||||||
<ToggleSwitchV1
|
|
||||||
checked={teamJoinable}
|
|
||||||
handleCheck={() => {
|
|
||||||
handleIsJoinable?.(!teamJoinable);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return !isUndefined(isJoinable) ? (
|
|
||||||
<div className="tw-mt-3 tw-mb-1">{joinableSwitch}</div>
|
|
||||||
) : null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ownerName = getOwnerById();
|
const ownerName = getOwnerById();
|
||||||
@ -372,23 +246,6 @@ const ManageTab: FunctionComponent<ManageProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
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();
|
||||||
@ -434,77 +291,33 @@ 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">
|
||||||
|
<p className="tw-text-base tw-font-medium">
|
||||||
|
Manage {manageSectionType ? manageSectionType : 'Section'}
|
||||||
|
</p>
|
||||||
<div
|
<div
|
||||||
className={classNames('tw-mt-2 tw-pb-4', {
|
className={classNames('tw-mt-2 tw-pb-4', {
|
||||||
'tw-border-b tw-border-separator tw-mb-4': !hideTier,
|
'tw-border-b tw-border-separator tw-mb-4': !hideTier,
|
||||||
})}>
|
})}>
|
||||||
<div>
|
<OwnerWidget
|
||||||
<span className="tw-mr-2">Owner:</span>
|
allowTeamOwner={allowTeamOwner}
|
||||||
<span className="tw-relative">
|
handleIsJoinable={handleIsJoinable}
|
||||||
<NonAdminAction
|
handleOwnerSelection={handleOwnerSelection}
|
||||||
html={
|
handleSelectOwnerDropdown={() =>
|
||||||
<Fragment>
|
setListVisible((visible) => !visible)
|
||||||
<p>You do not have permissions to update the owner.</p>
|
}
|
||||||
</Fragment>
|
hasEditAccess={hasEditAccess}
|
||||||
}
|
isAuthDisabled={isAuthDisabled}
|
||||||
isOwner={hasEditAccess}
|
isJoinableActionAllowed={isJoinableActionAllowed()}
|
||||||
permission={Operation.UpdateOwner}
|
listOwners={listOwners}
|
||||||
position="left">
|
listVisible={listVisible}
|
||||||
<Button
|
owner={owner}
|
||||||
className={classNames('tw-underline', {
|
ownerName={ownerName}
|
||||||
'tw-opacity-40':
|
statusOwner={statusOwner}
|
||||||
!userPermissions[Operation.UpdateOwner] &&
|
teamJoinable={teamJoinable}
|
||||||
!isAuthDisabled &&
|
/>
|
||||||
!hasEditAccess,
|
|
||||||
})}
|
|
||||||
data-testid="owner-dropdown"
|
|
||||||
disabled={
|
|
||||||
!userPermissions[Operation.UpdateOwner] &&
|
|
||||||
!isAuthDisabled &&
|
|
||||||
!hasEditAccess
|
|
||||||
}
|
|
||||||
size="custom"
|
|
||||||
theme="primary"
|
|
||||||
variant="link"
|
|
||||||
onClick={() => setListVisible((visible) => !visible)}>
|
|
||||||
{ownerName ? (
|
|
||||||
<span
|
|
||||||
className={classNames('tw-truncate', {
|
|
||||||
'tw-w-52': ownerName.length > 32,
|
|
||||||
})}
|
|
||||||
title={ownerName}>
|
|
||||||
{ownerName}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
'Select Owner'
|
|
||||||
)}
|
|
||||||
<SVGIcons
|
|
||||||
alt="edit"
|
|
||||||
className="tw-ml-1"
|
|
||||||
icon="icon-edit"
|
|
||||||
title="Edit"
|
|
||||||
width="12px"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</NonAdminAction>
|
|
||||||
{listVisible && (
|
|
||||||
<DropDownList
|
|
||||||
showSearchBar
|
|
||||||
dropDownList={listOwners}
|
|
||||||
groupType="tab"
|
|
||||||
listGroups={getOwnerGroup()}
|
|
||||||
value={owner}
|
|
||||||
onSelect={handleOwnerSelection}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{getOwnerUpdateLoader()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{getJoinableWidget()}
|
|
||||||
</div>
|
</div>
|
||||||
{getTierCards()}
|
{getTierCards()}
|
||||||
{getDeleteEntityWidget()}
|
{getDeleteEntityWidget()}
|
||||||
{getDeleteModal()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,7 @@ import { TableDetail } from 'Models';
|
|||||||
export interface ManageProps {
|
export interface ManageProps {
|
||||||
currentTier?: string;
|
currentTier?: string;
|
||||||
currentUser?: string;
|
currentUser?: string;
|
||||||
|
manageSectionType?: string;
|
||||||
hideTier?: boolean;
|
hideTier?: boolean;
|
||||||
isJoinable?: boolean;
|
isJoinable?: boolean;
|
||||||
allowSoftDelete?: boolean;
|
allowSoftDelete?: boolean;
|
||||||
|
@ -45,6 +45,10 @@ jest.mock('../common/toggle-switch/ToggleSwitchV1', () => {
|
|||||||
return jest.fn().mockImplementation(() => <p>ToggleSwitchV1.Component</p>);
|
return jest.fn().mockImplementation(() => <p>ToggleSwitchV1.Component</p>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('../common/DeleteWidget/DeleteWidget', () => {
|
||||||
|
return jest.fn().mockImplementation(() => <p>DeleteWidget.Component</p>);
|
||||||
|
});
|
||||||
|
|
||||||
const mockTierData = {
|
const mockTierData = {
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@ -121,7 +125,9 @@ describe('Test Manage tab Component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const dangerZone = await findByTestId(container, 'danger-zone');
|
const dangerZone = await findByTestId(container, 'danger-zone');
|
||||||
|
const DeleteWidget = await findByText(container, 'DeleteWidget.Component');
|
||||||
|
|
||||||
expect(dangerZone).toBeInTheDocument();
|
expect(dangerZone).toBeInTheDocument();
|
||||||
|
expect(DeleteWidget).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -85,7 +85,7 @@ const EntityDeleteModal: FC<Prop> = ({
|
|||||||
data-testid="confirmation-text-input"
|
data-testid="confirmation-text-input"
|
||||||
disabled={loadingState === 'waiting'}
|
disabled={loadingState === 'waiting'}
|
||||||
name="entityName"
|
name="entityName"
|
||||||
placeholder={`${entityType}/${entityName}`}
|
placeholder="DELETE"
|
||||||
type="text"
|
type="text"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
|
@ -493,7 +493,7 @@ const TeamDetails = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="tw-h-full tw-flex tw-flex-col tw-flex-grow">
|
||||||
{teams.length && currentTeam ? (
|
{teams.length && currentTeam ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div
|
<div
|
||||||
@ -568,11 +568,12 @@ const TeamDetails = ({
|
|||||||
<div className="tw-flex tw-flex-col tw-flex-grow">
|
<div className="tw-flex tw-flex-col tw-flex-grow">
|
||||||
<TabsPane
|
<TabsPane
|
||||||
activeTab={currentTab}
|
activeTab={currentTab}
|
||||||
|
className="tw-px-6"
|
||||||
setActiveTab={(tab) => setCurrentTab(tab)}
|
setActiveTab={(tab) => setCurrentTab(tab)}
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="tw-flex-grow tw-pt-4">
|
<div className="tw-flex-grow tw-flex tw-flex-col tw-pt-4">
|
||||||
{currentTab === 1 && getUserCards()}
|
{currentTab === 1 && getUserCards()}
|
||||||
|
|
||||||
{currentTab === 2 && getDatasetCards()}
|
{currentTab === 2 && getDatasetCards()}
|
||||||
@ -580,7 +581,7 @@ const TeamDetails = ({
|
|||||||
{currentTab === 3 && getDefaultRoles()}
|
{currentTab === 3 && getDefaultRoles()}
|
||||||
|
|
||||||
{currentTab === 4 && (
|
{currentTab === 4 && (
|
||||||
<div>
|
<div className="tw-bg-white tw-shadow-md tw-py-4 tw-flex-grow">
|
||||||
<ManageTab
|
<ManageTab
|
||||||
allowDelete
|
allowDelete
|
||||||
allowSoftDelete
|
allowSoftDelete
|
||||||
@ -593,6 +594,7 @@ const TeamDetails = ({
|
|||||||
entityType="team"
|
entityType="team"
|
||||||
handleIsJoinable={handleOpenToJoinToggle}
|
handleIsJoinable={handleOpenToJoinToggle}
|
||||||
isJoinable={currentTeam.isJoinable}
|
isJoinable={currentTeam.isJoinable}
|
||||||
|
manageSectionType="Team"
|
||||||
onSave={handleManageSave}
|
onSave={handleManageSave}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import React, { Fragment, useState } from 'react';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { deleteEntity } from '../../../axiosAPIs/miscAPI';
|
||||||
|
import { ENTITY_DELETE_STATE } from '../../../constants/entity.constants';
|
||||||
|
import { EntityType } from '../../../enums/entity.enum';
|
||||||
|
import jsonData from '../../../jsons/en';
|
||||||
|
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
|
||||||
|
import EntityDeleteModal from '../../Modals/EntityDeleteModal/EntityDeleteModal';
|
||||||
|
import DeleteWidgetBody from './DeleteWidgetBody';
|
||||||
|
|
||||||
|
interface DeleteSectionProps {
|
||||||
|
allowSoftDelete?: boolean;
|
||||||
|
entityName: string;
|
||||||
|
entityType: string;
|
||||||
|
deletEntityMessage?: string;
|
||||||
|
hasPermission: boolean;
|
||||||
|
isAdminUser?: boolean;
|
||||||
|
entityId: string;
|
||||||
|
isRecursiveDelete?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteWidget = ({
|
||||||
|
allowSoftDelete,
|
||||||
|
entityName,
|
||||||
|
entityType,
|
||||||
|
hasPermission,
|
||||||
|
isAdminUser,
|
||||||
|
deletEntityMessage,
|
||||||
|
entityId,
|
||||||
|
isRecursiveDelete,
|
||||||
|
}: DeleteSectionProps) => {
|
||||||
|
const history = useHistory();
|
||||||
|
const [entityDeleteState, setEntityDeleteState] =
|
||||||
|
useState<typeof ENTITY_DELETE_STATE>(ENTITY_DELETE_STATE);
|
||||||
|
|
||||||
|
const prepareDeleteMessage = (softDelete = false) => {
|
||||||
|
const softDeleteText = `Soft deleting will deactivate the ${entityName}. This will disable any discovery, read or write operations on ${entityName}`;
|
||||||
|
const hardDeleteText = `Once you delete this ${entityType}, it will be removed permanently`;
|
||||||
|
|
||||||
|
return softDelete ? softDeleteText : hardDeleteText;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnEntityDelete = (softDelete = false) => {
|
||||||
|
setEntityDeleteState((prev) => ({ ...prev, state: true, softDelete }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnEntityDeleteCancel = () => {
|
||||||
|
setEntityDeleteState(ENTITY_DELETE_STATE);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prepareEntityType = () => {
|
||||||
|
const services = [
|
||||||
|
EntityType.DASHBOARD_SERVICE,
|
||||||
|
EntityType.DATABASE_SERVICE,
|
||||||
|
EntityType.MESSAGING_SERVICE,
|
||||||
|
EntityType.PIPELINE_SERVICE,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (services.includes((entityType || '') as EntityType)) {
|
||||||
|
return `services/${entityType}s`;
|
||||||
|
} else {
|
||||||
|
return `${entityType}s`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnEntityDeleteConfirm = () => {
|
||||||
|
setEntityDeleteState((prev) => ({ ...prev, loading: 'waiting' }));
|
||||||
|
deleteEntity(
|
||||||
|
prepareEntityType(),
|
||||||
|
entityId,
|
||||||
|
isRecursiveDelete,
|
||||||
|
entityDeleteState.softDelete
|
||||||
|
)
|
||||||
|
.then((res: AxiosResponse) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
setTimeout(() => {
|
||||||
|
handleOnEntityDeleteCancel();
|
||||||
|
showSuccessToast(
|
||||||
|
jsonData['api-success-messages']['delete-entity-success']
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
history.push('/');
|
||||||
|
}, 500);
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
showErrorToast(
|
||||||
|
jsonData['api-error-messages']['unexpected-server-response']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: AxiosError) => {
|
||||||
|
showErrorToast(
|
||||||
|
error,
|
||||||
|
jsonData['api-error-messages']['delete-entity-error']
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
handleOnEntityDeleteCancel();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDeleteModal = () => {
|
||||||
|
if (entityDeleteState.state) {
|
||||||
|
return (
|
||||||
|
<EntityDeleteModal
|
||||||
|
bodyText={
|
||||||
|
deletEntityMessage ||
|
||||||
|
prepareDeleteMessage(entityDeleteState.softDelete)
|
||||||
|
}
|
||||||
|
entityName={entityName}
|
||||||
|
entityType={entityType}
|
||||||
|
loadingState={entityDeleteState.loading}
|
||||||
|
softDelete={entityDeleteState.softDelete}
|
||||||
|
onCancel={handleOnEntityDeleteCancel}
|
||||||
|
onConfirm={handleOnEntityDeleteConfirm}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<p className="tw-text-base tw-font-medium">Delete section</p>
|
||||||
|
<div className="tw-mt-1 tw-bg-white" data-testid="danger-zone">
|
||||||
|
<div className="tw-border tw-border-error tw-rounded tw-mt-3 tw-shadow">
|
||||||
|
{allowSoftDelete && (
|
||||||
|
<div className="tw-border-b">
|
||||||
|
<DeleteWidgetBody
|
||||||
|
buttonText="Soft delete"
|
||||||
|
description={prepareDeleteMessage(true)}
|
||||||
|
hasPermission={hasPermission}
|
||||||
|
header={`Soft delete ${entityType} ${entityName}`}
|
||||||
|
isOwner={isAdminUser}
|
||||||
|
onClick={() => handleOnEntityDelete(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DeleteWidgetBody
|
||||||
|
buttonText="Delete"
|
||||||
|
description={prepareDeleteMessage()}
|
||||||
|
hasPermission={hasPermission}
|
||||||
|
header={`Delete ${entityType} ${entityName}`}
|
||||||
|
isOwner={isAdminUser}
|
||||||
|
onClick={() => handleOnEntityDelete(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{getDeleteModal()}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteWidget;
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { TITLE_FOR_NON_ADMIN_ACTION } from '../../../constants/constants';
|
||||||
|
import NonAdminAction from '../non-admin-action/NonAdminAction';
|
||||||
|
|
||||||
|
type DeleteWidgetBodyProps = {
|
||||||
|
header: string;
|
||||||
|
description: string;
|
||||||
|
buttonText: string;
|
||||||
|
isOwner?: boolean;
|
||||||
|
hasPermission: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeleteWidgetBody = ({
|
||||||
|
header,
|
||||||
|
description,
|
||||||
|
buttonText,
|
||||||
|
isOwner,
|
||||||
|
hasPermission,
|
||||||
|
onClick,
|
||||||
|
}: DeleteWidgetBodyProps) => {
|
||||||
|
return (
|
||||||
|
<div className="tw-flex tw-justify-between tw-px-5 tw-py-3">
|
||||||
|
<div className="tw-w-10/12" data-testid="danger-zone-text">
|
||||||
|
<p
|
||||||
|
className="tw-text-sm tw-mb-1 tw-font-medium"
|
||||||
|
data-testid="danger-zone-text-title">
|
||||||
|
{header}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="tw-text-grey-muted tw-text-xs"
|
||||||
|
data-testid="danger-zone-text-para">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<NonAdminAction
|
||||||
|
className="tw-self-center"
|
||||||
|
html={<p>{TITLE_FOR_NON_ADMIN_ACTION}</p>}
|
||||||
|
isOwner={isOwner}
|
||||||
|
position="left">
|
||||||
|
<button
|
||||||
|
className="tw-px-3 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-font-medium tw-delete-outline-button "
|
||||||
|
data-testid="delete-button"
|
||||||
|
disabled={!hasPermission}
|
||||||
|
onClick={onClick}>
|
||||||
|
{buttonText}
|
||||||
|
</button>
|
||||||
|
</NonAdminAction>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteWidgetBody;
|
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { isUndefined } from 'lodash';
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { Operation } from '../../../generated/entity/policies/policy';
|
||||||
|
import { useAuth } from '../../../hooks/authHooks';
|
||||||
|
import { Button } from '../../buttons/Button/Button';
|
||||||
|
import DropDownList from '../../dropdown/DropDownList';
|
||||||
|
import Loader from '../../Loader/Loader';
|
||||||
|
import { Status } from '../../ManageTab/ManageTab.interface';
|
||||||
|
import NonAdminAction from '../non-admin-action/NonAdminAction';
|
||||||
|
import ToggleSwitchV1 from '../toggle-switch/ToggleSwitchV1';
|
||||||
|
|
||||||
|
interface OwnerWidgetProps {
|
||||||
|
isJoinableActionAllowed: boolean;
|
||||||
|
hasEditAccess: boolean;
|
||||||
|
isAuthDisabled: boolean;
|
||||||
|
listVisible: boolean;
|
||||||
|
teamJoinable?: boolean;
|
||||||
|
allowTeamOwner?: boolean;
|
||||||
|
ownerName: string;
|
||||||
|
statusOwner: Status;
|
||||||
|
owner: string;
|
||||||
|
listOwners: {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
group: string;
|
||||||
|
type: string;
|
||||||
|
}[];
|
||||||
|
handleIsJoinable?: (bool: boolean) => void;
|
||||||
|
handleSelectOwnerDropdown: () => void;
|
||||||
|
handleOwnerSelection: (
|
||||||
|
_e: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||||
|
value?: string | undefined
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OwnerWidget = ({
|
||||||
|
isJoinableActionAllowed,
|
||||||
|
teamJoinable,
|
||||||
|
isAuthDisabled,
|
||||||
|
hasEditAccess,
|
||||||
|
ownerName,
|
||||||
|
listVisible,
|
||||||
|
owner,
|
||||||
|
allowTeamOwner,
|
||||||
|
statusOwner,
|
||||||
|
listOwners,
|
||||||
|
handleIsJoinable,
|
||||||
|
handleSelectOwnerDropdown,
|
||||||
|
handleOwnerSelection,
|
||||||
|
}: OwnerWidgetProps) => {
|
||||||
|
const { userPermissions } = useAuth();
|
||||||
|
|
||||||
|
const getOwnerGroup = () => {
|
||||||
|
return allowTeamOwner ? ['Teams', 'Users'] : ['Users'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOwnerUpdateLoader = () => {
|
||||||
|
switch (statusOwner) {
|
||||||
|
case 'waiting':
|
||||||
|
return (
|
||||||
|
<Loader
|
||||||
|
className="tw-inline-block tw-ml-2"
|
||||||
|
size="small"
|
||||||
|
style={{ marginBottom: '-4px' }}
|
||||||
|
type="default"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'success':
|
||||||
|
return <FontAwesomeIcon className="tw-ml-2" icon="check" />;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div className="tw-mt-1 tw-bg-white">
|
||||||
|
<div className="tw-border tw-border-main tw-rounded tw-mt-3 tw-shadow">
|
||||||
|
<div className="tw-flex tw-justify-between tw-items-center tw-px-5 tw-py-3">
|
||||||
|
<div className="tw-w-10/12">
|
||||||
|
<p className="tw-text-sm tw-mb-1 tw-font-medium">Owner</p>
|
||||||
|
<p className="tw-text-grey-muted tw-text-xs">
|
||||||
|
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||||
|
Necessitatibus, sint.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="tw-relative">
|
||||||
|
<NonAdminAction
|
||||||
|
html={
|
||||||
|
<Fragment>
|
||||||
|
<p>You do not have permissions to update the owner.</p>
|
||||||
|
</Fragment>
|
||||||
|
}
|
||||||
|
isOwner={hasEditAccess}
|
||||||
|
permission={Operation.UpdateOwner}
|
||||||
|
position="left">
|
||||||
|
<Button
|
||||||
|
className={classNames('tw-underline', {
|
||||||
|
'tw-opacity-40':
|
||||||
|
!userPermissions[Operation.UpdateOwner] &&
|
||||||
|
!isAuthDisabled &&
|
||||||
|
!hasEditAccess,
|
||||||
|
})}
|
||||||
|
data-testid="owner-dropdown"
|
||||||
|
disabled={
|
||||||
|
!userPermissions[Operation.UpdateOwner] &&
|
||||||
|
!isAuthDisabled &&
|
||||||
|
!hasEditAccess
|
||||||
|
}
|
||||||
|
size="custom"
|
||||||
|
theme="primary"
|
||||||
|
variant="link"
|
||||||
|
onClick={handleSelectOwnerDropdown}>
|
||||||
|
{ownerName ? (
|
||||||
|
<span
|
||||||
|
className={classNames('tw-truncate', {
|
||||||
|
'tw-w-52': ownerName.length > 32,
|
||||||
|
})}
|
||||||
|
title={ownerName}>
|
||||||
|
{ownerName}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
'Add Owner'
|
||||||
|
)}
|
||||||
|
{getOwnerUpdateLoader()}
|
||||||
|
</Button>
|
||||||
|
</NonAdminAction>
|
||||||
|
{listVisible && (
|
||||||
|
<DropDownList
|
||||||
|
horzPosRight
|
||||||
|
showSearchBar
|
||||||
|
dropDownList={listOwners}
|
||||||
|
groupType="tab"
|
||||||
|
listGroups={getOwnerGroup()}
|
||||||
|
value={owner}
|
||||||
|
onSelect={handleOwnerSelection}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{isJoinableActionAllowed && !isUndefined(teamJoinable) && (
|
||||||
|
<div className="tw-flex tw-justify-between tw-px-5 tw-py-3 tw-border-t">
|
||||||
|
<div className="tw-w-10/12">
|
||||||
|
<p className="tw-text-sm tw-mb-1 tw-font-medium">
|
||||||
|
Open to join
|
||||||
|
</p>
|
||||||
|
<p className="tw-text-grey-muted tw-text-xs">
|
||||||
|
Lorem ipsum dolor, sit amet consectetur adipisicing elit.
|
||||||
|
Necessitatibus, sint.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="tw-flex tw-items-center">
|
||||||
|
<ToggleSwitchV1
|
||||||
|
checked={teamJoinable}
|
||||||
|
handleCheck={() => {
|
||||||
|
handleIsJoinable?.(!teamJoinable);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OwnerWidget;
|
@ -457,4 +457,11 @@
|
|||||||
.Toastify__toast-body {
|
.Toastify__toast-body {
|
||||||
@apply tw-items-start;
|
@apply tw-items-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* delete button style */
|
||||||
|
|
||||||
|
.tw-delete-outline-button {
|
||||||
|
@apply tw-border-gray-300 tw-border tw-text-error focus:tw-outline-none focus:tw-bg-transparent hover:tw-border-error focus:tw-border-error
|
||||||
|
hover:tw-text-error focus:tw-text-error focus:tw-ring focus:tw-ring-error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user