Skip to content
This repository has been archived by the owner on Apr 10, 2024. It is now read-only.

Commit

Permalink
2.3 release (#59)
Browse files Browse the repository at this point in the history
* source bashrc in entrypoint

* submit jobs request propagate returned  error messages

* get request response json

* catch nonzero exit subprocess on git status if not register code not in repo

* typo

* catch subprocess nonzero exit in defaultvalues handler

* report no git repo error to user

* don't convert inputs to lower case

* register algo config req params check for none

* register commit check for r and matlab file exts

* update architecture docs for submit jobs, add for dps info

* add toastify error on register responses

* toastify error for other submit jobs requests

* dependency

* copy bashrc to bash_profile in entrypoint

* add toastify error for default vals in register

* testing register result

* more register toastify errors

Co-authored-by: echyam <[email protected]>
  • Loading branch information
mayadebellis and echyam authored Jun 3, 2020
1 parent 9485be4 commit f85ed63
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 71 deletions.
15 changes: 15 additions & 0 deletions dps_info/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## DPS Info Extension Documentation & Architecture
### Architecture
<img alt="architecture diagram" src="dps_info_architecture.png" width="350">

#### 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
Binary file added dps_info/dps_info_architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 2 additions & 7 deletions submit_jobs/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
80 changes: 44 additions & 36 deletions submit_jobs/src/activate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
},
Expand Down
10 changes: 6 additions & 4 deletions submit_jobs/src/funcs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PageConfig } from '@jupyterlab/coreutils'
import { INotification } from 'jupyterlab_toastify';
import { request, RequestResult } from './request';
import { popupResultText } from './widgets';

Expand Down Expand Up @@ -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);
Expand All @@ -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']);
}
});
}
Expand Down
34 changes: 20 additions & 14 deletions submit_jobs/src/widgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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 = "<pre>" + result + "</pre>";
if ( isXML === undefined || (! isXML) ){
textarea.innerHTML = '<pre>' + result + '</pre>';
// console.log(textarea);
} else {
var xml = "<root><content><p>"+result+"</p></content></root>";
var xml = '<root><content><p>'+result+'</p></content></root>';
var options = {indentation: ' ', stripComments: true, collapseContent: false};
var formattedXML = format(xml,options);
textarea.innerHTML = formattedXML;
Expand All @@ -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 = "<pre>" + result + "</pre>";
textarea.innerHTML = '<pre>' + result + '</pre>';
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);
Expand Down
33 changes: 23 additions & 10 deletions submit_jobs/submit_jobs/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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
Expand Down
Binary file modified submit_jobs/submit_jobs_architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f85ed63

Please sign in to comment.