From 20daee21e5ebd26b5beb3be85a8e78ffc61f9de6 Mon Sep 17 00:00:00 2001 From: Med16-11 Date: Mon, 26 Jun 2023 00:12:57 +0530 Subject: [PATCH 1/7] Added job scheduling page Signed-off-by: Med16-11 resolved conflits --- src/components/Drawer/index.jsx | 5 +- src/pages/Schedule/index.jsx | 198 ++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 src/pages/Schedule/index.jsx diff --git a/src/components/Drawer/index.jsx b/src/components/Drawer/index.jsx index 1b971fb..0339a51 100644 --- a/src/components/Drawer/index.jsx +++ b/src/components/Drawer/index.jsx @@ -77,7 +77,6 @@ export default function Drawer(props) { Runs - Nodes @@ -87,7 +86,9 @@ export default function Drawer(props) { Node Jobs Stats - + + Schedule + ); } diff --git a/src/pages/Schedule/index.jsx b/src/pages/Schedule/index.jsx new file mode 100644 index 0000000..7fdac09 --- /dev/null +++ b/src/pages/Schedule/index.jsx @@ -0,0 +1,198 @@ +import React, { useState, useEffect } from "react"; +import Typography from "@mui/material/Typography"; +import { Helmet } from "react-helmet"; +import Button from "@mui/material/Button"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { useLocalStorage } from 'usehooks-ts'; + +export default function Schedule() { + const predefinedKeyOptions = [ + "--ceph-repo", "--suite-repo", "--subset", + "--suite-branch", "--suite", "--limit", + "--repo", "--ceph-branch", "subset" + ]; + + const [inputValue, setInputValue] = useLocalStorage("inputValue", ""); + const [selectedKeyIndices, setSelectedKeyIndices] = useLocalStorage("selectedKeyIndices", []); + const [valueData, setValueData] = useLocalStorage("valueData", []); + const [hoveredRowIndex, setHoveredRowIndex] = useState(null); + const [checkedRows, setCheckedRows] = useLocalStorage("checkedRows", []); + + const handleInputChange = (event) => { + setInputValue(event.target.value); + }; + + const handleRun = () => { + updateCommand(checkedRows); + }; + + const handleDryRun = () => { + updateCommand(checkedRows) + }; + + useEffect(() => { + updateCommand(checkedRows); + }, [selectedKeyIndices, valueData, checkedRows]); + + const addNewRow = () => { + setSelectedKeyIndices([...selectedKeyIndices, 0]); + setValueData([...valueData, ""]); + }; + + const handleCheckboxChange = (index, event) => { + const newCheckedRows = [...checkedRows]; + + if (event.target.checked) { + newCheckedRows.push(index); + } else { + const indexToRemove = newCheckedRows.indexOf(index); + if (indexToRemove !== -1) { + newCheckedRows.splice(indexToRemove, 1); + } + } + + setCheckedRows(newCheckedRows); + updateCommand(newCheckedRows); + }; + + const handleKeySelectChange = (index, event) => { + const updatedSelectedKeyIndices = [...selectedKeyIndices]; + updatedSelectedKeyIndices[index] = event.target.value; + setSelectedKeyIndices(updatedSelectedKeyIndices); + updateCommand(checkedRows); + }; + + const handleValueChange = (index, event) => { + const updatedValueData = [...valueData]; + updatedValueData[index] = event.target.value; + setValueData(updatedValueData); + updateCommand(checkedRows); + }; + + const handleDeleteRow = (index) => { + const updatedSelectedKeyIndices = [...selectedKeyIndices]; + updatedSelectedKeyIndices.splice(index, 1); + setSelectedKeyIndices(updatedSelectedKeyIndices); + + const updatedValueData = [...valueData]; + updatedValueData.splice(index, 1); + setValueData(updatedValueData); + + const newCheckedRows = checkedRows.filter((checkedIndex) => checkedIndex !== index); + setCheckedRows(newCheckedRows); + + updateCommand(newCheckedRows); + }; + + const updateCommand = (checkedRows) => { + const commandArgs = checkedRows + .map((index) => { + const selectedKeyIndex = selectedKeyIndices[index]; + const selectedKey = predefinedKeyOptions[selectedKeyIndex]; + const selectedValue = valueData[index]; + return `${selectedKey} ${selectedValue}`; + }) + .join(" "); + const teuthologySuiteCommand = `teuthology suite ${commandArgs}`; + + setInputValue(teuthologySuiteCommand); + }; + + return ( +
+ + Schedule - Pulpito + + + Schedule a run + +
+ +
+ + +
+
+ + + + + + + + + + {selectedKeyIndices.map((selectedKeyIndex, index) => ( + setHoveredRowIndex(index)} + onMouseLeave={() => setHoveredRowIndex(null)} + style={{ + background: hoveredRowIndex === index ? "#f5f5f5" : "transparent", + }} + > + + + + + ))} + +
KeyValue
+ handleCheckboxChange(index, event)} + /> + + + + +
+ handleValueChange(index, event)} + /> + {hoveredRowIndex === index && ( + handleDeleteRow(index)} + /> + )} +
+
+ +
+); +} From ee23161ea8d10fa053ff0e223d8bc73c5ab7b754 Mon Sep 17 00:00:00 2001 From: Med16-11 Date: Wed, 23 Aug 2023 20:22:01 +0530 Subject: [PATCH 2/7] lock & unlock widget added Signed-off-by: Med16-11 --- src/pages/Schedule/index.jsx | 103 +++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 35 deletions(-) diff --git a/src/pages/Schedule/index.jsx b/src/pages/Schedule/index.jsx index 7fdac09..a328779 100644 --- a/src/pages/Schedule/index.jsx +++ b/src/pages/Schedule/index.jsx @@ -3,7 +3,9 @@ import Typography from "@mui/material/Typography"; import { Helmet } from "react-helmet"; import Button from "@mui/material/Button"; import DeleteIcon from "@mui/icons-material/Delete"; -import { useLocalStorage } from 'usehooks-ts'; +import LockIcon from "@mui/icons-material/Lock"; +import LockOpenIcon from "@mui/icons-material/LockOpen"; +import { useLocalStorage } from "usehooks-ts"; export default function Schedule() { const predefinedKeyOptions = [ @@ -17,6 +19,8 @@ export default function Schedule() { const [valueData, setValueData] = useLocalStorage("valueData", []); const [hoveredRowIndex, setHoveredRowIndex] = useState(null); const [checkedRows, setCheckedRows] = useLocalStorage("checkedRows", []); + const [rowLocks, setRowLocks] = useLocalStorage("rowLocks", Array(selectedKeyIndices.length).fill(false)); + const [keyLocks, setKeyLocks] = useLocalStorage("keyLocks", Array(selectedKeyIndices.length).fill(false)); const handleInputChange = (event) => { setInputValue(event.target.value); @@ -26,8 +30,8 @@ export default function Schedule() { updateCommand(checkedRows); }; - const handleDryRun = () => { - updateCommand(checkedRows) + const handleDryRun = () => { + updateCommand(checkedRows); }; useEffect(() => { @@ -37,6 +41,8 @@ export default function Schedule() { const addNewRow = () => { setSelectedKeyIndices([...selectedKeyIndices, 0]); setValueData([...valueData, ""]); + setRowLocks([...rowLocks, false]); + setKeyLocks([...keyLocks, false]); }; const handleCheckboxChange = (index, event) => { @@ -81,20 +87,38 @@ export default function Schedule() { const newCheckedRows = checkedRows.filter((checkedIndex) => checkedIndex !== index); setCheckedRows(newCheckedRows); + const updatedRowLocks = [...rowLocks]; + updatedRowLocks.splice(index, 1); + setRowLocks(updatedRowLocks); + + const updatedKeyLocks = [...keyLocks]; + updatedKeyLocks.splice(index, 1); + setKeyLocks(updatedKeyLocks); + updateCommand(newCheckedRows); }; + const toggleRowLock = (index) => { + const updatedRowLocks = [...rowLocks]; + updatedRowLocks[index] = !updatedRowLocks[index]; + setRowLocks(updatedRowLocks); + + const updatedKeyLocks = [...keyLocks]; + updatedKeyLocks[index] = !updatedKeyLocks[index]; + setKeyLocks(updatedKeyLocks); + }; + const updateCommand = (checkedRows) => { const commandArgs = checkedRows .map((index) => { - const selectedKeyIndex = selectedKeyIndices[index]; + const selectedKeyIndex = selectedKeyIndices[index]; const selectedKey = predefinedKeyOptions[selectedKeyIndex]; const selectedValue = valueData[index]; return `${selectedKey} ${selectedValue}`; }) .join(" "); const teuthologySuiteCommand = `teuthology suite ${commandArgs}`; - + setInputValue(teuthologySuiteCommand); }; @@ -118,18 +142,18 @@ export default function Schedule() { + style={{ margin: "10px", padding: "8px", backgroundColor: "#1976D2", color: "#fff" }} + variant="contained" + onClick={handleDryRun} + > + Dry Run + @@ -151,18 +175,26 @@ export default function Schedule() { }} > - - ))} - -
- handleCheckboxChange(index, event)} - /> - +
+ handleCheckboxChange(index, event)} + /> +
toggleRowLock(index)} + > + {rowLocks[index] ? : } +
+
- - -); + > + {hoveredRowIndex === index && } + + + + + ))} + + + + + ); } From 564c1b915aad0880c74c427b06633499a3f7a92d Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Thu, 9 Nov 2023 13:06:19 -0700 Subject: [PATCH 3/7] App: Add route for Schedule Signed-off-by: Zack Cerza --- src/App.tsx | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index af26dd9..60e2c0d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,7 +13,7 @@ import Nodes from "./pages/Nodes"; import Node from "./pages/Node"; import StatsNodesLock from "./pages/StatsNodesLock"; import StatsNodesJobs from "./pages/StatsNodesJobs"; -import { ErrorBoundary } from "react-error-boundary"; +import Schedule from "./pages/Schedule"; import "./App.css"; import ErrorFallback from "./components/ErrorFallback"; @@ -39,21 +39,20 @@ function App(props: AppProps) {
- - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +
); From aedaec802c80f26e157fb59961f1dc67c2b9a88e Mon Sep 17 00:00:00 2001 From: Kamoltat Sirivadhna Date: Thu, 30 Nov 2023 10:57:33 -0500 Subject: [PATCH 4/7] src/pages/Schedule/index: UI facelift + improve logic 1. Rewrote the components with Material UI 2. Improved OnListener logics 3. Improved how we store data in useLocalStorage. Signed-off-by: Kamoltat Sirivadhna --- src/pages/Schedule/index.jsx | 448 +++++++++++++++++++++-------------- 1 file changed, 267 insertions(+), 181 deletions(-) diff --git a/src/pages/Schedule/index.jsx b/src/pages/Schedule/index.jsx index a328779..a237f0b 100644 --- a/src/pages/Schedule/index.jsx +++ b/src/pages/Schedule/index.jsx @@ -1,125 +1,160 @@ -import React, { useState, useEffect } from "react"; -import Typography from "@mui/material/Typography"; +import { useEffect, useState } from 'react'; import { Helmet } from "react-helmet"; +import { useLocalStorage } from "usehooks-ts"; +import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; +import Fab from '@mui/material/Fab'; +import AddIcon from '@mui/icons-material/Add'; import DeleteIcon from "@mui/icons-material/Delete"; import LockIcon from "@mui/icons-material/Lock"; import LockOpenIcon from "@mui/icons-material/LockOpen"; -import { useLocalStorage } from "usehooks-ts"; +import Paper from '@mui/material/Paper'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import TableCell from '@mui/material/TableCell'; +import TableBody from '@mui/material/TableBody'; +import Table from '@mui/material/Table'; +import TextField from '@mui/material/TextField'; +import Select from '@mui/material/Select'; +import MenuItem from '@mui/material/MenuItem'; +import Checkbox from '@mui/material/Checkbox'; +import Tooltip from '@mui/material/Tooltip'; +import InfoIcon from '@mui/icons-material/Info'; export default function Schedule() { - const predefinedKeyOptions = [ - "--ceph-repo", "--suite-repo", "--subset", - "--suite-branch", "--suite", "--limit", - "--repo", "--ceph-branch", "subset" - ]; + const keyOptions = + [ + "--ceph", + "--ceph-repo", + "--suite-repo", + "--suite-branch", + "--suite", + "--subset", + "--machine", + "--filter", + "--distro", + "--rerun", + "--rerun-statuses", + "--limit", + "--priority", + ]; + const OptionsInfo = { + "--ceph": "The ceph branch to run against [default: main]", + "--ceph-repo": "Query this repository for Ceph branch and \ + SHA1 values [default: https://github.com/ceph/ceph-ci.git]", + "--suite-repo": "Use tasks and suite definition in this \ + repository [default: https://github.com/ceph/ceph-ci.git]", + "--suite-branch": "Use this suite branch instead of the ceph branch", + "--suite": "The suite to schedule", + "--subset": "Instead of scheduling the entire suite, break the \ + set of jobs into pieces (each of which will \ + contain each facet at least once) and schedule \ + piece . Scheduling 0/, 1/, \ + 2/ ... -1/ will schedule all \ + jobs in the suite (many more than once). If specified, \ + this value can be found in results.log.", + "--machine": "Machine type e.g., smithi, mira, gibba.", + "--filter": "Only run jobs whose description contains at least one \ + of the keywords in the comma separated keyword string specified.", + "--distro": "Distribution to run against", + "--rerun": "Attempt to reschedule a run, selecting only those \ + jobs whose status are mentioned by --rerun-status. \ + Note that this is implemented by scheduling an \ + entirely new suite and including only jobs whose \ + descriptions match the selected ones. It does so \ + using the same logic as --filter. \ + Of all the flags that were passed when scheduling \ + the original run, the resulting one will only \ + inherit the --suite value. Any other arguments \ + must be passed again while scheduling. By default, \ + 'seed' and 'subset' will be taken from results.log, \ + but can be overide if passed again. \ + This is important for tests involving random facet \ + (path ends with '$' operator).", + "--rerun-statuses": "A comma-separated list of statuses to be used \ + with --rerun. Supported statuses are: 'dead', \ + 'fail', 'pass', 'queued', 'running', 'waiting' \ + [default: fail,dead]", + "--limit": "Queue at most this many jobs [default: 0]", + "--priority": "Job priority (lower is sooner) 0 - 1000", + } - const [inputValue, setInputValue] = useLocalStorage("inputValue", ""); - const [selectedKeyIndices, setSelectedKeyIndices] = useLocalStorage("selectedKeyIndices", []); - const [valueData, setValueData] = useLocalStorage("valueData", []); - const [hoveredRowIndex, setHoveredRowIndex] = useState(null); - const [checkedRows, setCheckedRows] = useLocalStorage("checkedRows", []); - const [rowLocks, setRowLocks] = useLocalStorage("rowLocks", Array(selectedKeyIndices.length).fill(false)); - const [keyLocks, setKeyLocks] = useLocalStorage("keyLocks", Array(selectedKeyIndices.length).fill(false)); + const [rowData, setRowData] = useLocalStorage("rowData", []); + const [rowIndex, setRowIndex] = useLocalStorage("rowIndex", -1); + const [commandBarValue, setCommandBarValue] = useState([]); - const handleInputChange = (event) => { - setInputValue(event.target.value); - }; + useEffect(() => { + setCommandBarValue(rowData); + }, [rowData]) const handleRun = () => { - updateCommand(checkedRows); + return false; }; const handleDryRun = () => { - updateCommand(checkedRows); + return false; }; - useEffect(() => { - updateCommand(checkedRows); - }, [selectedKeyIndices, valueData, checkedRows]); + const handleForcePriority = () => { + return false; + }; const addNewRow = () => { - setSelectedKeyIndices([...selectedKeyIndices, 0]); - setValueData([...valueData, ""]); - setRowLocks([...rowLocks, false]); - setKeyLocks([...keyLocks, false]); + console.log("addNewRow"); + const updatedRowIndex = rowIndex + 1; + setRowIndex(updatedRowIndex); + const index = (updatedRowIndex % keyOptions.length); + const object = { + key: keyOptions[index], + value: "", + lock: false, + checked: true, + } + const updatedRowData = [...rowData]; + updatedRowData.push(object); + setRowData(updatedRowData); }; const handleCheckboxChange = (index, event) => { - const newCheckedRows = [...checkedRows]; - + console.log("handleCheckboxChange"); + const newRowData = [...rowData]; if (event.target.checked) { - newCheckedRows.push(index); + newRowData[index].checked = true; } else { - const indexToRemove = newCheckedRows.indexOf(index); - if (indexToRemove !== -1) { - newCheckedRows.splice(indexToRemove, 1); - } + newRowData[index].checked = false; } - - setCheckedRows(newCheckedRows); - updateCommand(newCheckedRows); + setRowData(newRowData); }; const handleKeySelectChange = (index, event) => { - const updatedSelectedKeyIndices = [...selectedKeyIndices]; - updatedSelectedKeyIndices[index] = event.target.value; - setSelectedKeyIndices(updatedSelectedKeyIndices); - updateCommand(checkedRows); + console.log("handleKeySelectChange"); + const newRowData = [...rowData]; + newRowData[index].key = event.target.value; + setRowData(newRowData); }; const handleValueChange = (index, event) => { - const updatedValueData = [...valueData]; - updatedValueData[index] = event.target.value; - setValueData(updatedValueData); - updateCommand(checkedRows); + console.log("handleValueChange"); + const newRowData = [...rowData]; + newRowData[index].value = event.target.value; + setRowData(newRowData); }; const handleDeleteRow = (index) => { - const updatedSelectedKeyIndices = [...selectedKeyIndices]; - updatedSelectedKeyIndices.splice(index, 1); - setSelectedKeyIndices(updatedSelectedKeyIndices); - - const updatedValueData = [...valueData]; - updatedValueData.splice(index, 1); - setValueData(updatedValueData); - - const newCheckedRows = checkedRows.filter((checkedIndex) => checkedIndex !== index); - setCheckedRows(newCheckedRows); - - const updatedRowLocks = [...rowLocks]; - updatedRowLocks.splice(index, 1); - setRowLocks(updatedRowLocks); - - const updatedKeyLocks = [...keyLocks]; - updatedKeyLocks.splice(index, 1); - setKeyLocks(updatedKeyLocks); - - updateCommand(newCheckedRows); + console.log("handleDeleteRow"); + let newRowData = [...rowData]; + newRowData.splice(index, 1) + setRowData(newRowData); + const updatedRowIndex = rowIndex - 1; + setRowIndex(updatedRowIndex); }; const toggleRowLock = (index) => { - const updatedRowLocks = [...rowLocks]; - updatedRowLocks[index] = !updatedRowLocks[index]; - setRowLocks(updatedRowLocks); - - const updatedKeyLocks = [...keyLocks]; - updatedKeyLocks[index] = !updatedKeyLocks[index]; - setKeyLocks(updatedKeyLocks); - }; - - const updateCommand = (checkedRows) => { - const commandArgs = checkedRows - .map((index) => { - const selectedKeyIndex = selectedKeyIndices[index]; - const selectedKey = predefinedKeyOptions[selectedKeyIndex]; - const selectedValue = valueData[index]; - return `${selectedKey} ${selectedValue}`; - }) - .join(" "); - const teuthologySuiteCommand = `teuthology suite ${commandArgs}`; - - setInputValue(teuthologySuiteCommand); + console.log("toggleRowLock"); + const newRowData = [...rowData]; + newRowData[index].lock = !newRowData[index].lock; + setRowData(newRowData); }; return ( @@ -127,105 +162,156 @@ export default function Schedule() { Schedule - Pulpito - + Schedule a run -
- -
- - +
+ + { + if (data.checked) { + return `${data.key} ${data.value}`; + } + }) + .join(" ")}`} + placeholder="teuthology-suite" + disabled={true} + /> + +
+ + + + + + + + +
- - - - - - - - - - {selectedKeyIndices.map((selectedKeyIndex, index) => ( - setHoveredRowIndex(index)} - onMouseLeave={() => setHoveredRowIndex(null)} - style={{ - background: hoveredRowIndex === index ? "#f5f5f5" : "transparent", - }} - > - -
KeyValue
-
- handleCheckboxChange(index, event)} - /> -
toggleRowLock(index)} - > - {rowLocks[index] ? : } -
-
-
- + + + + Key + Value + + + + { + {rowData.map((data, index) => ( + - {predefinedKeyOptions.map((option, optionIndex) => ( - - ))} - - - - - ))} - -
-
- handleValueChange(index, event)} - disabled={rowLocks[index]} - /> -
handleDeleteRow(index)} - > - {hoveredRowIndex === index && } -
-
-
- - + + handleCheckboxChange(index, event)} /> + + +
+ + + + +
+
+ + handleValueChange(index, event)} + disabled={data.lock} + /> + + +
+ +
toggleRowLock(index)} + > + {data.lock ? : } +
+
+ +
{ + handleDeleteRow(index); + }} + > + +
+
+
+
+ + ))} + } +
+ +
+ + + + + + +
); } From 9941df59477534ad7040945472c6f51898601def Mon Sep 17 00:00:00 2001 From: Kamoltat Sirivadhna Date: Tue, 30 Jan 2024 14:50:56 -0500 Subject: [PATCH 5/7] src/pages/Schedule: Connect pulpito-ng with teuthology-api for scheduling suites Use axios post request to for run, dry-run and force-priority Signed-off-by: Kamoltat Sirivadhna --- src/lib/teuthologyAPI.ts | 36 ++++++++++++++++++++++++++++++------ src/pages/Schedule/index.jsx | 29 +++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/lib/teuthologyAPI.ts b/src/lib/teuthologyAPI.ts index 41f6651..1a7bcc7 100644 --- a/src/lib/teuthologyAPI.ts +++ b/src/lib/teuthologyAPI.ts @@ -3,28 +3,51 @@ import { useQuery } from "@tanstack/react-query"; import { Cookies } from "react-cookie"; import type { UseQueryResult } from "@tanstack/react-query"; -const TEUTHOLOGY_API_SERVER = +const TEUTHOLOGY_API_SERVER = import.meta.env.VITE_TEUTHOLOGY_API || ""; const GH_USER_COOKIE = "GH_USER"; -function getURL(relativeURL: URL|string): string { - if ( ! TEUTHOLOGY_API_SERVER ) return ""; +function getURL(relativeURL: URL | string): string { + if (!TEUTHOLOGY_API_SERVER) return ""; return new URL(relativeURL, TEUTHOLOGY_API_SERVER).toString(); } function doLogin() { const url = getURL("/login/"); - if ( url ) window.location.href = url; + if (url) window.location.href = url; } function doLogout() { const cookies = new Cookies(); cookies.remove(GH_USER_COOKIE); - + const url = getURL("/logout/"); window.location.href = url; } +function doSchedule(commandValue: any, dryRun = false) { + console.log("doSchedule"); + console.log(commandValue); + let url; + if (dryRun) { + url = getURL("/suite?dry_run=true"); + } else { + url = getURL("/suite?dry_run=false"); + } + if (commandValue['--user'] != useUserData().get("username")) { + console.log("Error: --user doesn't match username of current logged in account"); + return false; + } + axios.post(url, commandValue, { + withCredentials: true, + headers: { "Content-Type": "application/json" }, + }).then((resp) => { + console.log(resp); + }, (error) => { + console.log(error); + }); +} + function useSession(): UseQueryResult { const url = getURL("/"); const query = useQuery({ @@ -59,6 +82,7 @@ function useUserData(): Map { export { doLogin, doLogout, + doSchedule, useSession, - useUserData + useUserData, } diff --git a/src/pages/Schedule/index.jsx b/src/pages/Schedule/index.jsx index a237f0b..e17bc6e 100644 --- a/src/pages/Schedule/index.jsx +++ b/src/pages/Schedule/index.jsx @@ -21,6 +21,7 @@ import MenuItem from '@mui/material/MenuItem'; import Checkbox from '@mui/material/Checkbox'; import Tooltip from '@mui/material/Tooltip'; import InfoIcon from '@mui/icons-material/Info'; +import { useUserData, doSchedule } from '../../lib/teuthologyAPI'; export default function Schedule() { const keyOptions = @@ -83,20 +84,44 @@ export default function Schedule() { const [rowData, setRowData] = useLocalStorage("rowData", []); const [rowIndex, setRowIndex] = useLocalStorage("rowIndex", -1); const [commandBarValue, setCommandBarValue] = useState([]); + const userData = useUserData(); + let commandValue = {}; useEffect(() => { setCommandBarValue(rowData); }, [rowData]) + function getCommandValue() { + let retCommandValue = {}; + commandBarValue.map((data) => { + if (data.checked) { + retCommandValue[data.key] = data.value; + } + }) + let username = userData.get("username"); + if (!username) { + console.log("User is not logged in"); + return {}; + } else { + retCommandValue['--user'] = userData.get("username"); + } + return retCommandValue; + } + const handleRun = () => { - return false; + let commandValue = getCommandValue(); + doSchedule(commandValue); }; const handleDryRun = () => { - return false; + let commandValue = getCommandValue(); + doSchedule(commandValue, true); }; const handleForcePriority = () => { + let commandValue = getCommandValue(); + commandValue['--force-priority'] = true; + doSchedule(commandValue); return false; }; From e1a1300f3041896e744c7e1c94416f84a748e12a Mon Sep 17 00:00:00 2001 From: Kamoltat Sirivadhna Date: Thu, 1 Feb 2024 17:59:56 -0500 Subject: [PATCH 6/7] src/pages/Schedule: Added useMutation and CircularProgress schedule feature uses useMutation to deal with cases like onSuccess, onError and isLoading. CircularProgress is added when button is clicked and mutation is in the state of isLoading. Signed-off-by: Kamoltat Sirivadhna --- src/lib/teuthologyAPI.ts | 23 +++--- src/pages/Schedule/index.jsx | 151 +++++++++++++++++++++++++++-------- 2 files changed, 126 insertions(+), 48 deletions(-) diff --git a/src/lib/teuthologyAPI.ts b/src/lib/teuthologyAPI.ts index 1a7bcc7..364a68f 100644 --- a/src/lib/teuthologyAPI.ts +++ b/src/lib/teuthologyAPI.ts @@ -25,26 +25,21 @@ function doLogout() { window.location.href = url; } -function doSchedule(commandValue: any, dryRun = false) { - console.log("doSchedule"); - console.log(commandValue); - let url; - if (dryRun) { - url = getURL("/suite?dry_run=true"); - } else { - url = getURL("/suite?dry_run=false"); +async function useSchedule(commandValue: any) { + const url = getURL("/suite?logs=true"); + const username = useUserData().get("username"); + if (username) { + commandValue['--owner'] = username; } - if (commandValue['--user'] != useUserData().get("username")) { - console.log("Error: --user doesn't match username of current logged in account"); - return false; - } - axios.post(url, commandValue, { + await axios.post(url, commandValue, { withCredentials: true, headers: { "Content-Type": "application/json" }, }).then((resp) => { console.log(resp); + return resp; }, (error) => { console.log(error); + throw new Error(error); }); } @@ -82,7 +77,7 @@ function useUserData(): Map { export { doLogin, doLogout, - doSchedule, + useSchedule, useSession, useUserData, } diff --git a/src/pages/Schedule/index.jsx b/src/pages/Schedule/index.jsx index e17bc6e..84fd0a9 100644 --- a/src/pages/Schedule/index.jsx +++ b/src/pages/Schedule/index.jsx @@ -21,7 +21,11 @@ import MenuItem from '@mui/material/MenuItem'; import Checkbox from '@mui/material/Checkbox'; import Tooltip from '@mui/material/Tooltip'; import InfoIcon from '@mui/icons-material/Info'; -import { useUserData, doSchedule } from '../../lib/teuthologyAPI'; +import Alert from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import CircularProgress from '@mui/material/CircularProgress'; +import { useUserData, useSchedule } from '../../lib/teuthologyAPI'; +import { useMutation } from "@tanstack/react-query"; export default function Schedule() { const keyOptions = @@ -85,13 +89,68 @@ export default function Schedule() { const [rowIndex, setRowIndex] = useLocalStorage("rowIndex", -1); const [commandBarValue, setCommandBarValue] = useState([]); const userData = useUserData(); - let commandValue = {}; + + const [open, setOpenSuccess] = useState(false); + const [openErr, setOpenErr] = useState(false); + const [ErrText, setErrText] = useState(""); + + const handleOpenSuccess = () => { + setOpenSuccess(true); + }; + const handleOpenErr = (errText) => { + setErrText(errText); + setOpenErr(true); + }; + + const handleCloseSuccess = () => { + setOpenSuccess(false); + }; + const handleCloseErr = () => { + setOpenErr(false); + }; + + const clickRun = useMutation({ + mutationFn: async (commandValue) => { + return await useSchedule(commandValue); + }, + onSuccess: () => { + handleOpenSuccess(); + }, + onError: () => { + handleOpenErr("Failed to Schedule Run"); + } + }) + + const clickDryRun = useMutation({ + mutationFn: async (commandValue) => { + return await useSchedule(commandValue); + }, + onSuccess: () => { + handleOpenSuccess(); + }, + onError: () => { + handleOpenErr("Failed to Schedule Dry Run"); + } + }) + + const clickForcePriority = useMutation({ + mutationFn: async (commandValue) => { + commandValue['--force-priority'] = true; + return await useSchedule(commandValue); + }, + onSuccess: () => { + handleOpenSuccess(); + }, + onError: () => { + handleOpenErr("Failed to Schedule run with --force-priority"); + } + }) useEffect(() => { setCommandBarValue(rowData); }, [rowData]) - function getCommandValue() { + function getCommandValue(dry_run) { let retCommandValue = {}; commandBarValue.map((data) => { if (data.checked) { @@ -105,26 +164,14 @@ export default function Schedule() { } else { retCommandValue['--user'] = userData.get("username"); } + if (dry_run) { + retCommandValue['--dry-run'] = true; + } else { + retCommandValue['--dry-run'] = false; + } return retCommandValue; } - const handleRun = () => { - let commandValue = getCommandValue(); - doSchedule(commandValue); - }; - - const handleDryRun = () => { - let commandValue = getCommandValue(); - doSchedule(commandValue, true); - }; - - const handleForcePriority = () => { - let commandValue = getCommandValue(); - commandValue['--force-priority'] = true; - doSchedule(commandValue); - return false; - }; - const addNewRow = () => { console.log("addNewRow"); const updatedRowIndex = rowIndex + 1; @@ -207,32 +254,68 @@ export default function Schedule() {
- + } + + + {ErrText} + + - + {clickForcePriority.isLoading ? ( + + ) : + } - + )}
From d876adc9582dd03bd083c09b63052cc2ae10dfd7 Mon Sep 17 00:00:00 2001 From: Kamoltat Sirivadhna Date: Wed, 13 Mar 2024 16:34:24 -0400 Subject: [PATCH 7/7] src/pages/Schedule: Display logs + Add Warning alerts 1. After a run is scheduled, user will be able to see the results including the logs from teuthology-suite. Only success runs will show all logs from teuthology-suite, failed runs will only return the exception with descriptive failure reasons. 2. Added Warning Alerts for 0 jobs scheduled in a run 3. Added more arguments that can be use to schedule run Signed-off-by: Kamoltat Sirivadhna --- src/lib/teuthologyAPI.ts | 4 +- src/pages/Schedule/index.jsx | 221 +++++++++++++++++++++++------------ 2 files changed, 151 insertions(+), 74 deletions(-) diff --git a/src/lib/teuthologyAPI.ts b/src/lib/teuthologyAPI.ts index 364a68f..6ca8c74 100644 --- a/src/lib/teuthologyAPI.ts +++ b/src/lib/teuthologyAPI.ts @@ -31,7 +31,7 @@ async function useSchedule(commandValue: any) { if (username) { commandValue['--owner'] = username; } - await axios.post(url, commandValue, { + return await axios.post(url, commandValue, { withCredentials: true, headers: { "Content-Type": "application/json" }, }).then((resp) => { @@ -39,7 +39,7 @@ async function useSchedule(commandValue: any) { return resp; }, (error) => { console.log(error); - throw new Error(error); + throw error; }); } diff --git a/src/pages/Schedule/index.jsx b/src/pages/Schedule/index.jsx index 84fd0a9..b055dfb 100644 --- a/src/pages/Schedule/index.jsx +++ b/src/pages/Schedule/index.jsx @@ -26,7 +26,12 @@ import Snackbar from '@mui/material/Snackbar'; import CircularProgress from '@mui/material/CircularProgress'; import { useUserData, useSchedule } from '../../lib/teuthologyAPI'; import { useMutation } from "@tanstack/react-query"; - +import Editor from "react-simple-code-editor"; +import { highlight, languages } from "prismjs/components/prism-core"; +import Accordion from "@mui/material/Accordion"; +import AccordionSummary from "@mui/material/AccordionSummary"; +import AccordionDetails from "@mui/material/AccordionDetails"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; export default function Schedule() { const keyOptions = [ @@ -36,11 +41,18 @@ export default function Schedule() { "--suite-branch", "--suite", "--subset", - "--machine", + "--sha1", + "--email", + "--machine-type", "--filter", + "--filter-out", + "--filter-all", + "--kernal", + "--flavor", "--distro", - "--rerun", - "--rerun-statuses", + "--distro-version", + "--newest", + "--num", "--limit", "--priority", ]; @@ -59,28 +71,35 @@ export default function Schedule() { 2/ ... -1/ will schedule all \ jobs in the suite (many more than once). If specified, \ this value can be found in results.log.", - "--machine": "Machine type e.g., smithi, mira, gibba.", + "--sha1": "The ceph sha1 to run against (overrides -c) \ + If both -S and -c are supplied, -S wins, and \ + there is no validation that sha1 is contained \ + in branch", + "--email": "When tests finish or time out, send an email \ + here. May also be specified in ~/.teuthology.yaml \ + as 'results_email'", + "--machine-type": "Machine type e.g., smithi, mira, gibba.", "--filter": "Only run jobs whose description contains at least one \ of the keywords in the comma separated keyword string specified.", + "--filter-out": "Do not run jobs whose description contains any of \ + the keywords in the comma separated keyword \ + string specified.", + "--filter-all": "Only run jobs whose description contains each one \ + of the keywords in the comma separated keyword \ + string specified.", + "--kernal": "The kernel branch to run against, \ + use 'none' to bypass kernel task. \ + [default: distro]", + "--flavor": "The ceph packages shaman flavor to run with: \ + ('default', 'crimson', 'notcmalloc', 'jaeger') \ + [default: default]", "--distro": "Distribution to run against", - "--rerun": "Attempt to reschedule a run, selecting only those \ - jobs whose status are mentioned by --rerun-status. \ - Note that this is implemented by scheduling an \ - entirely new suite and including only jobs whose \ - descriptions match the selected ones. It does so \ - using the same logic as --filter. \ - Of all the flags that were passed when scheduling \ - the original run, the resulting one will only \ - inherit the --suite value. Any other arguments \ - must be passed again while scheduling. By default, \ - 'seed' and 'subset' will be taken from results.log, \ - but can be overide if passed again. \ - This is important for tests involving random facet \ - (path ends with '$' operator).", - "--rerun-statuses": "A comma-separated list of statuses to be used \ - with --rerun. Supported statuses are: 'dead', \ - 'fail', 'pass', 'queued', 'running', 'waiting' \ - [default: fail,dead]", + "--distro-version": "Distro version to run against", + "--newest": "Search for the newest revision built on all \ + required distro/versions, starting from \ + either --ceph or --sha1, backtracking \ + up to commits [default: 0]", + "--num": "Number of times to run/queue the job [default: 1]", "--limit": "Queue at most this many jobs [default: 0]", "--priority": "Job priority (lower is sooner) 0 - 1000", } @@ -88,17 +107,31 @@ export default function Schedule() { const [rowData, setRowData] = useLocalStorage("rowData", []); const [rowIndex, setRowIndex] = useLocalStorage("rowIndex", -1); const [commandBarValue, setCommandBarValue] = useState([]); - const userData = useUserData(); + const username = useUserData().get("username"); const [open, setOpenSuccess] = useState(false); + const [openWrn, setOpenWrn] = useState(false); const [openErr, setOpenErr] = useState(false); - const [ErrText, setErrText] = useState(""); + const [logText, setLogText] = useLocalStorage("logText", ""); + + const handleOpenSuccess = (data) => { + if (data && data.data) { + const code = data.data.logs.join(""); + setLogText(code); + } - const handleOpenSuccess = () => { - setOpenSuccess(true); + if (data.data.job_count < 1) { + setOpenWrn(true) + } else { + setOpenSuccess(true); + } }; - const handleOpenErr = (errText) => { - setErrText(errText); + const handleOpenErr = (data) => { + console.log("handleOpenErr"); + if (data && data.response.data.detail) { + const code = data.response.data.detail; + setLogText(code); + } setOpenErr(true); }; @@ -108,16 +141,19 @@ export default function Schedule() { const handleCloseErr = () => { setOpenErr(false); }; - + const handleCloseWrn = () => { + setOpenWrn(false); + }; const clickRun = useMutation({ mutationFn: async (commandValue) => { return await useSchedule(commandValue); }, - onSuccess: () => { - handleOpenSuccess(); + onSuccess: (data) => { + handleOpenSuccess(data); }, - onError: () => { - handleOpenErr("Failed to Schedule Run"); + onError: (err) => { + console.log(err); + handleOpenErr(err); } }) @@ -125,11 +161,11 @@ export default function Schedule() { mutationFn: async (commandValue) => { return await useSchedule(commandValue); }, - onSuccess: () => { - handleOpenSuccess(); + onSuccess: (data) => { + handleOpenSuccess(data); }, - onError: () => { - handleOpenErr("Failed to Schedule Dry Run"); + onError: (err) => { + handleOpenErr(err); } }) @@ -138,11 +174,11 @@ export default function Schedule() { commandValue['--force-priority'] = true; return await useSchedule(commandValue); }, - onSuccess: () => { - handleOpenSuccess(); + onSuccess: (data) => { + handleOpenSuccess(data); }, - onError: () => { - handleOpenErr("Failed to Schedule run with --force-priority"); + onError: (err) => { + handleOpenErr(err); } }) @@ -151,18 +187,18 @@ export default function Schedule() { }, [rowData]) function getCommandValue(dry_run) { + setLogText(""); let retCommandValue = {}; commandBarValue.map((data) => { if (data.checked) { retCommandValue[data.key] = data.value; } }) - let username = userData.get("username"); if (!username) { console.log("User is not logged in"); return {}; } else { - retCommandValue['--user'] = userData.get("username"); + retCommandValue['--user'] = username; } if (dry_run) { retCommandValue['--dry-run'] = true; @@ -231,6 +267,7 @@ export default function Schedule() { return (
+ {username ? <> : User is not logged in ... feature disabled.} Schedule - Pulpito @@ -253,23 +290,44 @@ export default function Schedule() { />
+ + + Schedule Success! + + + + + Schedule Failed! + + + + + Warning! 0 Jobs Scheduled + + - - - Run Scheduled! - - {clickRun.isLoading ? ( ) : } - - - {ErrText} - - {clickForcePriority.isLoading ? ( @@ -296,7 +344,7 @@ export default function Schedule() { style={{ height: "50px", width: "100px", marginLeft: "20px" }} variant="contained" color="error" - disabled={clickDryRun.isLoading || clickRun.isLoading} + disabled={clickDryRun.isLoading || clickRun.isLoading || !username} onClick={() => { clickForcePriority.mutate(getCommandValue(false)) }} @@ -306,12 +354,12 @@ export default function Schedule() { {clickDryRun.isLoading ? : (
); }