Skip to content

Commit 342a0af

Browse files
committed
fix: disable run, rerun and delete/terminate buttons when the user isn't a project editor
1 parent f24cfd2 commit 342a0af

File tree

10 files changed

+64
-19
lines changed

10 files changed

+64
-19
lines changed

components/executionsCards/ApplicationCard/ApplicationCard.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ApplicationSummary } from "@squonk/data-manager-client";
33
import { CircularProgress, useTheme } from "@mui/material";
44
import dynamic from "next/dynamic";
55

6+
import { useIsEditorOfCurrentProject } from "../../../hooks/projectHooks";
67
import { BaseCard } from "../../BaseCard";
78
import type { InstancesListProps } from "../InstancesList";
89
import type { ApplicationModalButtonProps } from "./ApplicationModalButton";
@@ -31,11 +32,14 @@ export interface ApplicationCardProps extends Pick<ApplicationModalButtonProps,
3132
export const ApplicationCard = ({ app, projectId }: ApplicationCardProps) => {
3233
const theme = useTheme();
3334

35+
const isEditor = useIsEditorOfCurrentProject();
36+
3437
return (
3538
<BaseCard
3639
actions={({ setExpanded }) => (
3740
<ApplicationModalButton
3841
applicationId={app.application_id}
42+
disabled={!isEditor}
3943
projectId={projectId}
4044
onLaunch={() => setExpanded(true)}
4145
/>

components/executionsCards/ApplicationCard/ApplicationModalButton.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ const ApplicationModal = dynamic<ApplicationModalProps>(
1212
},
1313
);
1414

15-
export type ApplicationModalButtonProps = Pick<
16-
ApplicationModalProps,
17-
"onLaunch" | "applicationId" | "projectId"
18-
>;
15+
export interface ApplicationModalButtonProps
16+
extends Pick<ApplicationModalProps, "onLaunch" | "applicationId" | "projectId"> {
17+
disabled?: boolean;
18+
}
1919

2020
/**
2121
* Button controlling a modal that allows the user to create an new instance of an application
@@ -24,6 +24,7 @@ export const ApplicationModalButton = ({
2424
applicationId,
2525
projectId,
2626
onLaunch,
27+
disabled,
2728
}: ApplicationModalButtonProps) => {
2829
const [open, setOpen] = useState(false);
2930
const [hasOpened, setHasOpened] = useState(false);
@@ -34,7 +35,7 @@ export const ApplicationModalButton = ({
3435
<span>
3536
<Button
3637
color="primary"
37-
disabled={!projectId}
38+
disabled={!projectId || disabled}
3839
onClick={() => {
3940
setOpen(true);
4041
setHasOpened(true);

components/executionsCards/JobCard/JobCard.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,24 @@ export interface ApplicationCardProps extends Pick<RunJobButtonProps, "projectId
1919
* the job to be instantiated
2020
*/
2121
job: JobSummary;
22+
/**
23+
* Whether to disable the button
24+
*/
25+
disabled?: boolean;
2226
}
2327

2428
/**
2529
* MuiCard that displays a summary of a job with actions to create new instances and view
2630
* existing instances.
2731
*/
28-
export const JobCard = ({ projectId, job }: ApplicationCardProps) => {
32+
export const JobCard = ({ projectId, job, disabled = false }: ApplicationCardProps) => {
2933
const theme = useTheme();
3034

3135
return (
3236
<BaseCard
3337
actions={({ setExpanded }) => (
3438
<RunJobButton
35-
disabled={job.disabled}
39+
disabled={job.disabled || disabled}
3640
jobId={job.id}
3741
projectId={projectId}
3842
onLaunch={() => setExpanded(true)}

components/instances/ResultApplicationCard.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { InstanceGetResponse, InstanceSummary } from "@squonk/data-manager-
22

33
import { CardContent, ListItem, ListItemText } from "@mui/material";
44

5-
import { useProjectFromId } from "../../hooks/projectHooks";
5+
import { useIsEditorOfCurrentProject, useProjectFromId } from "../../hooks/projectHooks";
66
import { HrefButton } from "../HrefButton";
77
import { ProjectListItem } from "../projects/ProjectListItem";
88
import { ResultCard } from "../results/ResultCard";
@@ -36,11 +36,14 @@ export const ResultApplicationCard = ({
3636

3737
const associatedProject = useProjectFromId(instance.project_id);
3838

39+
const isEditor = useIsEditorOfCurrentProject();
40+
3941
return (
4042
<ResultCard
4143
actions={({ setSlideIn }) => (
4244
<>
4345
<TerminateInstance
46+
disabled={!isEditor}
4447
instanceId={instanceId}
4548
phase={instance.phase}
4649
projectId={instance.project_id}

components/instances/ResultJobCard.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Alert, CardContent, ListItem, ListItemText } from "@mui/material";
55
import { LogsButton } from "../../components/results/LogsButton";
66
import { RerunJobButton } from "../../components/results/RerunJobButton";
77
import { ResultCard } from "../../components/results/ResultCard";
8-
import { useProjectFromId } from "../../hooks/projectHooks";
8+
import { useIsEditorOfCurrentProject, useProjectFromId } from "../../hooks/projectHooks";
99
import { ProjectListItem } from "../projects/ProjectListItem";
1010
import { ArchivedStatus } from "./ArchivedStatus";
1111
import { ArchiveInstance } from "./ArchiveInstance";
@@ -40,6 +40,8 @@ export const ResultJobCard = ({
4040

4141
const associatedProject = useProjectFromId(instance.project_id);
4242

43+
const isEditor = useIsEditorOfCurrentProject();
44+
4345
if (instance.job_id === undefined) {
4446
return <Alert severity="error">Instance is missing a job ID</Alert>;
4547
}
@@ -49,12 +51,13 @@ export const ResultJobCard = ({
4951
actions={({ setSlideIn }) => (
5052
<>
5153
<TerminateInstance
54+
disabled={!isEditor}
5255
instanceId={instanceId}
5356
phase={instance.phase}
5457
projectId={instance.project_id}
5558
onTermination={() => setSlideIn(false)}
5659
/>
57-
<RerunJobButton instance={instance} />
60+
<RerunJobButton disabled={!isEditor} instance={instance} />
5861
<LogsButton instance={instance} instanceId={instanceId} />
5962
<ArchiveInstance archived={instance.archived} instanceId={instanceId} />
6063
</>

components/instances/TerminateInstance.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface TerminateInstanceProps {
1818
instanceId: InstanceSummary["id"];
1919
phase: InstanceSummary["phase"] | InstanceGetResponse["phase"];
2020
projectId: InstanceSummary["project_id"] | InstanceGetResponse["project_id"];
21+
disabled?: boolean;
2122
/**
2223
* Called when the delete request is successfully made
2324
*/
@@ -29,6 +30,7 @@ export const TerminateInstance = ({
2930
phase,
3031
projectId,
3132
onTermination,
33+
disabled = false,
3234
}: TerminateInstanceProps) => {
3335
const queryClient = useQueryClient();
3436
const { mutateAsync: terminateInstance } = useTerminateInstance();
@@ -62,7 +64,7 @@ export const TerminateInstance = ({
6264
}}
6365
>
6466
{({ openModal }) => (
65-
<Button onClick={openModal}>
67+
<Button disabled={disabled} onClick={openModal}>
6668
{/* Instances in an end state are deleted but others are still running so are terminated.
6769
It's all the same to the API though. */}
6870
{verb}

components/results/RerunJobButton.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,22 @@ export interface RerunJobButtonProps {
1111
* Instance of the job that will be used to provide default options to rerun the job
1212
*/
1313
instance: InstanceSummary | InstanceGetResponse;
14+
/**
15+
* Whether the button is disabled
16+
*/
17+
disabled: boolean;
1418
}
1519

1620
/**
1721
* Wrapper around the *execution card* job run modal that reloads defaults from an existing instance
1822
*/
19-
export const RerunJobButton = ({ instance }: RerunJobButtonProps) => {
23+
export const RerunJobButton = ({ instance, disabled = false }: RerunJobButtonProps) => {
2024
const [open, setOpen] = useState(false);
2125

2226
// If the job id is undefined, it's probably an application which we don't currently let be rerun.
2327
return instance.job_id !== undefined ? (
2428
<>
25-
<Button color="primary" onClick={() => setOpen(true)}>
29+
<Button color="primary" disabled={disabled} onClick={() => setOpen(true)}>
2630
Run again
2731
</Button>
2832
<JobModal

components/tasks/ResultTaskCard.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Button, CardContent } from "@mui/material";
55
import { useQueryClient } from "@tanstack/react-query";
66
import { useRouter } from "next/router";
77

8-
import { useCurrentProjectId } from "../../hooks/projectHooks";
8+
import { useCurrentProjectId, useIsEditorOfCurrentProject } from "../../hooks/projectHooks";
99
import { useEnqueueError } from "../../hooks/useEnqueueStackError";
1010
import { ResultCard } from "../results/ResultCard";
1111
import { WarningDeleteButton } from "../WarningDeleteButton";
@@ -32,6 +32,8 @@ export const ResultTaskCard = ({ task, collapsedByDefault = true }: ResultTaskCa
3232

3333
const { projectId } = useCurrentProjectId();
3434

35+
const isEditor = useIsEditorOfCurrentProject();
36+
3537
const { query } = useRouter();
3638

3739
return (
@@ -55,7 +57,11 @@ export const ResultTaskCard = ({ task, collapsedByDefault = true }: ResultTaskCa
5557
}
5658
}}
5759
>
58-
{({ openModal }) => <Button onClick={openModal}>Delete</Button>}
60+
{({ openModal }) => (
61+
<Button disabled={!isEditor} onClick={openModal}>
62+
Delete
63+
</Button>
64+
)}
5965
</WarningDeleteButton>
6066
)}
6167
collapsed={

hooks/projectHooks.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,12 @@ export const useIsUserAProjectOwnerOrEditor = () => {
8888
(project?.editors.includes(user.username) || project?.owner === user.username)
8989
);
9090
};
91+
92+
export const useIsEditorOfCurrentProject = () => {
93+
const currentProject = useCurrentProject();
94+
95+
const { user } = useKeycloakUser();
96+
const isEditor = !!user.username && currentProject?.editors.includes(user.username);
97+
98+
return isEditor;
99+
};

pages/executions.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { CenterLoader } from "../components/CenterLoader";
2222
import { ApplicationCard } from "../components/executionsCards/ApplicationCard";
2323
import { JobCard } from "../components/executionsCards/JobCard";
2424
import { SearchTextField } from "../components/SearchTextField";
25-
import { useCurrentProject } from "../hooks/projectHooks";
25+
import { useCurrentProject, useIsEditorOfCurrentProject } from "../hooks/projectHooks";
2626
import Layout from "../layouts/Layout";
2727
import { dmOptions } from "../utils/api/ssrQueryOptions";
2828
import { search } from "../utils/app/searches";
@@ -75,6 +75,7 @@ const Executions = () => {
7575

7676
const currentProject = useCurrentProject();
7777

78+
const isEditor = useIsEditorOfCurrentProject();
7879
// Needs to assert some types here as Orval still doesn't get this right
7980
const {
8081
data: applicationsData,
@@ -113,7 +114,7 @@ const Executions = () => {
113114
// Then create a card for each
114115
?.map((job) => (
115116
<Grid item key={job.id} md={3} sm={6} xs={12}>
116-
<JobCard job={job} projectId={currentProject?.project_id} />
117+
<JobCard disabled={!isEditor} job={job} projectId={currentProject?.project_id} />
117118
</Grid>
118119
)) ?? [];
119120

@@ -126,7 +127,7 @@ const Executions = () => {
126127
return applicationCards;
127128
}
128129
return jobCards;
129-
}, [applications, currentProject?.project_id, executionTypes, jobs, searchValue]);
130+
}, [applications, currentProject?.project_id, executionTypes, jobs, searchValue, isEditor]);
130131

131132
return (
132133
<>
@@ -185,7 +186,15 @@ const Executions = () => {
185186
{!currentProject && (
186187
<Grid item xs={12}>
187188
<Alert severity="warning">
188-
Select a project from the settings to launch apps and run jobs
189+
Select a project from the settings to launch apps and run jobs.
190+
</Alert>
191+
</Grid>
192+
)}
193+
194+
{!isEditor && (
195+
<Grid item xs={12}>
196+
<Alert severity="warning">
197+
You must be a project editor to run jobs in this project.
189198
</Alert>
190199
</Grid>
191200
)}

0 commit comments

Comments
 (0)