@maap-jupyterlab/dps-jupyter-extension
Version:
A JupyterLab extension for submitting and viewing jobs.
303 lines (302 loc) • 16 kB
JavaScript
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")))));
};