diff --git a/dps_info/documentation.md b/dps_info/documentation.md new file mode 100644 index 00000000..175b2e76 --- /dev/null +++ b/dps_info/documentation.md @@ -0,0 +1,15 @@ +## DPS Info Extension Documentation & Architecture +### Architecture +architecture diagram + +#### sourcefiles +- index.ts: instantiate & export jupyter extensions +- activate.ts: contains all extension activate functions +- panel.ts: contains skeleton code for a basic side panel in jupyter lab, wth a commented-out example activate function (extension must be instantiated and exported as well) +- jobinfo.ts: 1) creates a widget for listing DPS jobs associated with the user in table; 2) creates a main area jupyter widget for easy UI in listing, describing, and executing algorithms; (1) and (2) are NOT synchronized; the table in (1) is organized as: + +| Job Id | Status | Algorithm | +| ------ | ------ | --------- | + +- funcs.ts: common functions across classes & API calls in jobinfo.ts +- request.ts: class and functions utility for making http requests and reading their responses diff --git a/dps_info/dps_info_architecture.png b/dps_info/dps_info_architecture.png new file mode 100644 index 00000000..a31d99d5 Binary files /dev/null and b/dps_info/dps_info_architecture.png differ diff --git a/entrypoint.sh b/entrypoint.sh index a9527cf0..a076630a 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -27,5 +27,6 @@ env | grep _ >> /etc/environment # Add conda bin to path export PATH=$PATH:/opt/conda/bin +cp /root/.bashrc ~/.bash_profile jupyter lab --ip=0.0.0.0 --port=3100 --allow-root --NotebookApp.token='' --LabApp.base_url=$PREVIEW_URL --no-browser --debug diff --git a/submit_jobs/documentation.md b/submit_jobs/documentation.md index 7461f612..dc91e694 100644 --- a/submit_jobs/documentation.md +++ b/submit_jobs/documentation.md @@ -5,14 +5,9 @@ #### sourcefiles - index.ts: instantiate & export jupyter extensions - fields.json: required parameters for each DPS/MAS function that calls the MAAP API -- funcs.ts: common functions across classes & API calls; includes extension activate functions (except for side panels) +- activate.ts: contains all extension activate functions +- funcs.ts: common functions across classes & API calls - selector.ts: widget that creates a dropdown menu for selecting from a prepopulated list of choices - widgets.ts: contains common widgets for executing DPS/MAS cals to MAAP API; includes 2 dialog popup functions widgets depend on -- panel.ts: contains skeleton code for a basic side panel in jupyter lab, wth a commented-out example activate function (extension must be instantiated and exported as well) -- jobinfo.ts: 1) creates a widget for listing DPS jobs associated with the user in table; 2) creates a main area jupyter widget for easy UI in listing, describing, and executing algorithms; format in (1): - -| Job Id | Status | Algorithm | -| ------ | ------ | --------- | - - request.ts: class and functions utility for making http requests and reading their responses - dialog.ts: customize more dialog types & popup functions from the base class/function in @jupyterlab/apputils diff --git a/submit_jobs/src/activate.ts b/submit_jobs/src/activate.ts index 8ed2b4a7..2c2350cd 100644 --- a/submit_jobs/src/activate.ts +++ b/submit_jobs/src/activate.ts @@ -4,6 +4,7 @@ import { ILauncher } from '@jupyterlab/launcher'; import { IFileBrowserFactory } from "@jupyterlab/filebrowser"; import { IMainMenu } from '@jupyterlab/mainmenu'; import { Menu } from '@phosphor/widgets'; +import { INotification } from 'jupyterlab_toastify'; import { InputWidget, RegisterWidget, popupText } from './widgets'; import { ProjectSelector } from './selector'; import { popup, popupResult } from './dialogs'; @@ -68,47 +69,54 @@ export function activateRegisterAlgorithm( // send request to defaultvalueshandler let getValuesFn = function(resp:Object) { console.log('getValuesFn'); - let configPath = resp['config_path'] as string; - let defaultValues = resp['default_values'] as Object; - let prevConfig = resp['previous_config'] as boolean; + console.log(resp['status_code']); + if (resp['status_code'] != 200) { + // error + popupText(resp['result'],'Error Registering Algorithm'); + INotification.error(resp['result']); + } else { + let configPath = resp['config_path'] as string; + let defaultValues = resp['default_values'] as Object; + let prevConfig = resp['previous_config'] as boolean; - if (defaultValues['inputs'] == undefined) { - defaultValues['inputs'] = []; - } - if (defaultValues['description'] == undefined) { - defaultValues['description'] = ''; - } - - console.log(defaultValues); + if (defaultValues['inputs'] == undefined) { + defaultValues['inputs'] = []; + } + if (defaultValues['description'] == undefined) { + defaultValues['description'] = ''; + } - let subtext = 'Auto-generated algorithm configuration:'; - if (prevConfig) { - subtext = 'Current algorithm configuration:'; - } + console.log(defaultValues); - // register function to be called - // popup read-only default values - let registerfn = function() { - console.log('registerfn testing'); - let w = new RegisterWidget(registerFields,username,defaultValues,subtext,configPath); - w.setPredefinedFields(defaultValues); - console.log(w); - popup(w); - } + let subtext = 'Auto-generated algorithm configuration:'; + if (prevConfig) { + subtext = 'Current algorithm configuration:'; + } - // check if algorithm already exists - // ok -> call registeralgorithmhandler - // cancel -> edit template at algorithm_config.yaml (config_path) - algorithmExists(defaultValues['algo_name'],defaultValues['version'],defaultValues['environment']).then((algoExists) => { - console.log('algo Exists'); - console.log(algoExists); - if (algoExists != undefined && algoExists) { - popupText('WARNING Algorithm name and version already exists. \n If you continue, the previously registered algorithm \nwill be LOST','Overwrite Algorithm?',registerfn); - // ask user if they want to continue - } else { - registerfn() + // register function to be called + // popup read-only default values + let registerfn = function() { + console.log('registerfn testing'); + let w = new RegisterWidget(registerFields,username,defaultValues,subtext,configPath); + w.setPredefinedFields(defaultValues); + console.log(w); + popup(w); } - }); + + // check if algorithm already exists + // ok -> call registeralgorithmhandler + // cancel -> edit template at algorithm_config.yaml (config_path) + algorithmExists(defaultValues['algo_name'],defaultValues['version'],defaultValues['environment']).then((algoExists) => { + console.log('algo Exists'); + console.log(algoExists); + if (algoExists != undefined && algoExists) { + popupText('WARNING Algorithm name and version already exists. \n If you continue, the previously registered algorithm \nwill be LOST','Overwrite Algorithm?',registerfn); + // ask user if they want to continue + } else { + registerfn() + } + }); + } }; inputRequest('defaultValues','Register Algorithm',{'code_path':path},getValuesFn); }, diff --git a/submit_jobs/src/funcs.ts b/submit_jobs/src/funcs.ts index 5087fc62..03fd1bbe 100644 --- a/submit_jobs/src/funcs.ts +++ b/submit_jobs/src/funcs.ts @@ -1,4 +1,5 @@ import { PageConfig } from '@jupyterlab/coreutils' +import { INotification } from 'jupyterlab_toastify'; import { request, RequestResult } from './request'; import { popupResultText } from './widgets'; @@ -49,10 +50,8 @@ export function inputRequest(endpt:string,title:string,inputs:{[k:string]:string // add params for (let key in inputs) { var fieldValue = inputs[key]; - - if(key !== 'proxy-ticket') - fieldValue = fieldValue.toLowerCase(); - + // if(key !== 'proxy-ticket') + // fieldValue = fieldValue.toLowerCase(); requestUrl.searchParams.append(key.toLowerCase(), fieldValue); } console.log(requestUrl.href); @@ -70,6 +69,9 @@ export function inputRequest(endpt:string,title:string,inputs:{[k:string]:string console.log('fn defined'); fn(json_response); } + } else { + var json_response:any = res.json(); + INotification.error(json_response['result']); } }); } diff --git a/submit_jobs/src/widgets.ts b/submit_jobs/src/widgets.ts index ccbe0545..b933e70b 100644 --- a/submit_jobs/src/widgets.ts +++ b/submit_jobs/src/widgets.ts @@ -337,8 +337,14 @@ export class InputWidget extends Widget { let json_response:any = res.json(); // console.log(json_response); me._responseText = me._responseText + '\n' + json_response['result']; + if (json_response['status_code'] != 200) { + INotification.error(me._responseText); + } } else { - me._responseText = "Error Sending Request."; + let json_response:any = res.json(); + // console.log(json_response); + me._responseText = "Error Sending Request:\n" + json_response['result']; + INotification.error(me._responseText); } console.log("updating"); me.updateSearchResults(); @@ -382,13 +388,13 @@ export class RegisterWidget extends InputWidget { subtxt.style.flexDirection = 'column'; subtxt.innerHTML = subtext; this.node.appendChild(subtxt); - this.node.appendChild(document.createElement("BR")); + this.node.appendChild(document.createElement('BR')); } for (var field of this.fields) { // textarea for inputs field in register - if (field == "inputs") { - var fieldLabel = document.createElement("Label"); + if (field == 'inputs') { + var fieldLabel = document.createElement('Label'); fieldLabel.innerHTML = field; this.node.appendChild(fieldLabel); @@ -413,7 +419,7 @@ export class RegisterWidget extends InputWidget { this.node.appendChild(fieldInputs); } else { - var fieldLabel = document.createElement("Label"); + var fieldLabel = document.createElement('Label'); fieldLabel.innerHTML = field; this.node.appendChild(fieldLabel); @@ -429,7 +435,7 @@ export class RegisterWidget extends InputWidget { } // BREAK - var x = document.createElement("BR"); + var x = document.createElement('BR'); this.node.appendChild(x) // footer text - edit config at path @@ -496,7 +502,7 @@ export class WidgetResult extends Widget { // update panel text on resolution of result popup getValue() { console.log('checking popup resolution fn'); - if (this.okfn != undefined) { + if (typeof this.okfn === "function") { console.log(this.okfn); try{ this.okfn(); @@ -515,18 +521,18 @@ export function popupResultText(result:string,title:string,fn?:any,isXML?:boolea body.style.display = 'flex'; body.style.flexDirection = 'column'; - var textarea = document.createElement("div"); + var textarea = document.createElement('div'); textarea.id = 'result-text'; textarea.style.display = 'flex'; textarea.style.flexDirection = 'column'; var format = require('xml-formatter'); // console.log(result); - if ( isXML == undefined || (! isXML) ){ - textarea.innerHTML = "
" + result + "
"; + if ( isXML === undefined || (! isXML) ){ + textarea.innerHTML = '
' + result + '
'; // console.log(textarea); } else { - var xml = "

"+result+"

"; + var xml = '

'+result+'

'; var options = {indentation: ' ', stripComments: true, collapseContent: false}; var formattedXML = format(xml,options); textarea.innerHTML = formattedXML; @@ -543,16 +549,16 @@ export function popupText(result:string,title:string,fn?:any) { body.style.display = 'flex'; body.style.flexDirection = 'column'; - var textarea = document.createElement("div"); + var textarea = document.createElement('div'); textarea.id = 'result-text'; textarea.style.display = 'flex'; textarea.style.flexDirection = 'column'; // console.log(result); - textarea.innerHTML = "
" + result + "
"; + textarea.innerHTML = '
' + result + '
'; body.appendChild(textarea); // console.log(body); - if (fn == undefined) { + if (fn === undefined) { popupTitle(new Widget({node:body}),title); } else { popupTitle(new WidgetResult(body,fn),title); diff --git a/submit_jobs/submit_jobs/handlers.py b/submit_jobs/submit_jobs/handlers.py index 9d948e0c..df28fab8 100644 --- a/submit_jobs/submit_jobs/handlers.py +++ b/submit_jobs/submit_jobs/handlers.py @@ -123,7 +123,7 @@ def get(self,**params): # only description and inputs are allowed to be empty for f in ['algo_name','version','environment','run_command','repository_url','docker_url']: - if config[f] == '': + if config[f] == '' or config[f] == None: self.finish({"status_code": 412, "result": "Error: Register field {} cannot be empty".format(f)}) return @@ -151,19 +151,21 @@ def get(self,**params): os.chdir(proj_path) # get git status - git_status_out = subprocess.check_output("git status --branch --porcelain", shell=True).decode("utf-8") - logger.debug(git_status_out) + try: + git_status_out = subprocess.check_output("git status --branch --porcelain", shell=True).decode("utf-8") + logger.debug(git_status_out) # is there a git repo? - if 'not a git repository' in git_status_out: - self.finish({"status_code": 412, "result": "Error: \n{}".format(git_status_out)}) + except: + # subprocess could also error out (nonzero exit code) + self.finish({"status_code": 412, "result": "Error: \nThe code you want to register is not saved in a git repository."}) return git_status = git_status_out.splitlines()[1:] git_status = [e.strip() for e in git_status] - # filter for unsaved python files - unsaved = list(filter(lambda e: ( (e.split('.')[-1] in ['ipynb','py','sh','jl']) and (e[0] in ['M','?']) ), git_status)) + # filter for unsaved python, julia, matlab shell files + unsaved = list(filter(lambda e: ( (e.split('.')[-1] in ['ipynb','py','sh','jl','r','m','mat']) and (e[0] in ['M','?']) ), git_status)) if len(unsaved) != 0: self.finish({"status_code": 412, "result": "Error: Notebook(s) and/or script(s) have not been committed\n{}".format('\n'.join(unsaved))}) return @@ -1221,8 +1223,19 @@ def get(self): proj_path = '/projects/'+params['code_path'] proj_path = '/'.join(proj_path.split('/')[:-1]) os.chdir(proj_path) - repo_url = subprocess.check_output("git remote get-url origin", shell=True).decode('utf-8').strip() - # logger.debug(repo_url) + + # try to get git remote url + try: + repo_url = subprocess.check_output("git remote get-url origin", shell=True).decode('utf-8').strip() + logger.debug(repo_url) + print('reop url is {}'.format(repo_url)) + + # is there a git repo? + except: + # subprocess could also error out (nonzero exit code) + self.finish({"status_code": 412, "result": "Error: \nThe code you want to register is not saved in a git repository."}) + return + vals = {} code_path = params['code_path'] @@ -1265,7 +1278,7 @@ def get(self): logger.debug(settings) # outputs: algo_name, version, environment, repository_url, dockerfile_path - self.finish({"status_code": 200, "default_values":settings, "config_path":config_path, "previous_config":prev_config}) + self.finish({"status_code": 200, "result": "Got default values.", "default_values":settings, "config_path":config_path, "previous_config":prev_config}) class ListJobsHandler(IPythonHandler): # inputs: username diff --git a/submit_jobs/submit_jobs_architecture.png b/submit_jobs/submit_jobs_architecture.png index 9f73b578..94ab1f7d 100644 Binary files a/submit_jobs/submit_jobs_architecture.png and b/submit_jobs/submit_jobs_architecture.png differ