From 035d71d077d12153880a1a609b219501b6cf4ae7 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Wed, 3 Aug 2016 12:37:10 -0500 Subject: [PATCH] Added execution canceling. Fixes #481 WIP #481 Added buttons and jobId, secret setting WIP #481 Added 'jobId', 'secret' to Job WIP #481 Canceling job exec support WIP #481 Added canceling executing pipelines WIP #481 Fixed canceling pipelines WIP #481 Improved result messages from executions WIP #481 Updated decorator and status setting for ExecJob WIP #481 Updated job colors in lists WIP #481 Updated pipeline library WIP #481 Fixed code climate issues --- src/common/styles/global.css | 4 ++ src/common/viz/Utils.js | 7 ++ .../EasyDAG/JobDecorator.EasyDAGWidget.js | 7 +- src/plugins/ExecuteJob/ExecuteJob.js | 61 +++++++++++++++--- .../ExecutePipeline/ExecutePipeline.js | 31 +++++++-- src/seeds/cifar10/cifar10.webgmex | Bin 1376738 -> 1377069 bytes src/seeds/pipeline/pipeline.webgmex | Bin 90691 -> 91022 bytes src/seeds/project/project.webgmex | Bin 1278839 -> 1279170 bytes src/seeds/xor/xor.webgmex | Bin 1321670 -> 1322001 bytes .../panels/ForgeActionButton/Actions.js | 36 ++++++++++- .../ForgeActionButton/ForgeActionButton.js | 49 ++++++++++++-- .../ExecutionIndex/ExecutionIndexWidget.js | 16 ++--- .../PipelineEditor/PipelineEditorWidget.js | 9 +-- 13 files changed, 177 insertions(+), 43 deletions(-) diff --git a/src/common/styles/global.css b/src/common/styles/global.css index ee301f9e6..68ec7df1b 100644 --- a/src/common/styles/global.css +++ b/src/common/styles/global.css @@ -22,3 +22,7 @@ .create-node text { font-style: italic; } + +.job-canceled { + background-color: #ffe0b2; +} diff --git a/src/common/viz/Utils.js b/src/common/viz/Utils.js index 8da6cf0d2..1d11fd08f 100644 --- a/src/common/viz/Utils.js +++ b/src/common/viz/Utils.js @@ -8,5 +8,12 @@ define({ date = `Today (${new Date(timestamp).toLocaleTimeString()})`; } return date; + }, + ClassForJobStatus: { + success: 'success', + canceled: 'job-canceled', + failed: 'danger', + pending: '', + running: 'warning' } }); diff --git a/src/decorators/JobDecorator/EasyDAG/JobDecorator.EasyDAGWidget.js b/src/decorators/JobDecorator/EasyDAG/JobDecorator.EasyDAGWidget.js index d3b72adde..b1cae6320 100644 --- a/src/decorators/JobDecorator/EasyDAG/JobDecorator.EasyDAGWidget.js +++ b/src/decorators/JobDecorator/EasyDAG/JobDecorator.EasyDAGWidget.js @@ -1,10 +1,6 @@ /*globals define, _*/ /*jshint browser: true, camelcase: false*/ -/** - * @author brollb / https://github.com/brollb - */ - define([ 'decorators/EllipseDecorator/EasyDAG/EllipseDecorator.EasyDAGWidget', 'css!./JobDecorator.EasyDAGWidget.css' @@ -20,6 +16,7 @@ define([ pending: '#9e9e9e', queued: '#cfd8dc', running: '#fff59d', + canceled: '#ffcc80', success: '#66bb6a', fail: '#e57373' }; @@ -35,6 +32,8 @@ define([ status: true, execFiles: true, stdout: true, + secret: true, + jobId: true, debug: true }; EllipseDecorator.call(this, options); diff --git a/src/plugins/ExecuteJob/ExecuteJob.js b/src/plugins/ExecuteJob/ExecuteJob.js index de11623d8..8027ef325 100644 --- a/src/plugins/ExecuteJob/ExecuteJob.js +++ b/src/plugins/ExecuteJob/ExecuteJob.js @@ -48,6 +48,7 @@ define([ this._markForDeletion = {}; // id -> node this._oldMetadataByName = {}; // name -> id this.lastAppliedCmd = {}; + this.canceled = false; }; /** @@ -212,6 +213,24 @@ define([ } delete this.lastAppliedCmd[nodeId]; delete this._markForDeletion[nodeId]; + + this.core.delAttribute(job, 'jobId'); + this.core.delAttribute(job, 'secret'); + }; + + ExecuteJob.prototype.resultMsg = function(msg) { + this.sendNotification(msg); + this.createMessage(null, msg); + }; + + ExecuteJob.prototype.onOperationCanceled = function(op) { + var job = this.core.getParent(op), + name = this.core.getAttribute(op, 'name'), + msg = `"${name}" canceled!`; + + this.core.setAttribute(job, 'status', 'canceled'); + this.resultMsg(msg); + this.onComplete(op, null); }; ExecuteJob.prototype.onOperationFail = @@ -221,7 +240,7 @@ define([ exec = this.core.getParent(job), name = this.core.getAttribute(job, 'name'), jobId = this.core.getPath(job), - status = err ? 'fail' : 'success', + status = err ? 'fail' : (this.canceled ? 'canceled' : 'success'), msg = err ? `${name} execution failed: ${err}` : `${name} executed successfully!`, promise = Q(); @@ -236,6 +255,9 @@ define([ } if (err) { this.core.setAttribute(exec, 'status', 'failed'); + } else if (this.canceled) { + // Should I set this to 'canceled'? + this.core.setAttribute(exec, 'status', 'canceled'); } else { // Check if all the other jobs are successful. If so, set the // execution status to 'success' @@ -261,6 +283,7 @@ define([ }); } + this.createMessage(null, msg); promise .then(() => this.save(msg)) .then(() => { @@ -421,7 +444,13 @@ define([ this.logger.debug(`Making a commit from ${this.currentHash}`); this.save(`Queued "${name}" operation in ${this.pipelineName}`) .then(() => executor.createJob({hash})) - .then(() => this.watchOperation(executor, hash, opNode, job)) + .then(info => { + this.core.setAttribute(job, 'jobId', info.hash); + if (info.secret) { // o.w. it is a cached job! + this.core.setAttribute(job, 'secret', info.secret); + } + return this.watchOperation(executor, hash, opNode, job); + }) .catch(err => this.logger.error(`Could not execute "${name}": ${err}`)); }; @@ -742,7 +771,19 @@ define([ var jobId = this.core.getPath(job), opId = this.core.getPath(op), info, - name; + secret, + name = this.core.getAttribute(job, 'name'); + + // If canceled, stop the operation + if (this.canceled) { + secret = this.core.getAttribute(job, 'secret'); + if (secret) { + executor.cancelJob(hash, secret); + this.core.delAttribute(job, 'secret'); + this.canceled = true; + return this.onOperationCanceled(op); + } + } return executor.getInfo(hash) .then(_info => { // Update the job's stdout @@ -756,8 +797,7 @@ define([ return executor.getOutput(hash, currentLine, actualLine+1) .then(outputLines => { var stdout = this.core.getAttribute(job, 'stdout'), - output = outputLines.map(o => o.output).join(''), - jobName = this.core.getAttribute(job, 'name'); + output = outputLines.map(o => o.output).join(''); // parse deepforge commands output = this.parseForMetadataCmds(job, output); @@ -765,7 +805,7 @@ define([ if (output) { stdout += output; this.core.setAttribute(job, 'stdout', stdout); - return this.save(`Received stdout for ${jobName}`); + return this.save(`Received stdout for ${name}`); } }); } @@ -775,7 +815,6 @@ define([ if (info.status === 'RUNNING' && this.core.getAttribute(job, 'status') !== 'running') { - name = this.core.getAttribute(job, 'name'); this.core.setAttribute(job, 'status', 'running'); this.save(`Started "${name}" operation in ${this.pipelineName}`); } @@ -787,7 +826,13 @@ define([ return; } - name = this.core.getAttribute(job, 'name'); + if (info.status === 'CANCELED') { + // If it was cancelled, the pipeline has been stopped + this.logger.debug(`"${name}" has been CANCELED!`); + this.canceled = true; + return this.onOperationCanceled(op); + } + this.core.setAttribute(job, 'execFiles', info.resultHashes[name + '-all-files']); return this.blobClient.getArtifact(info.resultHashes.stdout) .then(artifact => { diff --git a/src/plugins/ExecutePipeline/ExecutePipeline.js b/src/plugins/ExecutePipeline/ExecutePipeline.js index f6e517e8c..c781ba00d 100644 --- a/src/plugins/ExecutePipeline/ExecutePipeline.js +++ b/src/plugins/ExecutePipeline/ExecutePipeline.js @@ -74,6 +74,7 @@ define([ // - keep track if the pipeline has errored // - if so, don't start any more jobs this.pipelineError = null; + this.canceled = false; this.runningJobs = 0; // metadata records @@ -273,8 +274,17 @@ define([ this.onPipelineComplete(err); }; + ExecutePipeline.prototype.onOperationCanceled = function(op) { + var job = this.core.getParent(op); + this.core.setAttribute(job, 'status', 'canceled'); + this.runningJobs--; + this.logger.debug(`${this.core.getAttribute(job, 'name')} has been canceled`); + this.onPipelineComplete(); + }; + ExecutePipeline.prototype.onPipelineComplete = function(err) { - var name = this.core.getAttribute(this.activeNode, 'name'); + var name = this.core.getAttribute(this.activeNode, 'name'), + msg = `"${this.pipelineName}" `; if (err) { this.runningJobs--; @@ -282,8 +292,10 @@ define([ this.pipelineError = this.pipelineError || err; - if (this.pipelineError && this.runningJobs > 0) { - this.logger.info('Pipeline errored but is waiting for the running ' + + this.logger.debug(`${this.runningJobs} remaining jobs`); + if ((this.pipelineError || this.canceled) && this.runningJobs > 0) { + var action = this.pipelineError ? 'error' : 'cancel'; + this.logger.info(`Pipeline ${action}ed but is waiting for the running ` + 'jobs to finish'); return; } @@ -293,11 +305,20 @@ define([ this.sendNotification(`"${this.pipelineName}" execution completed on branch "${this.currentForkName}"`); } + if (this.pipelineError) { + msg += 'failed!'; + } else if (this.canceled) { + msg += 'canceled!'; + } else { + msg += 'finished!'; + } + this.logger.debug(`Pipeline "${name}" complete!`); this.core.setAttribute(this.activeNode, 'status', - (!this.pipelineError ? 'success' : 'failed')); + (this.pipelineError ? 'failed' : (this.canceled ? 'canceled' : 'success'))); this._finished = true; + this.resultMsg(msg); this.save('Pipeline execution finished') .then(() => { this.result.setSuccess(!this.pipelineError); @@ -314,7 +335,7 @@ define([ this.logger.info(`About to execute ${readyOps.length} operations`); // If the pipeline has errored don't start any more jobs - if (this.pipelineError) { + if (this.pipelineError || this.canceled) { if (this.runningJobs === 0) { this.onPipelineComplete(); } diff --git a/src/seeds/cifar10/cifar10.webgmex b/src/seeds/cifar10/cifar10.webgmex index 2e86ade17f08d742fff90217c2be52823b0ebd38..3d6772116ded707fefbe1d36f00e1c1ed1b637f7 100644 GIT binary patch delta 1265 zcma)+PiP%Q9LKYp_mU>9FZh< z5ORn_+M-~6Ec91O53L|YYVhz5i(s*(2(?m5dZ-{utMyb0p=TpFDIWB;1D|0AhVSn? zzxgtMtR7rDad2$*_5R9m-`4m`^tazS`|77Xe`jCM?~YA2DPfEmCZ!}MQ&y>j8^lS5 z1t+PI2_Z~SOSQ)dQC4Kg5@w8*v6kHjtgLMQvUjB^1QyCr ziy2px+QceuInNMH+)NY0Fi$d^<-bpcz0~jcEsQ7;*k%S}B(afLb82G5h$K2DiJ-CK zoToO&yTjg}l`q|0bjr0BJV~8Pn~<1GX$4XiTP}!3I@U;XEhb(X9{e|f7wHeRh zJNg|JN7XUl7<6oN3^|4!+Z{U`PdRGE+RVs}Q>6_SrFweE>ai_d*z<+kf4}0lTn(^b delta 1164 zcma)+J!lj`6vwlh^Uj>{yvt2A5yV4D?i7WYo!R-Y(Mk*k6bV*-Ftam5kcjbHAw@L7 zsD&{eIAEoPSOt_T77@WhK@=?n5o`j9g$5y72_oX8Q>%UOdoacOoIh{&%Fz78P|yCo z?YUyxvNd=A-b23q92yR~+lGUgp8mQNHeyOp3XwEISO^6pI7%#+1~ZswtGS?Z#vE)w zN+{C^3rVAdYn}+>10!W|gb@;m#*!u!!B_%H2m2LmYG8 zs$eL>STTX2VVwH`h4xM+j9Uzq+_bG&`^z?M=EO*5Ar>ZP7#qT+MHVL(DnoRlG&2mb zSc=vneNoLkhWow%di%4jJ-sm&9uJ!1N48GP4o=Mu2ADUq*BSw=*U_@m?tR|dLWh_Qk zxtT9vkK0iy7wcX-iPsWDI^v8f!Kgrf%LFQtc%=|S1oG(|deUBX`=^7Fs||K-ab+In z(#NIpXu*x!u8nT#Y}o7I%g$1IVr}_NV{50O|^-z;l;B u4IchqC3gn2f^_aIs0Fu8xz9}xfRS|H1#stj>E8q6euUjHf6E`6wzfYMXJbn}p%tO+rBKBpQkZ{xkYF42R0PpO zu@`9=a4#Z_A{4X`Ss^zeSP(3N2QLOM1rY>M!Go6`+=$?x)8acl9?b83-#5JHo2lC` zQn{JwWKUnBTYFh@^4a~lH(3FRtf=RX6fEsqf!`wSD}BT0~AR54c&?qG=>i_rReCpK1(!&DI?%VgO16vl{S#j&K8 zBFHhhDTx^=Ac9ir`r%V}3yeyaO3A1l$yL5>63V&HknpijnJFVl5^*Y-QR>V8QN|Hd z%7{-Ca+xcok{*hf0!d*-W^shEl!{i8@N;q^8QVdK)~ldVwh(?wzZ?TgZ#NFMF7Qbi+$%w@_lgpi)zzRq z4lKx*;S696jf_SgMqBMNgz#I=joEy#Wx%{%n;Hl{o`6T)rl*V2?b-pocs`}Sz8>xl je?c{Vd{$S|nV^}3YHJun2lfAbiNQV&sP|-am5Ia;G=(3) delta 836 zcmbVLF>4e-7`>14G)73HU=9*FPGeR~!Z$lJJ2L@642EDp>`bA>%>p(r{brPNNP)u_ZvPtoY^)Ntnybk9oWwD3rk(MxtT9XPVh~QckpPO^d+71fbUV(t8CW}u)uPy-zhxwWC=u10XzB^L%=ikHu@(5VS zmwmCcerx(dUANt^_TyqPw7u4J*$e`Pt?%E;tLI?jSXB3M1FkHZg5B)xb-10KXv0|ccpAij pIpD|Ha$*Jwc#hD4ihUw`(u9?AHHHHm`MVg|k_9~awJJ%Y@e3FF_%Hwf diff --git a/src/seeds/project/project.webgmex b/src/seeds/project/project.webgmex index 705de3aacb7019478861829f39057e36760c3ca6..cae0d86ace0df43187e854df52f65ebba21e011a 100644 GIT binary patch delta 1346 zcmb7^PiS049LMuEZ+Fve?E5n%-D(>)#jI;g%*>nj=FPLkrk9#%scF#`6l~`&Sj9h8 zP(&oP(Na((?%@aC^dN;^8bkXIRvIk|QV+R`huWszED{jxMRE2bClN?IALbO@%2Ahs}sU`dQ5np2Zv zmdHe>NhWBjIOn-tIp)udx4F)_)`Dkw!i>$5luKy^R+d;U5{-4LvE-QYr_zG8pQ2HC z?rhM6>-#IecIOZIrx7`Wi07y{1|7bm>Zm#Djv>b;N5iq%G3>a#9@g|xqSEj<-=lkCi@radw{TP0}v(sO`w1D7B zIMR2-;G@Ig$eGri&=HlbJMsBP9_X_Y5EinsL($tF9NHElc;&4a!QM9zg7BAm71p+* zpxj8&lbe99*YEA(Z>x1BUQ*^o17Gj(5UhrkA^5owjljZnuMNj`hhs2p(Jr{~6#4*; z??<)r#9p*e@Bc8%hY^F>$Iw+cdM!ZJ6EDj-4L5GZ+p9*MG_M0`#=BuAjM{MScDxf7 zXHo7Ecy%1L|Lc2b4y}0q*d^=r9XZk>`8nLuc;HosVT%W0PbF#;;C9nYl9qf?{PO zg8KYIP}h*+fR)==Xd@ez+w7vKxP>C5u&^>}sUiVkZ45hhY9ZGHzk$=7bN*-koO>5% z{yRT2uxq-jQd{vUj!!I4ZR3p-?Uv6tmj7*)OnrfU{pV175L|G%KVoB;! zDmcgK{K3k2Q&UV+>5Nh$h^0m-ik*;BYNu>?bS9*y*ytd%@mUOQl+0=2F;y-VlsYEE zVm!?}HjdfMSP_JaRI5LS(aPbX3Wi4E#hUus?^^%TRx}KE&P7ca|E1i3tFP-D;K0Rb z0G>~j-^}gWT{(lu1w=}La-b_v39Jls2daUdKyRQH_#x02SQS{EBNAQPH&T538hmq{ z{Ltm-5#K~IEG0KK^(;6>@GMc3aGf!4nPl8JPl-2kOVHjG|^*DN; z!1lhlU1lkqUg&jyb;bL~HW&5H5d1L_kL2@X@!MO4%?5Q@=lSxp_{REnQOiMeCtlks z=rg#oCti1<@gWY>^Tvn%TR*o7HUh$9p0_DFQ3BmpL-6}xKZ27-P%B?zXlE}Rn?@L} z-tKvyU)qMwSK9?gQZTqTPT}YzdI0&B`g&NLs*l5?DYWOHZxRxJJdO}xha!t zm@AM?n|6P@d>;DS8=$}1+0)ONH55x8aZ4%VjNpI;CLn^uejqG2I5Naa-93aHm9+X3YrO{*DX~;0w3rDMhL&0- ztPqhOQul_`Ff3?+Wk{3|g4^1KOsy=1 z=0a;JC=Zyi)O8rgR{F|fD`cR3O|%~iEnTBse^vL&Kx?N@jL#+4+UUr4n#$KIWFp2+ zCN#!blG@dQU#;boEx?9*`K9HQ*(WFOHXIa(a8j+|q@ zV}WC#qb->d`TNIG$2;dYDtbi3vDIND3B^`oLaYnPH*UI;D-tuTF$)Q7eBam!%RlFH zJ)A1+OU!f}VwWT1Jz-Kk~Hz z>RzD_zJ6@A$?d+v$g0#chWa%$kc{puytrFVxfd`yhFEfDe(}mO`@hRE-o3gj{f5A; zafl3c8#ZLM&jqf~;&Be{iHCHE$D1@1x1c3iqYj$G@TyYkhOVDzJ!~CE%b<4# zaSy|t!BYF=wDTQ2hsw!~8C3I{|G4jgYDa0&pYJ5Q-k>fIJ{<8nGIO#}N8VBx8*j?O riT=`>%pqq0vsclMi7y;oRE0fB@f|ua-1hHW(;mvkJ delta 1154 zcmbW1J7^R^7{_lf&%4CjT_z!ipvDt(mxnmBGdmAKF^vdGaRCbhfjF}>+eu85BKRN( z#!5J1n94v3Q3?1u$QBA3M5~A?j0RI^XCtH(8+EQyyTuQF179)U^Uqh?slM5+7OP`T z&Dm7L?wV$Afk-#W2}6|1>+%!AcrB7iX)_v&nff~4{M)kC29Pnliu?RQ<^d+G4U0p zMoVOTM^N#IYg|8^9!pmwM2ZJSVTmOSbQIzcI!9>5upoY50?a6dLH%}f zy(}@r0XIqrh8l%4fuUiXb7_Qjj|>@)FjS33`~R}i5l)O`5yZj-3}ZvMj8KHb2r5H# zs5CPSu|SIY{gEG;#4Xs-^;|LWbmE2e+_HClg)gpf&yjXCIhq|Uj#fvTqur5lWE~xj zPRBk+SG>Y=&#ok{u90t=51DH|YCsdgTqev~S#zlK@FP9yN zxD1UD5NV$?ssy6~xxo^sNQe<4h6tpzy|CNbYpZ>L+O3v!b9`qncd5;;b`}oVm*d`O zeDQm(?%UN;y5bRA>&ry(qoMrNvBdkf8xO!pJbgaD`84^T(QEl2zS&W@cfkCwQszr1 z`V-5=KD?UmTU;9~IJ)DtLGXIHoG`JScv7J8+ZW)nXYouou%{;hh-WsyT*mI-0nmOK z%pSGB=E1udZGszZ<%9~hv%B5Q_Pz(NmTk|Jo!JKG{=~P(Z-H`L_yFo&{Ou#?_w3f= aTrHmZ1ZF3?{yi&pcen?(&bbqlO8o}Om|blE diff --git a/src/visualizers/panels/ForgeActionButton/Actions.js b/src/visualizers/panels/ForgeActionButton/Actions.js index b00ebd8bb..e182f52f9 100644 --- a/src/visualizers/panels/ForgeActionButton/Actions.js +++ b/src/visualizers/panels/ForgeActionButton/Actions.js @@ -218,9 +218,43 @@ define([ icon: 'play_for_work', priority: 1, href: download.execFiles + }, + // Stop execution button + { + name: 'Stop Current Job', + icon: 'stop', + priority: 1001, + filter: function() { + var job = this.client.getNode(this._currentNodeId); + return this.isJobRunning(job); + }, + action: function() { + this.stopJob(); + } + } + ], + Execution: [ + makeRestartButton('Execution', 'ExecutePipeline'), + // Stop execution button + { + name: 'Stop Running Execution', + icon: 'stop', + priority: 1001, + filter: function() { + var exec = this.client.getNode(this._currentNodeId); + return exec.getAttribute('status') === 'running'; + }, + action: function() { + // Stop every running job + var execNode = this.client.getNode(this._currentNodeId), + jobIds = execNode.getChildrenIds(); + + jobIds.map(id => this.client.getNode(id)) + .filter(job => this.isJobRunning(job)) // get running jobs + .forEach(job => this.stopJob(job)); // stop them + } } ], - Execution: [makeRestartButton('Execution', 'ExecutePipeline')], Pipeline: [ { name: 'Create new node', diff --git a/src/visualizers/panels/ForgeActionButton/ForgeActionButton.js b/src/visualizers/panels/ForgeActionButton/ForgeActionButton.js index 532e9ae7b..8bb976d44 100644 --- a/src/visualizers/panels/ForgeActionButton/ForgeActionButton.js +++ b/src/visualizers/panels/ForgeActionButton/ForgeActionButton.js @@ -1,8 +1,9 @@ -/*globals DeepForge, $, Materialize, define, _ */ +/*globals DeepForge, $, WebGMEGlobal, window, Materialize, define, _ */ /*jshint browser: true*/ define([ 'blob/BlobClient', + 'executor/ExecutorClient', 'js/Constants', 'panel/FloatingActionButton/FloatingActionButton', 'deepforge/viz/PipelineControl', @@ -16,6 +17,7 @@ define([ 'deepforge/globals' ], function ( BlobClient, + ExecutorClient, CONSTANTS, PluginButton, PipelineControl, @@ -33,6 +35,11 @@ define([ var ForgeActionButton= function (layoutManager, params) { PluginButton.call(this, layoutManager, params); this._pluginConfig = JSON.parse(PluginConfig); + this._executor = new ExecutorClient({ + logger: this.logger.fork('ExecutorClient'), + serverPort: WebGMEGlobal.gmeConfig.server.port, + httpsecure: window.location.protocol === 'https:' + }); this._client = this.client; this._actions = []; this._blobClient = new BlobClient({ @@ -60,7 +67,7 @@ define([ if (!base) { // must be ROOT or FCO basename = node.getAttribute('name') || 'ROOT_NODE'; actions = (ACTIONS[basename] || []) - .filter(action => !action.filter || action.filter()); + .filter(action => !action.filter || action.filter.call(this)); return actions; } @@ -69,7 +76,7 @@ define([ base = this.client.getNode(base.getBaseId()); actions = ACTIONS[basename]; if (actions) { - actions = actions.filter(action => !action.filter || action.filter()); + actions = actions.filter(action => !action.filter || action.filter.call(this)); } } @@ -328,14 +335,42 @@ define([ context.managerConfig.namespace = 'pipeline'; method = useSecondary ? 'runBrowserPlugin' : 'runServerPlugin'; - this.client[method](pluginId, context, err => { - if (err) { - return Materialize.toast(`${name} failed!`, 4000); + this.client[method](pluginId, context, (err, result) => { + var msg = err ? `${name} failed!` : `${name} executed successfully!`, + duration = err ? 4000 : 2000; + + // Check if it was canceled - if so, show that type of message + if (result) { + msg = result.messages[0].message; + duration = 4000; } - Materialize.toast(`${name} executed successfully!`, 2000); + Materialize.toast(msg, duration); }); }; + ForgeActionButton.prototype.isJobRunning = function(job) { + var status = job.getAttribute('status'); + + return (status === 'running' || status === 'pending') && + job.getAttribute('secret') && job.getAttribute('jobId'); + }; + + ForgeActionButton.prototype.stopJob = function(job) { + var jobHash, + secret; + + job = job || this.client.getNode(this._currentNodeId); + jobHash = job.getAttribute('jobId'); + secret = job.getAttribute('secret'); + if (!jobHash || !secret) { + this.logger.error('Cannot stop job. Missing jobHash or secret'); + } + + return this._executor.cancelJob(jobHash, secret) + .then(() => this.logger.info(`${jobHash} has been cancelled!`)) + .fail(err => this.logger.error(`Job cancel failed: ${err}`)); + }; + return ForgeActionButton; }); diff --git a/src/visualizers/widgets/ExecutionIndex/ExecutionIndexWidget.js b/src/visualizers/widgets/ExecutionIndex/ExecutionIndexWidget.js index 0a35fd814..ecf2ffef5 100644 --- a/src/visualizers/widgets/ExecutionIndex/ExecutionIndexWidget.js +++ b/src/visualizers/widgets/ExecutionIndex/ExecutionIndexWidget.js @@ -14,13 +14,7 @@ define([ 'use strict'; var ExecutionIndexWidget, - WIDGET_CLASS = 'execution-index', - STATUS_TO_CLASS = { - success: 'success', - failed: 'danger', - pending: '', - running: 'warning' - }; + WIDGET_CLASS = 'execution-index'; ExecutionIndexWidget = function (logger, container) { this._logger = logger.fork('Widget'); @@ -122,7 +116,7 @@ define([ ExecutionIndexWidget.prototype.addExecLine = function (desc) { var row = $('', {class: 'exec-row', 'data-id': desc.id}), checkBox = $('', {type: 'checkbox'}), - statusClass = STATUS_TO_CLASS[desc.status], + statusClass = Utils.ClassForJobStatus[desc.status], fields, pipeline, name, @@ -201,16 +195,16 @@ define([ if (node) { node.$name.text(desc.name); node.$el.removeClass(node.statusClass); - node.$el.addClass(STATUS_TO_CLASS[desc.status]); + node.$el.addClass(Utils.ClassForJobStatus[desc.status]); - if (STATUS_TO_CLASS[desc.status] !== node.statusClass) { + if (Utils.ClassForJobStatus[desc.status] !== node.statusClass) { // Only update the selection if the status has changed. // ie, it has started running this.updateSelected(desc); } this._logger.debug(`setting execution ${desc.id} to ${desc.status}`); - node.statusClass = STATUS_TO_CLASS[desc.status]; + node.statusClass = Utils.ClassForJobStatus[desc.status]; } else if (desc.type === 'line') { this.lineGraph.updateNode(desc); } diff --git a/src/visualizers/widgets/PipelineEditor/PipelineEditorWidget.js b/src/visualizers/widgets/PipelineEditor/PipelineEditorWidget.js index 6d575ccf6..98af1a9a3 100644 --- a/src/visualizers/widgets/PipelineEditor/PipelineEditorWidget.js +++ b/src/visualizers/widgets/PipelineEditor/PipelineEditorWidget.js @@ -33,12 +33,7 @@ define([ DEFAULT: 'default', CONNECTING: 'connecting' }, - UPLOAD_ARTIFACT_ID = '__UPLOAD_ARTIFACT__', - STATUS_TO_CLASS = { - running: 'warning', - success: 'success', - failed: 'danger' - }; + UPLOAD_ARTIFACT_ID = '__UPLOAD_ARTIFACT__'; PipelineEditorWidget = function (logger, container, execCntr) { EasyDAGWidget.call(this, logger, container); @@ -319,7 +314,7 @@ define([ var row = $(''), title = $('', {class: 'execution-name'}), timestamp = $(''), - className = STATUS_TO_CLASS[exec.status] || '', + className = Utils.ClassForJobStatus[exec.status] || '', date = Utils.getDisplayTime(exec.createdAt), rmIcon = $(REMOVE_ICON);