2022-03-25 15:39:39 +01:00
class CCPExecutionForm extends HTMLElement {
# boot ;
# rootdoc ;
# data ;
# method ;
2022-05-05 12:19:06 +02:00
# executionmonitor ;
2022-03-25 15:39:39 +01:00
2022-07-18 18:08:53 +02:00
# serviceurl ;
2022-03-25 15:39:39 +01:00
constructor ( ) {
super ( )
this . # boot = document . querySelector ( "d4s-boot-2" )
this . # rootdoc = this . attachShadow ( { "mode" : "open" } )
2024-04-11 17:25:41 +02:00
}
connectedCallback ( ) {
this . # serviceurl = this . getAttribute ( "serviceurl" )
2023-03-17 10:49:21 +01:00
this . connectNewExecutionRequest ( )
2024-02-02 17:45:04 +01:00
this . render ( )
2024-02-02 17:32:51 +01:00
const params = new URLSearchParams ( window . location . search )
2024-02-02 17:45:04 +01:00
if ( params . get ( 'execution' ) ) {
const execution = { id : params . get ( 'execution' ) }
this . prepareFromExecution ( execution )
} else if ( params . get ( 'method' ) ) {
2024-02-02 17:35:59 +01:00
this . # method = params . get ( 'method' )
this . loadMethod ( )
2024-02-02 17:45:04 +01:00
} else {
this . showMethod ( )
2024-02-02 17:35:59 +01:00
}
2022-03-25 15:39:39 +01:00
}
2024-04-11 17:25:41 +02:00
2022-03-25 15:39:39 +01:00
static get observedAttributes ( ) {
2022-05-05 12:19:06 +02:00
return [ "method" ] ;
2022-03-25 15:39:39 +01:00
}
attributeChangedCallback ( name , oldValue , newValue ) {
2023-06-15 10:36:25 +02:00
//if((oldValue != newValue) && (name === "method")){
if ( name === "method" ) {
2022-03-25 15:39:39 +01:00
this . # method = newValue
this . loadMethod ( )
}
}
2023-03-17 10:49:21 +01:00
connectNewExecutionRequest ( ) {
document . addEventListener ( "newexecutionrequest" , ev => {
2023-05-23 19:55:04 +02:00
if ( window . confirm ( "Please confirm overwrite of execution form?" ) ) {
2023-03-17 11:02:25 +01:00
this . setAttribute ( "method" , ev . detail )
this . parentElement . scrollIntoViewIfNeeded ( )
2023-03-17 10:49:21 +01:00
}
} )
}
2022-07-18 18:08:53 +02:00
render ( ) {
this . # rootdoc . innerHTML = `
< div >
2024-04-16 13:07:23 +02:00
< link rel = "canonical" href = "https://getbootstrap.com/docs/5.0/components/modal/" >
2022-07-18 18:08:53 +02:00
< link rel = "stylesheet" href = "https://cdn.dev.d4science.org/ccp/css/common.css" > < / l i n k >
< link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity = "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin = "anonymous" >
< style >
2023-03-20 11:07:42 +01:00
. ccp - execution - form {
position : relative ;
}
2022-07-18 18:08:53 +02:00
< / s t y l e >
< template id = "EXECUTION_FORM_TEMPLATE" >
< div class = "ccp-execution-form" name = "execution_form" >
< h5 class = "ccp-method-title" > < / h 5 >
< p class = "description font-italic font-weight-lighter" > < / p >
2023-03-20 11:07:42 +01:00
< div class = "plexiglass d-none" > < / d i v >
2022-07-18 18:08:53 +02:00
< form name = "execution_form" class = "d-flex flex-column gap-3" style = "gap:5px" >
< div class = "card" >
< div class = "card-header" >
< h5 > Inputs < / h 5 >
< / d i v >
< div class = "card-body ccp-inputs" >
2023-09-26 17:14:47 +02:00
< div >
2022-07-18 18:08:53 +02:00
< div class = "form-group" > < / d i v >
< / d i v >
< / d i v >
< / d i v >
2024-02-09 11:42:49 +01:00
< div class = "card" >
< div class = "card-header" >
< h5 > Options < / h 5 >
< / d i v >
< div class = "card-body" >
< div class = "col form-check" >
< input class = "form-check-input" type = "checkbox" name = "auto-archive-outputs" alt = "Automatically archive outputs to workspace" title = "Automatically archive outputs to workspace" checked = "checked" >
< label class = "form-check-label" > Automatically archive outputs to workspace < / l a b e l >
< / d i v >
< / d i v >
< / d i v >
2022-07-18 18:08:53 +02:00
< div class = "card" >
< div class = "card-header" >
< h5 > Outputs < / h 5 >
< / d i v >
< div class = "card-body" >
< div class = "form-row ccp-outputs" >
< div class = "col form-group" > < / d i v >
< / d i v >
< / d i v >
< / d i v >
2023-04-14 11:51:08 +02:00
< div class = "form-row" >
< div class = "col-6" >
2023-10-06 16:19:37 +02:00
< button id = "execute_method_button" class = "btn btn-info" > Execute < / b u t t o n >
2023-03-20 11:07:42 +01:00
< / d i v >
2023-04-14 11:51:08 +02:00
< div class = "col-6" >
2024-02-02 18:33:42 +01:00
< div class = "mb-3" >
2023-06-07 17:13:52 +02:00
< label > Generate code for : < / l a b e l >
2023-06-07 17:24:07 +02:00
< div class = "d-flex" >
2024-02-02 18:12:35 +01:00
< select name = "language-selector" class = "form-control" style = "padding:2px" >
< option value = "text/python" data - ext = "py" title = "Generate plain Python3" > Python 3 < / o p t i o n >
< option value = "text/plain+r" data - ext = "r" title = "Generate plain R" > R < / o p t i o n >
< option value = "application/vnd.jupyter+python" data - ext = "ipynb" title = "Generate Jupyter notebook with Python 3 cells" > Jupyter Python3 < / o p t i o n >
< option value = "text/julia" data - ext = "jl" title = "Generate Julia 1.9.0 code (HTTP 1.10.0, JSON3 1.13.2)" > Julia 1.9 . 0 < / o p t i o n >
2024-04-10 12:28:47 +02:00
< option value = "application/x-sh+curl" data - ext = "sh" title = "Generate Bash script (curl)" > Bash ( curl ) < / o p t i o n >
< option value = "application/x-sh+wget" data - ext = "sh" title = "Generate Bash script (wget)" > Bash ( wget ) < / o p t i o n >
< / s e l e c t >
2024-02-02 18:12:35 +01:00
< button name = "codegen" title = "Generate code" class = "btn btn-primary ccp-toolbar-button ccp-toolbar-button-small" >
< svg viewBox = "0 96 960 960" >
< path d = "M320 814 80 574l242-242 43 43-199 199 197 197-43 43Zm318 2-43-43 199-199-197-197 43-43 240 240-242 242Z" > < / p a t h >
< / s v g >
< / b u t t o n >
< / d i v >
2024-02-02 18:33:42 +01:00
< / d i v >
< div >
< div class = "d-flex" >
2024-04-09 13:16:51 +02:00
< a name = "direct_link_method" class = "text-truncate" href = "${window.location.href}" > Direct link < / a >
2023-06-07 17:24:07 +02:00
< / d i v >
2023-03-20 11:07:42 +01:00
< / d i v >
< / d i v >
2023-04-14 11:51:08 +02:00
< / d i v >
2022-07-18 18:08:53 +02:00
< / f o r m >
< / d i v >
< / t e m p l a t e >
< template id = "EXECUTION_FORM_EMPTY_TEMPLATE" >
< div name = "execution_form" >
2022-07-19 09:54:40 +02:00
< i style = "padding:3rem" > Drop a method here ! < / i >
2022-07-18 18:08:53 +02:00
< / d i v >
< / t e m p l a t e >
< div name = "execution_form" > < / d i v >
< / d i v >
`
2022-03-25 15:39:39 +01:00
}
2023-03-20 11:07:42 +01:00
lockRender ( ) {
2023-05-08 11:10:33 +02:00
const plexi = this . # rootdoc . querySelector ( ".plexiglass" )
plexi . innerHTML = ` `
plexi . classList . toggle ( "d-none" )
2023-03-20 11:07:42 +01:00
}
unlockRender ( ) {
this . # rootdoc . querySelector ( ".plexiglass" ) . classList . toggle ( "d-none" )
}
2023-05-08 11:10:33 +02:00
writeToPlexi ( message ) {
const plexi = this . # rootdoc . querySelector ( ".plexiglass" )
plexi . innerHTML = `
< div class = "d-flex" style = "flex-direction: column;justify-content:center;position:relative;height:100%;align-items: center;" >
< div class = "mx-2 p-4 bg-success text-white border" style = "border-radius: 5px;box-shadow: 2px 2px 2px darkgray;" >
< div > $ { message } < / h 5 > < / d i v >
< / d i v >
< / d i v >
`
}
2022-03-25 15:39:39 +01:00
loadMethod ( ) {
2023-02-02 12:30:38 +01:00
return this . # boot . secureFetch ( this . # serviceurl + "/processes/" + this . # method ) . then (
2022-03-25 15:39:39 +01:00
( resp ) => {
2022-04-12 19:51:38 +02:00
if ( resp . status === 200 ) {
return resp . json ( )
} else throw "Error retrieving process"
}
) . then ( data => {
this . # data = data
2023-03-07 18:51:56 +01:00
const infra =
this . # data . links . filter ( l => l . rel === "compatibleWith" ) [ 0 ]
return this . # boot . secureFetch ( this . # serviceurl + "/" + infra . href )
2022-05-05 12:19:06 +02:00
} ) . then ( resp => {
this . # data . executable = resp . status === 200
} ) . then ( ( ) => {
2022-04-12 19:51:38 +02:00
this . showMethod ( )
2023-03-20 11:10:23 +01:00
} ) . catch ( err => alert ( err ) )
2022-03-25 15:39:39 +01:00
}
showEmpty ( resp ) {
BSS . apply ( this . # empty _executionform _bss , this . # rootdoc )
}
showMethod ( ) {
if ( this . # method == null ) this . showEmpty ( ) ;
else {
BSS . apply ( this . # executionform _bss , this . # rootdoc )
}
}
2022-04-12 19:51:38 +02:00
sendExecutionRequest ( ) {
2023-03-20 11:07:42 +01:00
this . lockRender ( )
2022-04-12 19:51:38 +02:00
const url = this . # serviceurl + "/processes/" + this . # method + "/execution"
2022-05-05 12:19:06 +02:00
const req = this . buildRequest ( )
2022-04-12 19:51:38 +02:00
this . # boot . secureFetch (
2022-05-05 12:19:06 +02:00
url , { method : "POST" , body : JSON . stringify ( req ) , headers : { "Content-Type" : "application/json" } }
2022-04-12 19:51:38 +02:00
) . then ( reply => {
if ( reply . status !== 200 && reply . status !== 201 ) {
throw "Error while requesting resource"
}
2022-05-05 12:19:06 +02:00
return reply . json ( )
} ) . then ( data => {
if ( data . status !== "accepted" ) {
throw "Execution has not been accepted by server"
}
2023-05-05 18:39:15 +02:00
const event = new CustomEvent ( 'newexecution' , { detail : data . jobID } ) ;
2023-01-26 11:31:08 +01:00
document . dispatchEvent ( event )
2023-05-08 11:10:33 +02:00
this . writeToPlexi ( "Execution request accepted with id: " + data . jobID )
window . setTimeout ( ( ) => this . unlockRender ( ) , 3000 )
2023-03-20 11:07:42 +01:00
} ) . catch ( err => { alert ( "Unable to call execute: " + err ) ; this . unlockRender ( ) } )
2022-05-05 12:19:06 +02:00
}
2023-04-14 11:51:08 +02:00
generateCode ( mime , filename ) {
const url = this . # serviceurl + "/methods/" + this . # method + "/code"
const req = this . buildRequest ( )
this . # boot . secureFetch (
url , { method : "POST" , body : JSON . stringify ( req ) , headers : { "Content-Type" : "application/json" , "Accept" : mime } }
) . then ( reply => {
if ( reply . status !== 200 ) throw "Error while requesting code:" ;
return reply . blob ( )
} ) . then ( blob => {
const objectURL = URL . createObjectURL ( blob )
var tmplnk = document . createElement ( "a" )
tmplnk . download = filename
tmplnk . href = objectURL
document . body . appendChild ( tmplnk )
tmplnk . click ( )
document . body . removeChild ( tmplnk )
} ) . catch ( err => { alert ( err ) } )
}
2022-05-05 12:19:06 +02:00
buildRequest ( ) {
let request = { inputs : { } , outputs : { } , response : "raw" }
//fill inputs
const inputs = this . getInputs ( )
inputs . forEach ( i => {
request . inputs [ i . name ] = i . value
} )
//fill outputs
const outputs = this . getOutputs ( )
outputs . forEach ( o => {
if ( o . enabled ) request . outputs [ o . name ] = { transmissionMode : "value" } ;
} )
2024-02-09 11:42:49 +01:00
const autoarchiveoption = this . # rootdoc . querySelector ( "input[name='auto-archive-outputs']" )
if ( autoarchiveoption . checked ) {
request . subscribers = [
{ successUri : "http://registry:8080/executions/archive-to-folder" }
]
}
2022-05-05 12:19:06 +02:00
return request
}
getInputs ( ) {
return Array . prototype . slice . call ( this . # rootdoc . querySelectorAll ( "d4s-ccp-input" ) )
}
getOutputs ( ) {
return Array . prototype . slice . call ( this . # rootdoc . querySelectorAll ( "d4s-ccp-output" ) )
2022-04-12 19:51:38 +02:00
}
2024-02-09 11:57:23 +01:00
initInputValues ( inputs ) {
2023-02-02 12:30:38 +01:00
Object . keys ( inputs ) . forEach ( k => {
const w = this . # rootdoc . querySelector ( ` d4s-ccp-input[name= ${ k } ] ` )
if ( w ) {
w . value = ( inputs [ k ] )
}
} )
}
2024-02-09 11:57:23 +01:00
initOptionValues ( request ) {
const autoarchiveoption = this . # rootdoc . querySelector ( "input[name='auto-archive-outputs']" )
2024-02-09 11:59:40 +01:00
autoarchiveoption . checked = request . subscribers && request . subscribers . filter ( s => s . successUri === "http://registry:8080/executions/archive-to-folder" ) . length === 1
2024-02-09 11:57:23 +01:00
}
2023-02-02 12:30:38 +01:00
prepareFromExecution ( exec ) {
let f1 =
this . # boot . secureFetch ( this . # serviceurl + ` /executions/ ${ exec . id } /metadata/method.json ` )
. then ( resp => {
if ( resp . status === 200 ) {
return resp . json ( )
} else throw "Error retrieving process"
}
) . then ( data => data )
let f2 =
this . # boot . secureFetch ( this . # serviceurl + ` /executions/ ${ exec . id } /metadata/request.json ` )
. then ( resp => {
if ( resp . status === 200 ) {
return resp . json ( )
} else throw "Error retrieving process"
}
) . then ( data => data )
var requestdata = null
Promise . all ( [ f1 , f2 ] ) . then ( m _and _r => {
this . # data = m _and _r [ 0 ]
requestdata = m _and _r [ 1 ]
this . # method = this . # data . id
2023-03-07 18:51:56 +01:00
const infra =
this . # data . links . filter ( l => l . rel === "compatibleWith" ) [ 0 ]
return this . # boot . secureFetch ( this . # serviceurl + "/" + infra . href )
2023-02-02 12:30:38 +01:00
} ) . then ( resp => {
this . # data . executable = resp . status === 200
} ) . then ( ( ) => {
this . showMethod ( )
2024-02-09 11:57:23 +01:00
this . initInputValues ( requestdata . inputs )
this . initOptionValues ( requestdata )
2023-02-02 12:30:38 +01:00
} ) . catch ( err => alert ( err ) )
}
2022-03-25 15:39:39 +01:00
# empty _executionform _bss = {
template : "#EXECUTION_FORM_EMPTY_TEMPLATE" ,
2022-03-28 16:42:02 +02:00
target : "div[name=execution_form]" ,
2022-03-25 15:39:39 +01:00
on _drop : ev => {
2023-02-02 12:30:38 +01:00
if ( ev . dataTransfer ) {
if ( ev . dataTransfer . getData ( 'text/plain+ccpmethod' ) ) {
const id = ev . dataTransfer . getData ( 'text/plain+ccpmethod' )
this . setAttribute ( "method" , id ) ;
ev . preventDefault ( )
ev . stopPropagation ( )
ev . currentTarget . style . backgroundColor = "white"
} else if ( ev . dataTransfer . getData ( 'text/plain+ccpexecution' ) ) {
this . prepareFromExecution ( JSON . parse ( ev . dataTransfer . getData ( 'application/json+ccpexecution' ) ) )
ev . preventDefault ( )
ev . stopPropagation ( )
ev . currentTarget . style . backgroundColor = "white"
}
2022-03-25 15:39:39 +01:00
}
} ,
on _dragover : ev => {
ev . preventDefault ( )
} ,
}
# executionform _bss = {
template : "#EXECUTION_FORM_TEMPLATE" ,
2022-03-28 16:52:09 +02:00
target : "div[name=execution_form]" ,
2022-03-25 15:39:39 +01:00
in : ( ) => this . # data ,
on _drop : ev => {
2023-02-02 12:30:38 +01:00
if ( ev . dataTransfer ) {
if ( ev . dataTransfer . getData ( 'text/plain+ccpmethod' ) ) {
const id = ev . dataTransfer . getData ( 'text/plain+ccpmethod' ) ;
this . setAttribute ( "method" , id ) ;
ev . preventDefault ( )
ev . stopPropagation ( )
} else if ( ev . dataTransfer . getData ( 'text/plain+ccpexecution' ) ) {
this . prepareFromExecution ( JSON . parse ( ev . dataTransfer . getData ( 'application/json+ccpexecution' ) ) )
ev . preventDefault ( )
ev . stopPropagation ( )
ev . currentTarget . style . backgroundColor = "white"
}
2022-03-25 15:39:39 +01:00
}
} ,
on _dragover : ev => {
ev . preventDefault ( )
} ,
recurse : [
{
2022-05-05 16:51:47 +02:00
target : ".ccp-method-title" ,
2023-04-03 12:51:08 +02:00
apply : ( e , d ) => e . innerHTML = `
$ { d . title } < span class = "badge badge-primary ml-1" > $ { d . version } < / s p a n >
2023-04-03 13:08:56 +02:00
$ { d . metadata . filter ( md => md . role === 'author' ) . map ( a => ` <span class="badge badge-warning ml-1"> ${ a . title } </span> ` ) }
2023-04-03 12:51:08 +02:00
`
2022-03-25 15:39:39 +01:00
} ,
{
target : "p.description" ,
apply : ( e , d ) => e . textContent = d . description
} ,
{
target : "div.ccp-inputs" ,
in : ( e , d ) => d ,
recurse : [
{
"in" : ( e , d ) => { return Object . values ( d . inputs ) } ,
target : "div" ,
apply : ( e , d ) => {
2023-12-13 18:29:44 +01:00
e . innerHTML = ` <d4s-ccp-input name=" ${ d . id } " input=' ${ btoa ( JSON . stringify ( d ) ) } '></d4s-ccp-input> `
2022-03-25 15:39:39 +01:00
}
}
]
2022-04-12 19:51:38 +02:00
} ,
{
target : "div.ccp-outputs" ,
in : ( e , d ) => d ,
recurse : [
{
"in" : ( e , d ) => { return Object . values ( d . outputs ) } ,
target : "div" ,
apply : ( e , d ) => {
2023-12-13 18:29:44 +01:00
e . innerHTML = ` <d4s-ccp-output name=" ${ d . id } " output=' ${ btoa ( JSON . stringify ( d ) ) } '></d4s-ccp-output> `
2022-04-12 19:51:38 +02:00
}
}
]
} ,
{
target : "div.ccp-runtimes *[name=refresh-runtimes]" ,
on _click : ev => {
BSS . apply ( this . # executionform _bss )
}
} ,
{
target : "#execute_method_button" ,
on _click : ev => {
ev . preventDefault ( )
ev . stopPropagation ( )
2022-05-05 12:19:06 +02:00
if ( this . # data . executable ) this . sendExecutionRequest ( ) ;
else alert ( "This method has no compatible runtimes available" )
2022-04-12 19:51:38 +02:00
return false ;
}
} ,
2023-04-14 11:51:08 +02:00
{
target : "button[name=codegen]" ,
on _click : ev => {
ev . preventDefault ( )
ev . stopPropagation ( )
const langsel = ev . target . parentElement . querySelector ( "select[name=language-selector]" )
const lang = langsel . value
const ext = langsel . selectedOptions [ 0 ] . getAttribute ( "data-ext" )
this . generateCode ( lang , ` ccprequest. ${ ext } ` )
return false
}
} ,
2024-02-02 18:07:28 +01:00
{
target : "a[name=direct_link_method]" ,
2024-04-09 13:22:14 +02:00
apply : ( e , d ) => e . href = window . location . origin + window . location . pathname + "?method=" + d . id
2024-02-02 18:07:28 +01:00
}
2022-03-25 15:39:39 +01:00
]
}
}
window . customElements . define ( 'd4s-ccp-executionform' , CCPExecutionForm ) ;