Skip to content

Commit

Permalink
Task/t UI 438 modal for change owner in system permissions (#442)
Browse files Browse the repository at this point in the history
* begin change owner file

* create and generate modal

* create API call for changeOwner

* create hook.

* fix API ensure it runs.

* pull dev items
  • Loading branch information
carrythemountain authored Jan 10, 2025
1 parent 6886506 commit c91ba08
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 5 deletions.
3 changes: 1 addition & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/icicle-tapisui-extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions packages/tapisui-api/src/systems/changeOwner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Systems } from '@tapis/tapis-typescript';
import { apiGenerator, errorDecoder } from '../utils';

const changeOwner = (
params: Systems.ChangeSystemOwnerRequest,
basePath: string,
jwt: string
) => {
const api: Systems.SystemsApi = apiGenerator<Systems.SystemsApi>(
Systems,
Systems.SystemsApi,
basePath,
jwt
);
return errorDecoder<Systems.RespChangeCount>(() =>
api.changeSystemOwner(params)
);
};

export default changeOwner;
1 change: 1 addition & 0 deletions packages/tapisui-api/src/systems/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export { default as getGlobusAuthUrl } from './getGlobusAuthUrl';
export { default as generateGlobusTokens } from './generateGlobusTokens';
export { default as grantUserPerms } from './grantUserPerms';
export { default as shareSystem } from './shareSystem';
export { default as changeOwner } from './changeOwner';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.form {
width: 100%;
padding: 16px;
display: grid;
gap: 16px;
}

.perms {
display: flex;
gap: 8px;
flex-direction: row;
cursor: pointer;
padding: 16px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import React, { useState } from 'react';
import { Systems as SystemsHooks } from '@tapis/tapisui-hooks';
import { Systems } from '@tapis/tapis-typescript';
import { LoadingButton as Button } from '@mui/lab';
import styles from './ChangeOwnerModal.module.scss';
import {
FormControl,
InputLabel,
FormHelperText,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Alert,
AlertTitle,
Input,
Select,
MenuItem,
Chip,
} from '@mui/material';
import { Security } from '@mui/icons-material';

type ModalProps = {
open: boolean;
toggle: () => void;
system: Systems.TapisSystem;
};

const ChangeOwnerModal: React.FC<ModalProps> = ({ open, toggle, system }) => {
const { change, isLoading, isError, isSuccess, error, reset, invalidate } =
SystemsHooks.useChangeOwner();
const inputInitialState: { username?: string; perms: string[] } = {
username: undefined,
perms: [],
};
const [input, setInput] = useState(inputInitialState);

return (
<Dialog
open={open}
onClose={() => {
toggle();
}}
aria-labelledby="Change Owners for a system"
aria-describedby="A modal for managing owner change for a system"
maxWidth="sm"
fullWidth={true}
>
<DialogTitle id="alert-dialog-title">
<Security style={{ marginTop: '-5px' }} />
<span style={{ marginLeft: '8px' }}>Change Owners</span>
</DialogTitle>
<DialogContent>
{isSuccess && (
<Alert severity="success" style={{ marginTop: '8px' }}>
Owners Changed
</Alert>
)}
{isError && error && (
<Alert
severity="error"
style={{ marginTop: '8px' }}
onClose={() => {
reset();
}}
>
<AlertTitle>Error</AlertTitle>
{error && error.message}
</Alert>
)}
{system.sharedWithUsers!.length === 0 || system.isPublic ? (
<div className={styles['form']}>
<FormControl variant="standard">
<InputLabel htmlFor="username">Username</InputLabel>
<Input
id="username"
disabled={isSuccess}
onChange={(e) => {
setInput({
...input,
username: e.target.value,
});
}}
/>
<FormHelperText>
The username of the user for whom you want to give ownership
</FormHelperText>
</FormControl>
</div>
) : (
<div className={styles['form']}>
<FormControl
fullWidth
margin="dense"
style={{ marginBottom: '-16px' }}
>
<InputLabel size="small" id="mode">
Username
</InputLabel>
<Select
label="Task invocation mode"
labelId="mode"
size="small"
disabled={isSuccess}
onChange={(e) => {
setInput({
...input,
username: e.target.value as string,
});
}}
>
{system.sharedWithUsers!.map((username) => {
return <MenuItem value={username}>{username}</MenuItem>;
})}
</Select>
</FormControl>
<FormHelperText>
The username of the user for whom you want to give ownership
</FormHelperText>
</div>
)}
{input.username && (
<div className={styles['perms']}>
{['Select'].map((perm) => {
return (
<Chip
label={perm}
color={input.perms.includes(perm) ? 'primary' : 'default'}
onClick={() => {
// Add perm to perms if not already included
const additionalPerms = perm === 'MODIFY' ? ['READ'] : [];
if (!input.perms.includes(perm)) {
setInput({
...input,
perms: [...input.perms, perm, ...additionalPerms],
});
return;
}

// Remove perm because it is already selected
setInput({
...input,
perms: input.perms.filter((p) => p !== perm),
});
}}
/>
);
})}
</div>
)}
</DialogContent>
<DialogActions>
<Button
color={isSuccess ? 'primary' : 'error'}
onClick={() => {
reset();
setInput(inputInitialState);
toggle();
}}
>
{isSuccess ? 'Continue' : 'Cancel'}
</Button>
<Button
disabled={
isSuccess ||
input.username === undefined ||
input.perms.length === 0
}
onClick={() => {
change(
{
systemId: system.id!,
userName: input.username!,
},
{
onSuccess: () => {
invalidate();
setInput(inputInitialState);
},
}
);
}}
loading={isLoading}
variant="outlined"
autoFocus
>
Update
</Button>
</DialogActions>
</Dialog>
);
};

export default ChangeOwnerModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ChangeOwnerModal';
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { default as UnShareSystemPublicModal } from './UnShareSystemPublicModal'
export { default as CreateChildSystemModal } from './CreateChildSystemModal';
export { default as SharingModal } from './SharingModal';
export { default as PermissionsModal } from './PermissionsModal';
export { default as ChangeOwnerModal } from './ChangeOwnerModal';
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
UnShareSystemPublicModal,
SharingModal,
PermissionsModal,
ChangeOwnerModal,
} from '../Modals';

const AuthButton: React.FC<{
Expand Down Expand Up @@ -211,7 +212,13 @@ const SystemSettingsMenu: React.FC<{ system: Systems.TapisSystem }> = ({
<ListItemText>Make public</ListItemText>
</MenuItem>
)}
<MenuItem disabled={system.owner !== username}>
{/* <MenuItem disabled={system.owner !== username}> */}
<MenuItem
onClick={() => {
setAnchorEl(null);
setModal('changeowner');
}}
>
<ListItemIcon>
<Dns fontSize="small" color="error" />
</ListItemIcon>
Expand Down Expand Up @@ -289,6 +296,13 @@ const SystemSettingsMenu: React.FC<{ system: Systems.TapisSystem }> = ({
setModal(undefined);
}}
/>
<ChangeOwnerModal
system={system}
open={modal === 'changeowner'}
toggle={() => {
setModal(undefined);
}}
/>
</span>
);
};
Expand Down
1 change: 1 addition & 0 deletions packages/tapisui-hooks/src/systems/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export { default as useGetGlobusAuthUrl } from './useGetGlobusAuthUrl';
export { default as useGenerateGlobusTokens } from './useGenerateGlobusTokens';
export { default as useShareSystem } from './useShareSystem';
export { default as useGrantUserPerms } from './useGrantUserPerms';
export { default as useChangeOwner } from './useChangeOwner';
3 changes: 3 additions & 0 deletions packages/tapisui-hooks/src/systems/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { changeOwner } from '@tapis/tapisui-api/dist/systems';

const QueryKeys = {
list: 'systems/list',
details: 'systems/details',
Expand All @@ -14,6 +16,7 @@ const QueryKeys = {
generateGlobusTokens: 'systems/generateGlobusTokens',
shareSystem: 'systems/shareSystem',
grantUserPerms: 'systems/grantUserPerms',
changeOwner: 'systems/changeOwner',
};

export default QueryKeys;
48 changes: 48 additions & 0 deletions packages/tapisui-hooks/src/systems/useChangeOwner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useMutation, MutateOptions, useQueryClient } from 'react-query';
import { Systems } from '@tapis/tapis-typescript';
import { Systems as API } from '@tapis/tapisui-api';
import { useTapisConfig } from '../';
import QueryKeys from './queryKeys';

const useChangeOwner = () => {
const { basePath, accessToken } = useTapisConfig();
const jwt = accessToken?.access_token || '';
const queryClient = useQueryClient();

const { mutate, isLoading, isError, isSuccess, data, error, reset } =
useMutation<
Systems.RespChangeCount,
Error,
Systems.ChangeSystemOwnerRequest
>([QueryKeys.changeOwner, basePath, jwt], (params) =>
API.changeOwner(params, basePath, jwt)
);

const invalidate = () => {
queryClient.invalidateQueries([QueryKeys.details]);
};

// Return hook object with loading states and login function
return {
isLoading,
isError,
isSuccess,
data,
error,
reset,
invalidate,
change: (
params: Systems.ChangeSystemOwnerRequest,
// react-query options to allow callbacks such as onSuccess
options?: MutateOptions<
Systems.RespChangeCount,
Error,
Systems.ChangeSystemOwnerRequest
>
) => {
return mutate(params, options);
},
};
};

export default useChangeOwner;

0 comments on commit c91ba08

Please sign in to comment.