UNPKG

@maap-jupyterlab/dps-jupyter-extension

Version:

A JupyterLab extension for submitting and viewing jobs.

303 lines (302 loc) 16 kB
import React from 'react'; import { Button, ButtonToolbar, Form } from 'react-bootstrap'; import { AlertBox } from './Alerts'; import { useSelector, useDispatch } from 'react-redux'; import AsyncSelect from 'react-select/async'; import { useEffect, useState, useRef } from 'react'; import { selectCMRSwitch, CMRSwitchActions } from '../redux/slices/CMRSwitchSlice'; import { getAlgorithms, describeAlgorithms, getResources, getCMRCollections, submitJob, getUserJobs } from '../api/maap_py'; import { algorithmsActions, selectAlgorithms } from '../redux/slices/algorithmsSlice'; import { parseScienceKeywords } from '../utils/ogc_parsers'; import '../../style/JobSubmission.css'; import { Notification } from "@jupyterlab/apputils"; import { selectUserInfo } from '../redux/slices/userInfoSlice'; import { jobsActions } from '../redux/slices/jobsSlice'; import { parseJobData } from '../utils/mapping'; import { copyNotebookCommand } from '../utils/utils'; export const JobSubmissionForm = () => { // Redux const dispatch = useDispatch(); const { setAlgorithm, setResource, setAlgorithmMetadata, setCMRCollection } = algorithmsActions; const { setUserJobInfo, setJobRefreshTimestamp } = jobsActions; const { selectedAlgorithm, selectedResource, selectedAlgorithmMetadata, selectedCMRCollection } = useSelector(selectAlgorithms); const { username } = useSelector(selectUserInfo); const { toggleValue, toggleDisabled } = CMRSwitchActions; const { switchIsChecked, switchIsDisabled } = useSelector(selectCMRSwitch); // Local state variables const [jobTag, setJobTag] = useState(''); const [command, setCommand] = useState(''); const [showWaitCursor, setShowWaitCursor] = useState(false); const [disableButton, setDisableButton] = useState(false); const jobSubmitForm = useRef(null); useEffect(() => { if (selectedAlgorithm != null) { let res = describeAlgorithms(selectedAlgorithm["value"]); res.then((data) => { dispatch(setAlgorithmMetadata(data)); }); } }, [selectedAlgorithm]); useEffect(() => { if (command != '') { copyNotebookCommand(command); } }, [command]); useEffect(() => { let elems = document.getElementsByClassName("jl-ReactAppWidget"); console.log("graceal1 in the use effect of show wait cursor"); // Apply the css to the parent div if (showWaitCursor) { elems[0].classList.add('wait-cursor'); } else { elems[0].classList.remove('wait-cursor'); } }, [showWaitCursor]); useEffect(() => { console.log("graceal1 in use effect of disable button"); let elems = document.getElementsByClassName("btn btn-primary"); if (disableButton) { for (let i = 0; i < elems.length; i++) { console.log(elems[i]); console.log(elems[i].getAttribute("type")); if (elems[i].getAttribute("type") === "submit") { elems[i].setAttribute("disabled", "true"); console.log("graceal1 disabled button in useEffect"); console.log(elems[i]); } } } else { for (let i = 0; i < elems.length; i++) { console.warn("graceal1 in else "); if (elems[i].getAttribute("type") === "submit" && elems[i].hasAttribute("disabled")) { console.warn("graceal1 in if statement of the else statement"); elems[i].removeAttribute("disabled"); console.log("graceal1 enabled button in useEffect"); console.log(elems[i]); } } } console.log("graceal1 printing elems in use effect"); console.log(elems); console.log(disableButton); }, [disableButton]); const handleAlgorithmChange = value => { dispatch(setAlgorithm(value)); }; const handleResourceChange = value => { dispatch(setResource(value)); }; const handleCMRCollectionChange = value => { dispatch(setCMRCollection(value)); }; /*const handleButtonClickSubmit = (event) => { event.preventDefault(); setShowWaitCursor(true); setDisableButton(true); console.log("graceal1 in handle button click submit"); if (jobSubmitForm.current) { console.log("graceal1 jobSubmitForm.current true"); jobSubmitForm.current.submit(); // Programmatically submit the form } }*/ const onSubmit = (event) => { console.log("graceal1 at the beginning of onsubmit"); event.preventDefault(); var jobParams = { algo_id: null, version: null, queue: null, username: null, identifier: null }; if (selectedAlgorithm) { let algorithm = selectedAlgorithm.value.split(':'); jobParams.algo_id = algorithm[0]; jobParams.version = algorithm[1]; } console.log("graceal1 after selectedAlg if statement"); if (selectedResource) { jobParams.queue = selectedResource.value; } jobParams.username = username; jobParams.identifier = jobTag; let data = new FormData(event.target); for (const input of data.entries()) { jobParams[input[0]] = input[1]; } console.log("graceal1 right before form validation"); let formValidation = validateForm(jobParams); console.log("graceal1 right after form validation"); if (!formValidation) { // Submit job console.log("graceal1 in !formValidation if statement about to submit job with jobParams"); console.log(jobParams); submitJob(jobParams).then((data) => { console.log("graceal1 in the then of submitjob with "); console.log(data); //setShowWaitCursor(false) let msg = " Job submitted successfully. " + data['response']; Notification.success(msg, { autoClose: false }); }).catch(error => { Notification.error(error.message, { autoClose: false }); }); // Refresh job list once job has been submitted let response = getUserJobs(username); console.log("graceal1 and response is "); console.log(response); response.then((data) => { console.log("graceal1 in then of response"); console.log(data); dispatch(setUserJobInfo(parseJobData(data["response"]["jobs"]))); }).finally(() => { console.log("graceal1 in finally of response"); dispatch(setJobRefreshTimestamp(new Date().toUTCString())); }); } else { console.log("graceal1 in else of !formValidation"); Notification.error(formValidation, { autoClose: false }); } console.log("graceal1 at the very bottom of onsubmit"); setShowWaitCursor(false); setDisableButton(false); }; const validateForm = (params) => { let errorMsg = ""; if (!params.algo_id) { errorMsg = " Missing algorithm selection. Job failed to submit."; } else if (!params.identifier) { errorMsg = " Missing job tag. Job failed to submit."; } else if (!params.queue) { errorMsg = " Missing resource selection. Job failed to submit."; } return errorMsg; }; // Reset job form const clearForm = () => { dispatch(setAlgorithm(null)); dispatch(setResource(null)); dispatch(setAlgorithmMetadata(null)); setJobTag(''); if (!switchIsDisabled) { dispatch(toggleDisabled()); } if (switchIsChecked) { dispatch(toggleValue()); } }; // Build job submission jupyter notebook command from user-provided selections const buildNotebookCommand = () => { // maap.submitJob({"algo_id": "test_algo", "username": "anonymous", "queue": "geospec-job_worker-32gb"}) let jobParams = { algo_id: null, version: null, queue: null, username: null, identifier: null }; if (selectedAlgorithm) { let algorithm = selectedAlgorithm.value.split(':'); jobParams.algo_id = algorithm[0]; jobParams.version = algorithm[1]; } if (selectedResource) { jobParams.queue = selectedResource.value; } jobParams.username = username; jobParams.identifier = jobTag; let data = new FormData(jobSubmitForm.current); let inputStr = ""; for (const input of data.entries()) { jobParams[input[0]] = input[1]; if (inputStr == "") { inputStr = input[0] + "=" + "\"" + input[1] + "\""; } else { inputStr = inputStr + ",\n " + input[0] + "=" + "\"" + input[1] + "\""; } } let tmp = "maap.submitJob(identifier=\"" + jobParams.identifier + "\",\n " + "algo_id=\"" + jobParams.algo_id + "\",\n " + "version=\"" + jobParams.version + "\",\n " + "username=\"" + jobParams.username + "\",\n " + "queue=\"" + jobParams.queue + "\",\n " + inputStr + ")"; setCommand(tmp); }; console.log("graceal1 in the render of job submission with show wait cursor and disable button as "); console.log(showWaitCursor); return (React.createElement("div", { className: "submit-wrapper" }, React.createElement(Form, { onSubmit: onSubmit, ref: jobSubmitForm }, React.createElement("h5", null, "1. General Information"), React.createElement(Form.Group, { className: "mb-3 algorithm-input" }, React.createElement(Form.Label, null, "Algorithm"), React.createElement(AsyncSelect, { menuPortalTarget: document.querySelector('body'), cacheOptions: true, defaultOptions: true, value: selectedAlgorithm, loadOptions: getAlgorithms, onChange: handleAlgorithmChange, placeholder: "Select algorithm..." })), React.createElement(Form.Group, { className: "mb-3 algorithm-input" }, React.createElement(Form.Label, null, "Job Tag"), React.createElement(Form.Control, { type: "text", value: jobTag, placeholder: "Enter job tag...", onChange: event => setJobTag(event.target.value) })), React.createElement(Form.Group, { className: "mb-3 algorithm-input" }, React.createElement(Form.Label, null, "Resource"), React.createElement(AsyncSelect, { cacheOptions: true, defaultOptions: true, value: selectedResource, loadOptions: getResources, onChange: handleResourceChange, placeholder: "Select resource..." })), selectedAlgorithmMetadata ? // If user has selected an algorithm, render inputs and CMR info React.createElement(React.Fragment, null, React.createElement("h5", null, "2. Algorithm Inputs"), Array.isArray(selectedAlgorithmMetadata.inputs) ? selectedAlgorithmMetadata.inputs.map((item, index) => { switch (item["ows:Title"]) { case "publish_to_cmr": if (switchIsDisabled) { dispatch(toggleDisabled()); } return "publish to cmr"; case "queue_name": return ""; // already integrated above using the resource async select default: return React.createElement("div", { key: `${item["ows:Title"]}_${index}` }, React.createElement(Form.Group, { className: "algorithm-input" }, React.createElement(Form.Label, null, `${item["ows:Title"]}`), React.createElement(Form.Control // value={item["ows:Title"]} , { // value={item["ows:Title"]} name: item["ows:Title"], placeholder: `Enter ${item["ows:Title"]}...`, required: false }))); } }) : React.createElement(AlertBox, { text: "No inputs defined for selected algorithm.", variant: "primary" }), React.createElement("div", { className: "cmr" }, React.createElement("div", { style: { display: 'flex' } }, React.createElement("h5", null, "3. Publish to Content Metadata Repository (CMR)?"), React.createElement(Form.Group, null, React.createElement(Form.Switch, { custom: true, disabled: switchIsDisabled, type: "switch", checked: switchIsChecked, onChange: () => dispatch(toggleValue()) }))), switchIsChecked ? React.createElement(Form.Group, { className: "mb-3 algorithm-input" }, React.createElement(Form.Label, null, "CMR Collections"), React.createElement(AsyncSelect, { cacheOptions: true, defaultOptions: true, value: selectedCMRCollection, loadOptions: getCMRCollections, onChange: handleCMRCollectionChange, placeholder: "Select CMR collection..." })) : null, selectedCMRCollection && switchIsChecked ? React.createElement("table", { className: "cmr-table" }, React.createElement("tr", null, React.createElement("td", null, "Concept ID"), React.createElement("td", null, selectedCMRCollection["concept-id"])), React.createElement("tr", null, React.createElement("td", null, "Name"), React.createElement("td", null, selectedCMRCollection["ShortName"])), React.createElement("tr", null, React.createElement("td", null, "Description"), React.createElement("td", null, selectedCMRCollection["Description"])), React.createElement("tr", null, React.createElement("td", null, "Science Keywords"), parseScienceKeywords(selectedCMRCollection["ScienceKeywords"]).map((item) => item + ", "))) : null, switchIsDisabled ? React.createElement(AlertBox, { text: "CMR publication not available for selected algorithm.", variant: "primary" }) : null)) : null, React.createElement("hr", null), React.createElement(ButtonToolbar, null, React.createElement(Button, { type: "submit", onClick: () => { setShowWaitCursor(true); setDisableButton(true); } }, "Submit Job"), React.createElement(Button, { variant: "outline-secondary", onClick: clearForm }, "Clear"), React.createElement(Button, { variant: "outline-primary", style: { marginLeft: 'auto' }, onClick: buildNotebookCommand }, "Copy as Jupyter Notebook Code"))))); };