AMBARI-20296. Workflow Manger support for pointing to kill node in decision editor.(Padma Priya N via gauravn7)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/f7472a09 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/f7472a09 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/f7472a09 Branch: refs/heads/branch-feature-AMBARI-12556 Commit: f7472a09a650c76ea84c4e98492e7aa081887443 Parents: 1766ebf Author: Gaurav Nagar <[email protected]> Authored: Fri Mar 3 21:02:56 2017 +0530 Committer: Gaurav Nagar <[email protected]> Committed: Fri Mar 3 21:02:56 2017 +0530 ---------------------------------------------------------------------- .../ui/app/components/decision-add-branch.js | 5 ++- .../ui/app/components/decision-config.js | 10 ++++- .../ui/app/components/flow-designer.js | 33 +++++++++------ .../ui/app/components/transition-config.js | 3 ++ .../ui/app/components/workflow-action-editor.js | 35 +++++++++------- .../ui/app/domain/cytoscape-flow-renderer.js | 7 +++- .../src/main/resources/ui/app/domain/node.js | 7 +++- .../main/resources/ui/app/domain/workflow.js | 2 +- .../templates/components/decision-config.hbs | 8 +++- .../app/templates/components/flow-designer.hbs | 4 +- .../templates/components/transition-config.hbs | 5 +++ .../components/workflow-action-editor.hbs | 2 +- .../resources/ui/app/validators/workflow-dag.js | 43 ++++++++++++++++++++ 13 files changed, 126 insertions(+), 38 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-add-branch.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-add-branch.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-add-branch.js index 41bb1e5..4066c79 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-add-branch.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-add-branch.js @@ -55,7 +55,8 @@ export default Ember.Component.extend(Validations, FindNodeMixin,{ self.set("isInsertAction",false); this.set("newNodeType",null); this.get('flowRenderer').populateOkToandErrorTONodes(node); - var descendantNodes= this.get('node.validOkToNodes'); + var descendantNodes= Ember.A([]); + descendantNodes.pushObjects(this.get('node.validOkToNodes')); this.set('descendantNodes',descendantNodes); self.$("#selector-content").show(); } @@ -69,7 +70,7 @@ export default Ember.Component.extend(Validations, FindNodeMixin,{ this.set("newNodeType",type); }, onTargetNodeChange(value){ - var node = this.get('descendantNodes').findBy('id',value); + var node = this.get('descendantNodes').findBy('id',value) || this.get('killNodes').findBy('id',value); this.set('targetNode', node); }, save(){ http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-config.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-config.js index 419be37..0df3f96 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-config.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/decision-config.js @@ -32,6 +32,14 @@ export default Ember.Component.extend(Validations,{ initialize : function(){ this.sendAction('register','decision',this); this.set('targetNodes', Ember.A([])); + this.get('targetNodes').pushObjects(this.get('currentNode.validOkToNodes')); this.get('targetNodes').pushObjects(this.get('killNodes')); - }.on('init') + }.on('init'), + actions : { + onTargetNodeChange(index){ + var node = this.get('targetNodes').findBy('id', this.$(`#target-node-select-${index}`).find(":selected").val()); + var config = this.get('actionModel').objectAt(index); + Ember.set(config, 'node', node); + } + } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/components/flow-designer.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/flow-designer.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/flow-designer.js index 2f8455f..6af82ba 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/components/flow-designer.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/flow-designer.js @@ -35,6 +35,9 @@ const Validations = buildValidations({ validators: [ validator('duplicate-data-node-name', { dependentKeys: ['[email protected]'] + }), + validator('workflow-dag', { + dependentKeys: ['[email protected]', '[email protected]'] }) ] }, @@ -79,8 +82,6 @@ export default Ember.Component.extend(FindNodeMixin, Validations, { globalConfig : {}, assetConfig : {}, parameters : {}, - clonedDomain : {}, - clonedErrorNode : {}, validationErrors : Ember.computed('validations.attrs.dataNodes.message', 'validations.attrs.workflow.killNodes.message', { get(key){ var errors = []; @@ -650,9 +651,7 @@ export default Ember.Component.extend(FindNodeMixin, Validations, { this.set('showActionEditor', true); this.set('currentAction', node.actionType); var domain = node.getNodeDetail(); - this.set('clonedDomain', JSOG.stringify(domain)); - this.set('clonedErrorNode', node.errorNode); - this.set('clonedKillMessage',node.get('killMessage')); + this.set('flowGraph', this.flowRenderer.getGraph()); node.set("domain", domain); this.set('currentNode', node); }, @@ -924,7 +923,6 @@ export default Ember.Component.extend(FindNodeMixin, Validations, { if(transition.okToNode && trans.condition !== 'error'){ if(trans.targetNode.id !== transition.okToNode.id){ trans.targetNode = transition.okToNode; - this.showUndo('transition'); } } }, this); @@ -1098,20 +1096,31 @@ export default Ember.Component.extend(FindNodeMixin, Validations, { resetLayout() { this.flowRenderer.resetLayout(); }, + validateWorkflow(promise){ + this.currentNode.onSave(); + this.rerender(); + if(this.get('flowRenderer').isWorkflowValid()){ + promise.resolve(); + }else{ + promise.reject(); + this.send('undo'); + this.rerender(); + } + }, closeActionEditor (isSaved){ this.send("hideNotification"); if(isSaved){ this.currentNode.onSave(); this.doValidation(); + this.rerender(); } else { - this.set('currentNode.domain',JSOG.parse(this.get('clonedDomain'))); - this.set('currentNode.errorNode', this.get('clonedErrorNode')); - if(this.currentNode.type === 'kill'){ - this.set('currentNode.killMessage', this.get('clonedKillMessage')); - } + this.send('undo'); } this.set('showActionEditor', false); - this.rerender(); + var newGraph = this.flowRenderer.getGraph(); + if(!this.get('flowGraph').same(newGraph)){ + this.showUndo('transition'); + } }, saveDraft(){ this.persistWorkInProgress(); http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/components/transition-config.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/transition-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/transition-config.js index ce04863..6c63ac4 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/components/transition-config.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/transition-config.js @@ -62,6 +62,9 @@ export default Ember.Component.extend(FindNodeMixin, Validations, { okToHandler (name){ var validOkToNodes = this.get('currentNode.validOkToNodes'); var node = validOkToNodes.findBy('name',name); + if(!node){ + node = this.get('killNodes').findBy('name',name); + } if(node.id !== this.get('defaultOkToNode').id){ this.set('showWarning', true); }else{ http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-action-editor.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-action-editor.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-action-editor.js index 09bffe7..7bf3534 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-action-editor.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/workflow-action-editor.js @@ -115,6 +115,8 @@ export default Ember.Component.extend( Ember.Evented,{ } if(Ember.isBlank(temp[actionType])){ this.set('actionModel', {}); + }else if(typeof temp[actionType] === 'string'){ + this.get('errors').pushObject({message:'Invalid definition'}); }else{ this.set('actionModel', temp[actionType]); } @@ -163,6 +165,7 @@ export default Ember.Component.extend( Ember.Evented,{ delete this.get('actionModel').slaInfo; delete this.get('actionModel').slaEnabled; } + this.set('errors', Ember.A([])); }.on('init'), initialize : function(){ this.$('#action_properties_dialog').modal({ @@ -171,7 +174,7 @@ export default Ember.Component.extend( Ember.Evented,{ }); this.$('#action_properties_dialog').modal('show'); this.$('#action_properties_dialog').modal().on('hidden.bs.modal', function() { - this.sendAction('closeActionEditor', this.get('saveClicked')); + this.sendAction('closeActionEditor', this.get('saveClicked'), this.get('transition')); }.bind(this)); this.get('fileBrowser').on('fileBrowserOpened',function(context){ this.get('fileBrowser').setContext(context); @@ -211,24 +214,23 @@ export default Ember.Component.extend( Ember.Evented,{ } }); }, - validateDecisionNode(){ - let containsOtherNodes = false; - this.get('actionModel').forEach((model)=>{ - if(model.node.type !== 'kill'){ - containsOtherNodes = true; - } + validateFlowGraph(){ + return new Ember.RSVP.Promise((resolve, reject) =>{ + var deferred = new Ember.RSVP.defer(); + this.sendAction('validateWorkflow', deferred); + deferred.promise.then(()=>{ + resolve(); + }).catch(()=>{ + reject(); + }); }); - if(!containsOtherNodes){ - this.get('errors').pushObject({message:'Atleast one of the decision branches should transition to a node other than a kill node.'}); - }else{ - this.get('errors').clear(); - } }, actions : { closeEditor (){ this.sendAction('close'); }, save () { + this.set('validationErrors', Ember.A([])); var isChildComponentsValid = this.validateChildrenComponents(); if(this.get('validations.isInvalid') || !isChildComponentsValid || this.get('errors').length > 0) { this.set('showErrorMessage', true); @@ -240,9 +242,14 @@ export default Ember.Component.extend( Ember.Evented,{ } this.processMultivaluedComponents(); this.processStaticProps(); - this.$('#action_properties_dialog').modal('hide'); this.sendAction('setNodeTransitions', this.get('transition')); - this.set('saveClicked', true); + this.validateFlowGraph().then(()=>{ + this.set('saveClicked', true); + this.$('#action_properties_dialog').modal('hide'); + }).catch(()=>{ + this.set('saveClicked', false); + this.get('validationErrors').pushObject({message : "Invalid workflow structure. There is no flow to end node."}); + }); }, openFileBrowser(model, context){ if(!context){ http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/domain/cytoscape-flow-renderer.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/domain/cytoscape-flow-renderer.js b/contrib/views/wfmanager/src/main/resources/ui/app/domain/cytoscape-flow-renderer.js index ae178b3..f8796ae 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/domain/cytoscape-flow-renderer.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/domain/cytoscape-flow-renderer.js @@ -369,13 +369,16 @@ var CytoscapeRenderer= Ember.Object.extend({ return descendantNode.get('type') === 'placeholder' || descendantNode.get('type') === 'kill' || descendantNode.id === node.id; }, this); errorToNodes = descendantNodes.reject((descendantNode)=>{ - return descendantNode.get('type') === 'placeholder' || descendantNode.id === node.id; + return descendantNode.get('type') === 'placeholder' || descendantNode.get('type') === 'kill' || descendantNode.id === node.id; }, this); node.set('validOkToNodes', okToNodes); node.set('validErrorToNodes', errorToNodes); }, + getGraph(){ + return this.cy.$('node'); + }, isWorkflowValid(){ - return this.cy.nodes("node[name][type='start']").successors("node[name]").intersection(this.cy.nodes("node[name][type='end']").length > 0); + return this.cy.nodes("node[name][type='start']").successors("node[name]").intersection(this.cy.nodes("node[name][type='end']")).length > 0; }, renderWorkflow(workflow){ this._getCyDataNodes(workflow); http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/domain/node.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/domain/node.js b/contrib/views/wfmanager/src/main/resources/ui/app/domain/node.js index d815df1..70ac19f 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/domain/node.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/domain/node.js @@ -39,6 +39,9 @@ var Node = Ember.Object.extend(FindNodeMixin,{ var i=0; this.get("domain").forEach(function(tran){ self.get("transitions")[i].condition=tran.condition; + if(self.get("transitions")[i].targetNode.id !== tran.node.id){ + self.get("transitions")[i].targetNode = tran.node; + } i++; }); }else if (this.isActionNode()){ @@ -66,7 +69,7 @@ var Node = Ember.Object.extend(FindNodeMixin,{ if (this.isDecisionNode()){ var flows=[]; this.get("transitions").forEach(function(tran){ - flows.push({condition: tran.condition, targetName: tran.getTargetNode().getName()}); + flows.push({condition: tran.condition, node: tran.getTargetNode(true)}); }); this.set("domain",flows); return this.get("domain"); @@ -199,7 +202,7 @@ var Node = Ember.Object.extend(FindNodeMixin,{ }else{ target=transitions[0].targetNode; } - if (target.isPlaceholder()){ + if (target && target.isPlaceholder()){ return target.getDefaultTransitionTarget(); } return target; http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow.js b/contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow.js index 228f0e2..a8e45c1 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/domain/workflow.js @@ -186,7 +186,7 @@ var Workflow= Ember.Object.extend(FindNodeMixin,{ deleteNode(node,transitionslist){ var self=this; var target=node.getDefaultTransitionTarget(); - if (!target.isEndNode() && (node.isForkNode()|| node.isDecisionNode())){ + if (target && !target.isEndNode() && (node.isForkNode()|| node.isDecisionNode())){ target=this.findJoinNode(node); if (!target){//A bug will give target as null if the decision has single path. target=node.getDefaultTransitionTarget(); http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/decision-config.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/decision-config.hbs b/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/decision-config.hbs index cb7ff46..87274b0 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/decision-config.hbs +++ b/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/decision-config.hbs @@ -29,7 +29,13 @@ {{#each actionModel as |flow index|}} <tr class="{{if (eq flow.condition 'default') 'active'}}"> <td>{{input type="text" class="form-control" value=flow.condition placeholder="condition"}}</td> - <td>{{flow.targetName}}</td> + <td> + <select id="target-node-select-{{index}}" {{action "onTargetNodeChange" index on="change"}} name="select-node" class="form-control"> + {{#each targetNodes as |node index|}} + <option value={{node.id}} selected={{eq node.name flow.node.name}}>{{node.name}}</option> + {{/each}} + </select> + </td> </tr> {{/each}} </tbody> http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/flow-designer.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/flow-designer.hbs b/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/flow-designer.hbs index d428815..2c85985 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/flow-designer.hbs +++ b/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/flow-designer.hbs @@ -251,7 +251,7 @@ <i class="fa fa-cloud-upload"></i> </span> </div> - {{decision-add-branch node=node registerAddBranchAction="registerAddBranchAction" addDecisionBranch="addDecisionBranch" workflow=workflow flowRenderer=flowRenderer}} + {{decision-add-branch node=node killNodes=workflow.killNodes registerAddBranchAction="registerAddBranchAction" addDecisionBranch="addDecisionBranch" workflow=workflow flowRenderer=flowRenderer}} </div> {{#if cyOverflow.overflown}} <div class="cyScrollMsg"><i class="fa fa-ellipsis-h cyScrollMsgContent" title="Use the pan tool or drag on canvas to see more" aria-hidden="true"></i></div> @@ -269,7 +269,7 @@ {{#if showActionEditor}} - {{workflow-action-editor actionType=currentAction closeActionEditor="closeActionEditor" setNodeTransitions="setNodeTransitions" actionModel=currentNode.domain nodeType=currentNode.type currentNode=currentNode killNodes=workflow.killNodes credentials=workflow.credentials}} + {{workflow-action-editor actionType=currentAction closeActionEditor="closeActionEditor" setNodeTransitions="setNodeTransitions" actionModel=currentNode.domain nodeType=currentNode.type currentNode=currentNode killNodes=workflow.killNodes credentials=workflow.credentials validateWorkflow="validateWorkflow"}} {{/if}} {{#if showingSaveWorkflow}} {{save-wf type='wf' close="closeSaveWorkflow" showSuccessMessage="showSuccessMessage" jobFilePath=workflowFilePath openFileBrowser="openFileBrowser" closeFileBrowser="closeFileBrowser" jobConfigs=configForSave}} http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/transition-config.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/transition-config.hbs b/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/transition-config.hbs index fe46446..83fe96c 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/transition-config.hbs +++ b/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/transition-config.hbs @@ -37,6 +37,11 @@ <label class="control-label col-xs-2">Ok To<span class="requiredField"> *</span></label> <div class=" col-xs-7"> <select onchange={{action "okToHandler" value="target.value"}} name="select-node" class="form-control" data-show-icon="true"> + <optgroup label="Kill Nodes"></optgroup> + {{#each killNodes as |node index|}} + <option value={{node.name}} selected={{eq node.name transition.okToNode.name}}>{{node.name}}</option> + {{/each}} + <optgroup label="Other Nodes"></optgroup> {{#each currentNode.validOkToNodes as |node index|}} <option value={{node.name}} selected={{eq node.name transition.okToNode.name}}>{{node.name}}</option> {{/each}} http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/workflow-action-editor.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/workflow-action-editor.hbs b/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/workflow-action-editor.hbs index 788916b..c9064a5 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/workflow-action-editor.hbs +++ b/contrib/views/wfmanager/src/main/resources/ui/app/templates/components/workflow-action-editor.hbs @@ -31,8 +31,8 @@ <div class="modal-body"> <div> <form class="form-horizontal" id="action_properties"> + {{designer-errors errors=errors validationErrors=validationErrors}} {{#if (eq nodeType 'decision')}} - {{designer-errors errors=errors}} {{#decision-config currentNode=currentNode actionModel=actionModel killNodes=killNodes register="registerChild"}}{{/decision-config}} {{/if}} {{#if (eq nodeType 'action')}} http://git-wip-us.apache.org/repos/asf/ambari/blob/f7472a09/contrib/views/wfmanager/src/main/resources/ui/app/validators/workflow-dag.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/validators/workflow-dag.js b/contrib/views/wfmanager/src/main/resources/ui/app/validators/workflow-dag.js new file mode 100644 index 0000000..18da25b --- /dev/null +++ b/contrib/views/wfmanager/src/main/resources/ui/app/validators/workflow-dag.js @@ -0,0 +1,43 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import Ember from 'ember'; +import BaseValidator from 'ember-cp-validations/validators/base'; + +const DuplicateDataNodeName = BaseValidator.extend({ + validate(value, options, model, attribute) { + if (model.get('dataNodes')) { + if(!model.get('flowRenderer').isWorkflowValid()){ + return "Invalid workflow structure. There is no flow to end node." + } + } + } +}); + +DuplicateDataNodeName.reopenClass({ + /** + * Define attribute specific dependent keys for your validator + * + * @param {String} attribute The attribute being evaluated + * @param {Unknown} options Options passed into your validator + * @return {Array} + */ + getDependentsFor(/* attribute, options */) { + return []; + } +}); + +export default DuplicateDataNodeName;
