Skip to content

Commit

Permalink
Merge pull request #51 from Devansh3712/kill-run
Browse files Browse the repository at this point in the history
pages/Run/index.tsx: Add kill run button
  • Loading branch information
zmc authored Sep 30, 2024
2 parents 2ebc077 + 53d2449 commit e2bad8f
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 20 deletions.
29 changes: 29 additions & 0 deletions src/components/Alert/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Snackbar from "@mui/material/Snackbar";
import Alert from "@mui/material/Alert";
import { useState } from "react";

type AlertProps = {
severity: "success" | "error",
message: string,
};

export default function AlertComponent(props: AlertProps) {
const [isOpen, setIsOpen] = useState(true);
const handleClose = (
event?: React.SyntheticEvent | Event,
reason?: string
) => {
if (reason === "clickaway") {
return;
}
setIsOpen(false);
};

return (
<Snackbar autoHideDuration={3000} open={isOpen} onClose={handleClose}>
<Alert onClose={handleClose} severity={props.severity} sx={{ width: "100%" }}>
{props.message}
</Alert>
</Snackbar>
);
}
157 changes: 157 additions & 0 deletions src/components/KillButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { useState } from "react";
import type { UseMutationResult, UseQueryResult } from "@tanstack/react-query";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import CircularProgress from "@mui/material/CircularProgress";
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import Dialog from '@mui/material/Dialog';
import Paper from "@mui/material/Paper";
import Typography from "@mui/material/Typography";
import Tooltip from '@mui/material/Tooltip';

import CodeBlock from "../CodeBlock";
import type { Run as RunResponse } from "../../lib/paddles.d";
import { KillRunPayload } from "../../lib/teuthologyAPI.d";
import { useSession, useRunKill } from "../../lib/teuthologyAPI";
import Alert from "../Alert";


type KillButtonProps = {
query: UseQueryResult<RunResponse>;
};

type KillButtonDialogProps = {
mutation: UseMutationResult;
payload: KillRunPayload;
open: boolean;
handleClose: () => void;
};

export default function KillButton({query: runQuery}: KillButtonProps) {
const killMutation = useRunKill();
const [open, setOpen] = useState(false);
const sessionQuery = useSession();
const data: RunResponse | undefined = runQuery.data;
const run_owner = data?.jobs[0].owner || "";
const killPayload: KillRunPayload = {
"--run": data?.name || "",
"--owner": run_owner,
"--machine-type": data?.machine_type || "",
"--preserve-queue": true,
}
const loggedUser = sessionQuery.data?.session?.username;
const isUserAdmin = sessionQuery.data?.session?.isUserAdmin;
const owner = killPayload["--owner"].toLowerCase()
const isOwner = (loggedUser?.toLowerCase() == owner) || (`scheduled_${loggedUser?.toLowerCase()}@teuthology` == owner)
const isButtonDisabled = (!isOwner && !isUserAdmin)

const getHelperMessage = () => {
if (isButtonDisabled) {
return `User (${loggedUser}) does not have admin privileges to kill runs owned by another user (${owner}). `;
} else {
if (!isOwner && isUserAdmin) return `Use admin privileges to kill run owned by '${owner}'. `;
return "Terminate all jobs in this run";
}
}

const toggleDialog = () => {
setOpen(!open);
};

const refreshAndtoggle = () => {
if (open && !killMutation.isIdle) { // on closing confirmation dialog after kill-run
runQuery.refetch();
}
toggleDialog();
killMutation.reset();
}

if ((data?.status.includes("finished")) || !(sessionQuery.data?.session?.username)) {
// run finished or user logged out
return null;
}


return (
<div>
<div style={{ display: "flex" }}>
<Tooltip arrow title={getHelperMessage()}>
<span>
<Button
variant="contained"
color="error"
size="large"
onClick={refreshAndtoggle}
disabled={isButtonDisabled}
sx={{ marginBottom: "12px" }}
>
{(isOwner) ? "Kill Run" : "Kill Run As Admin"}
</Button>
</span>
</Tooltip>
<KillButtonDialog
mutation={killMutation}
payload={killPayload}
open={open}
handleClose={refreshAndtoggle}
/>
</div>
{ (killMutation.isError) ? <Alert severity="error" message="Unable to kill run" /> : null }
{ (killMutation.isSuccess) ? <Alert severity="success" message={`Run killed successfully! \n`} /> : null }
</div>
);
};

function KillButtonDialog({mutation, open, handleClose, payload}: KillButtonDialogProps) {
return (
<div>
<Dialog onClose={handleClose} open={open} scroll="paper" fullWidth={true} maxWidth="md">
<DialogTitle variant="h6">KILL CONFIRMATION</DialogTitle>
<DialogContent dividers>
{ (mutation.isSuccess && mutation.data ) ?
<div>
<Typography variant="h6" display="block" color="green" gutterBottom>
Successful!
</Typography>
<Paper>
<CodeBlock value={mutation.data?.data?.logs || ""} language="python" />
</Paper>
</div> :
(mutation.isLoading) ? (
<div style={{display: "flex", alignItems: "center"}}>
<CircularProgress size={20} color="inherit" style={{marginRight: "12px"}} />
<Typography variant="subtitle1" display="block">
Killing run...
</Typography>
</div>
) :
(mutation.isError) ? (
<div>
<Typography variant="h6" display="block" color="red" gutterBottom>
Failed!
</Typography>
<Paper>
<CodeBlock value={(mutation.error?.response?.data?.detail) || ""} language="python" />
</Paper>
</div>
) :
<div>
<Typography variant="overline" display="block" gutterBottom>
Are you sure you want to kill this run/job?
</Typography>
<Button
variant="contained"
color="error"
size="large"
onClick={() => mutation.mutate(payload)}
>
Yes, I'm sure
</Button>
</div>
}
</DialogContent>
</Dialog>
</div>
)
}
24 changes: 9 additions & 15 deletions src/components/Login/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import GitHubIcon from '@mui/icons-material/GitHub';

import { doLogin, doLogout, useSession, useUserData } from "../../lib/teuthologyAPI";
import { doLogin, doLogout, useSession } from "../../lib/teuthologyAPI";


export default function Login() {
const sessionQuery = useSession();
const userData = useUserData();
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const [dropMenuAnchor, setDropMenuAnchor] = useState(null);
const open = Boolean(dropMenuAnchor);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
setDropMenuAnchor(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
setDropMenuAnchor(null);
};

if ( ! sessionQuery.isSuccess ) return null;
Expand All @@ -27,26 +26,21 @@ export default function Login() {
{sessionQuery.data?.session
? <div>
<Avatar
alt={userData.get("username") || ""}
src={userData.get("avatar_url") || ""}
alt={sessionQuery.data?.session?.username || ""}
src={sessionQuery.data?.session?.avatar_url || ""}
onClick={handleClick}
aria-controls={open ? 'basic-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
/>
<Menu
id="basic-menu"
anchorEl={anchorEl}
anchorEl={dropMenuAnchor}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={doLogout}>Logout</MenuItem>
</Menu>
</div>
: <Button
variant="contained"
color="success"
onClick={doLogin}
startIcon={<GitHubIcon fontSize="small" /> }
disabled={sessionQuery.isError}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/paddles.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export type Job = {
roles: NodeRoles[];
os_type: string;
os_version: string;
owner: string;
};

export type NodeRoles = string[];
Expand Down Expand Up @@ -78,6 +79,7 @@ export type Run = {
results: RunResults;
machine_type: string;
status: RunStatus;
user: string;
};

export type Node = {
Expand Down
15 changes: 15 additions & 0 deletions src/lib/teuthologyAPI.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

export type Session = {
session: {
id: int,
username: string,
isUserAdmin: boolean,
}
}

export type KillRunPayload = {
"--run": string,
"--owner": string,
"--machine-type": string,
"--preserve-queue": boolean,
}
26 changes: 21 additions & 5 deletions src/lib/teuthologyAPI.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import axios from "axios";
import { useQuery } from "@tanstack/react-query";
import { useQuery, useMutation } from "@tanstack/react-query";
import type { UseQueryResult, UseMutationResult } from "@tanstack/react-query";
import { Cookies } from "react-cookie";
import type { UseQueryResult } from "@tanstack/react-query";
import { Session } from "./teuthologyAPI.d"

const TEUTHOLOGY_API_SERVER =
import.meta.env.VITE_TEUTHOLOGY_API || "";
Expand All @@ -25,9 +26,9 @@ function doLogout() {
window.location.href = url;
}

function useSession(): UseQueryResult {
function useSession(): UseQueryResult<Session> {
const url = getURL("/");
const query = useQuery({
const query = useQuery<Session, Error>({
queryKey: ['ping-api', { url }],
queryFn: () => (
axios.get(url, {
Expand Down Expand Up @@ -56,9 +57,24 @@ function useUserData(): Map<string, string> {
return new Map();
}

function useRunKill(): UseMutationResult {
const url = getURL("/kill/?logs=true");
const mutation: UseMutationResult = useMutation({
mutationKey: ['run-kill', { url }],
mutationFn: (payload) => (
axios.post(url, payload, {
withCredentials: true
})
),
retry: 0,
});
return mutation;
}

export {
doLogin,
doLogout,
useSession,
useUserData
useUserData,
useRunKill,
}
2 changes: 2 additions & 0 deletions src/pages/Run/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { Run as Run_, RunParams } from "../../lib/paddles.d";
import { useRun } from "../../lib/paddles";
import JobList from "../../components/JobList";
import Link from "../../components/Link";
import KillButton from "../../components/KillButton";

const PREFIX = "index";

Expand Down Expand Up @@ -72,6 +73,7 @@ export default function Run() {
date
</FilterLink>
</div>
<KillButton query={query} />
<JobList query={query} params={params} setter={setParams} />
</Root>
);
Expand Down

0 comments on commit e2bad8f

Please sign in to comment.