http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/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 bd2944b..ed743f4 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
@@ -20,17 +20,45 @@ import {Workflow} from '../domain/workflow';
 import Constants from '../utils/constants';
 import {WorkflowGenerator} from '../domain/workflow-xml-generator';
 import {WorkflowImporter} from '../domain/workflow-importer';
+import {WorkflowJsonImporter} from '../domain/workflow-json-importer';
 import {WorkflowContext} from '../domain/workflow-context';
-import {DefaultLayoutManager as LayoutManager} from 
'../domain/default-layout-manager';
-import EmberValidations,{ validator } from 'ember-validations';
+import {JSPlumbRenderer} from '../domain/jsplumb-flow-renderer';
+import {CytoscapeRenderer} from '../domain/cytoscape-flow-renderer';
+import {FindNodeMixin} from '../domain/findnode-mixin';
+import { validator, buildValidations } from 'ember-cp-validations';
+import WorkflowPathUtil from '../domain/workflow-path-util';
 
+const Validations = buildValidations({
+  'dataNodes': { /* For Cytoscape */
+    validators: [
+      validator('duplicate-data-node-name', {
+        dependentKeys: ['[email protected]']
+      })
+    ]
+  },
+  'workflow.killNodes': {
+    validators: [
+      validator('duplicate-kill-node-name', {
+        dependentKeys: ['[email protected]']
+      })
+    ]
+  },
+  'flattenedNodes': {
+    validators: [
+      validator('duplicate-flattened-node-name', {
+        dependentKeys: ['[email protected]']
+      })
+    ]
+  }
+});
 
-export default Ember.Component.extend(EmberValidations,{
+export default Ember.Component.extend(FindNodeMixin, Validations, {
   workflowContext : WorkflowContext.create({}),
   workflowTitle:"",
   previewXml:"",
   supportedActionTypes:["java", "hive", "pig", "sqoop", "shell", "spark", 
"map-reduce", "hive2", "sub-workflow", "distcp", "ssh", "FS"],
   workflow:null,
+  hoveredWidget:null,/**/
   showingConfirmationNewWorkflow:false,
   showingWorkflowConfigProps:false,
   workflowSubmitConfigs:{},
@@ -40,106 +68,118 @@ export default Ember.Component.extend(EmberValidations,{
   domain:{},
   showActionEditor : false,
   flattenedNodes: [],
-
+  dataNodes: [], /* For cytoscape */
+  hoveredAction: null,
   workflowImporter:WorkflowImporter.create({}),
-  designerPlumb:null,
   propertyExtractor : Ember.inject.service('property-extractor'),
+  clipboardService : Ember.inject.service('workflow-clipboard'),
+  workspaceManager : Ember.inject.service('workspace-manager'),
   showGlobalConfig : false,
   showParameterSettings : false,
+  showNotificationPanel : false,
   globalConfig : {},
   parameters : {},
   clonedDomain : {},
   clonedErrorNode : {},
   validationErrors : [],
-  layoutManager:null,
   showingFileBrowser : false,
   killNode : {},
   isWorkflowImporting: false,
   isImportingSuccess: true,
-  initialize :function(){
-    this.designerPlumb=jsPlumb.getInstance({});
-    this.layoutManager=LayoutManager.create({});
+  shouldPersist : false,
+  useCytoscape: Constants.useCytoscape,
+  cyOverflow: {},
+  clipboard : Ember.computed.alias('clipboardService.clipboard'),
+  isStackTraceVisible: false,
+  isStackTraceAvailable: false,
+  stackTrace:"",
+  initialize : function(){
+    var id = 'cy-' + Math.ceil(Math.random() * 1000);
+    this.set('cyId', id);
+    this.sendAction('register', this.get('tabInfo'), this);
+  }.on('init'),
+  elementsInserted :function(){
+    if (this.useCytoscape){
+      this.flowRenderer=CytoscapeRenderer.create({id : this.get('cyId')});
+    }else{
+      this.flowRenderer=JSPlumbRenderer.create({});
+    }
+
     this.setConentWidth();
     this.set('workflow',Workflow.create({}));
     if(this.get("xmlAppPath")){
-      var workflowXmlPath = this.get("xmlAppPath"), relXmlPath = "", tempArr;
-      if(workflowXmlPath.indexOf("://") === -1 && workflowXmlPath.indexOf(":") 
=== -1){
-        relXmlPath = workflowXmlPath;
-      } else{
-        tempArr = workflowXmlPath.split("//")[1].split("/");
-        tempArr.splice(0, 1);
-        relXmlPath = "/" + tempArr.join("/");
-        if(!(relXmlPath.indexOf(".xml") === relXmlPath.length-4)) {
-          if(relXmlPath.charAt(relXmlPath.length-1) !== "/"){
-            relXmlPath = relXmlPath+ "/" +"workflow.xml";
-          } else{
-            relXmlPath = relXmlPath+"workflow.xml";
-          }
-        }
-      }
-      this.importWorkflow(relXmlPath);
+      this.showExistingWorkflow();
       return;
-    }else{
+    } else {
       this.workflow.initialize();
       this.initAndRenderWorkflow();
       this.$('#wf_title').focus();
-      this.restoreWorkinProgress();
+      if (Constants.autoRestoreWorkflowEnabled){
+        this.restoreWorkflow();
+      }
+    }
+    if(Ember.isBlank(this.get('workflow.name'))){
+      this.set('workflow.name', Ember.copy(this.get('tabInfo.name')));
     }
-  }.on('didInsertElement'),
-  validations: {
-    'flattenedNodes': {
-      inline : validator(function() {
-        var nodeNames = new Map();
-        this.get("validationErrors").clear();
-        this.get('flattenedNodes').forEach((item)=>{
-          Ember.set(item, "errors", false);
-          if(nodeNames.get(item.name)){
-            Ember.set(item, "errors", true);
-            this.get("validationErrors").pushObject({node:item,message:"Node 
name should be unique"});
-          }else{
-            nodeNames.set(item.name, item);
-            Ember.set(item, "errors", false);
-          }
-          if(this.get("supportedActionTypes").indexOf(item.actionType) === -1 
&& item.type === "action"){
-            this.get('validationErrors').pushObject({node : item ,message : 
item.actionType+" is unsupported"});
-          }
-          var nodeErrors=item.validateCustom();
-          if (nodeErrors.length>0){
-            Ember.set(item, "errors", true);
-            nodeErrors.forEach(function(errMsg){
-              this.get("errors").pushObject({node:item,message:errMsg });
-            }.bind(this));
-          }
-        }.bind(this));
 
-        if(this.get('flattenedNodes').length !== nodeNames.size || 
this.get("errors").length>0){
-          return true;
-        }
-      })
-    },
-    "workflow.killnodes": {
-      inline : validator(function() {
-        let killNodes = [], flag;
-        if(this.get("workflow") && this.get("workflow").killNodes){
-          killNodes = this.get("workflow").killNodes;
-          for(let i=0; i<killNodes.length; i++){
-            for(let j=0; j<killNodes.length; j++){
-              if(killNodes[i].name === killNodes[j].name && i !== j){
-                this.get('validationErrors').pushObject({node : killNodes[j] 
,message : "Duplicate killnode"});
-                flag = true;
-                break;
-              }
-            }
-            if(flag){
-              break;
-            }
-          }
-        }
-        if (flag){
-          return true;
+  }.on('didInsertElement'),
+  restoreWorkflow(){
+    if (!this.get("isNew")){
+      var draftWorkflow=this.getDraftWorkflow();
+      if (draftWorkflow){
+        this.resetDesigner();
+        this.set("workflow",draftWorkflow);
+        this.rerender();
+        this.doValidation();
+      }
+    }
+  },
+  observeXmlAppPath : Ember.observer('xmlAppPath', function(){
+    if(!this.get('xmlAppPath') || null === this.get('xmlAppPath')){
+      return;
+    }else{
+      this.showExistingWorkflow();
+    }
+  }),
+  observeFilePath : Ember.observer('workflowFilePath', function(){
+    if(!this.get('workflowFilePath') || null === this.get('workflowFilePath')){
+      return;
+    }else{
+      this.sendAction('changeFilePath', this.get('tabInfo'), 
this.get('workflowFilePath'));
+    }
+  }),
+  nameObserver : Ember.observer('workflow.name', function(){
+    if(!this.get('workflow')){
+      return;
+    }else if(this.get('workflow') && Ember.isBlank(this.get('workflow.name'))){
+      if(!this.get('clonedTabInfo')){
+        this.set('clonedTabInfo', Ember.copy(this.get('tabInfo')));
+      }
+      this.sendAction('changeTabName', this.get('tabInfo'), 
this.get('clonedTabInfo.name'));
+    }else{
+      this.sendAction('changeTabName', this.get('tabInfo'), 
this.get('workflow.name'));
+    }
+  }),
+  showParentWorkflow(type, path){
+    this.sendAction('openTab', type, path);
+  },
+  showExistingWorkflow(){
+    var workflowXmlPath = this.get("xmlAppPath"), relXmlPath = "", tempArr;
+    if(workflowXmlPath.indexOf("://") === -1 && workflowXmlPath.indexOf(":") 
=== -1){
+      relXmlPath = workflowXmlPath;
+    } else{
+      tempArr = workflowXmlPath.split("//")[1].split("/");
+      tempArr.splice(0, 1);
+      relXmlPath = "/" + tempArr.join("/");
+      if(relXmlPath.indexOf(".xml") !== relXmlPath.length-4) {
+        if(relXmlPath.charAt(relXmlPath.length-1) !== "/"){
+          relXmlPath = relXmlPath+ "/" +"workflow.xml";
+        } else{
+          relXmlPath = relXmlPath+"workflow.xml";
         }
-      })
+      }
     }
+    this.importWorkflow(relXmlPath);
   },
   setConentWidth(){
     var offset = 120;
@@ -150,197 +190,101 @@ export default Ember.Component.extend(EmberValidations,{
       return;
     });
   },
+  workflowXmlDownload(workflowXml){
+      var link = document.createElement("a");
+      link.download = "workflow.xml";
+      link.href = "data:text/xml,"+vkbeautify.xml(workflowXml);
+      link.click();
+  },
   nodeRendered: function(){
-    var self=this;
+    this.doValidation();
     if(this.get('renderNodeTransitions')){
-      var connections=[];
-      var visitedNodes=[];
-      
this.renderTransitions(this.get("workflow").startNode,connections,visitedNodes);
-      this.workflowConnections=connections;
+      
this.flowRenderer.onDidUpdate(this,this.get("workflow").startNode,this.get("workflow"));
       this.layout();
-      this.designerPlumb.setSuspendDrawing(true);
-      this.designerPlumb.batch(function(){
-        connections.forEach(function(conn){
-          self.designerPlumb.connect(conn);
-        });
-      });
-      this.designerPlumb.setSuspendDrawing(false,true);
       this.set('renderNodeTransitions',false);
     }
+    this.resize();
     this.persistWorkInProgress();
   }.on('didUpdate'),
-  cleanUpJsplumb:function(){
-    this.get('flattenedNodes').clear();
+  resize(){
+    this.flowRenderer.resize();
+  },
+  cleanupFlowRenderer:function(){
     this.set('renderNodeTransitions',false);
-    this.designerPlumb.detachEveryConnection();
+    this.flowRenderer.cleanup();
   }.on('willDestroyElement'),
   initAndRenderWorkflow(){
-    this.designerPlumb.ready(function() {
+    var panelOffset=this.$(".designer-panel").offset();
+    var canvasHeight=Ember.$(window).height()-panelOffset.top-25;
+    this.flowRenderer.initRenderer(function(){
       this.renderWorkflow();
-    }.bind(this));
+    
}.bind(this),{context:this,flattenedNodes:this.get("flattenedNodes"),dataNodes:this.get("dataNodes"),
 cyOverflow:this.get("cyOverflow"),canvasHeight:canvasHeight});
   },
   renderWorkflow(){
-    this.get('flattenedNodes').clear();
     this.set('renderNodeTransitions', true);
-    var visitedNodes=[];
-    this.renderNodes(this.get("workflow").startNode,visitedNodes);
+    this.flowRenderer.renderWorkflow(this.get("workflow"));
+    this.doValidation();
   },
   rerender(){
-    this.designerPlumb.detachEveryConnection();
+    this.flowRenderer.cleanup();
     this.renderWorkflow(this.get("workflow"));
   },
   setCurrentTransition(transition){
     this.set("currentTransition",transition);
   },
-  renderNodes(node,visitedNodes){
-    if (!node || node.isKillNode()){
-      return;
-    }
-    if (visitedNodes.contains(node)){
-      return;
-    }
-    visitedNodes.push(node);
-    if(!this.get('flattenedNodes').contains(node)){
-      this.get('flattenedNodes').pushObject(node);
-    }
-    if (node.transitions.length > 0){
-      node.transitions.forEach(function(transition) {
-        var target = transition.targetNode;
-        this.renderNodes(target,visitedNodes);
-      }.bind(this));
-    }
-  },
-  createConnection(sourceNode,target,transition){
-    var connectionColor="#777";
-    var lineWidth=1;
-    if (transition.condition){
-      if(transition.condition==="default"){
-        lineWidth=2;
-      }else if (transition.condition==="error"|| transition.errorPath){
-        connectionColor=Constants.globalSetting.errorTransitionColor;
-      }
-    }
-    var connectionObj={
-      source:sourceNode.id,
-      target:target.id,
-      connector:["Straight"],
-      paintStyle:{lineWidth:lineWidth,strokeStyle:connectionColor},
-      endpointStyle:{fillStyle:'rgb(243,229,0)'},
-      endpoint: ["Dot", {
-        radius: 1
-      }],
-      alwaysRespectStubs:true,
-      anchors: [["Bottom"],["Top"]],
-      overlays:[]
-    };
-    return connectionObj;
+  actionInfo(node){
+    this.send("showNotification", node);
   },
   deleteTransition(transition){
+    this.createSnapshot();
     this.get("workflow").deleteTransition(transition);
+    this.showUndo('transition');
     this.rerender();
   },
-  renderTransitions(sourceNode,connections,visitedNodes){
+  showWorkflowActionSelect(element){
     var self=this;
-    if(!sourceNode){
-      return;
-    }
-    if (visitedNodes.contains(sourceNode)){
-      return;
-    }
-    if (sourceNode.hasTransition() ){
-      var transitionCount=sourceNode.transitions.length;
-      sourceNode.transitions.forEach(function(transition) {
-        var target = transition.targetNode;
-        if (target.isKillNode() || !Constants.showErrorTransitions && 
transition.isOnError()){
-          return;
-        }
-        var connectionObj=self.createConnection(sourceNode,target,transition);
-
-        if (transition.condition){
-          var conditionHTML = "<div class='decision-condition' 
title='"+transition.condition+"'>"+ transition.condition+"</div>";
-          connectionObj.overlays.push([ "Label", {label:conditionHTML, 
location:0.75, id:"myLabel" } ]);
-        }
-        if (!target.isPlaceholder()){
-          connectionObj.overlays.push(["PlainArrow",{location:-0.1,width: 
7,length: 7}]);
-        }
-        if (!(sourceNode.isPlaceholder() || target.isKillNode())){
-          var location=target.type==="placeholder"?1:0.5;
-          var addNodeoverlay=["Custom" , {
-            id: sourceNode.id+"_"+target.id+"_"+"connector",
-            location:location,
-            create:function(component) {
-              var container=Ember.$('<div />');
-              var plus= Ember.$('<div class="fa fa-plus 
connector_overlay_new"></div>');
-              if ((sourceNode.isDecisionNode() && transitionCount>1 
||sourceNode.isForkNode() && transitionCount>2 ) &&
-                target.isPlaceholder() &&
-                !transition.isDefaultCasePath()){
-                var trash=Ember.$('<div class="node_actions node_left"><i 
class="fa fa-trash-o"></i></div>');
-                trash.on("click",function(){
-                  self.deleteTransition(transition);
-                });
-                plus.append(trash);
-              }
-              container.append(plus);
-              return container;
-            },
-            events:{
-              click:function(labelOverlay, originalEvent) {
-                var element = originalEvent.target;
-                self.set('popOverElement', element);
-                self.setCurrentTransition(transition);
-                self.$('.popover').popover('destroy');
-                Ember.$(element).parents(".jsplumb-overlay").css("z-index", 
"4");
-                self.$(element).attr('data-toggle','popover');
-                self.$(element).popover({
-                  html : true,
-                  title : "Add Node <button type='button' 
class='close'>&times;</button>",
-                  placement: 'right',
-                  trigger : 'focus',
-                  content : function(){
-                    return self.$('#workflow-actions').html();
-                  }
-                });
-                self.$(element).popover("show");
-                self.$('.popover .close').on('click',function(){
-                  Ember.$(".jsplumb-overlay").css("z-index", "");
-                  self.$('.popover').popover('destroy');
-                });
-              }
-            }
-          }];
-          connectionObj.overlays.push(addNodeoverlay);
-        }
-        connections.push(connectionObj);
-        self.renderTransitions(target,connections,visitedNodes);
-      });
-    }
+    this.$('.popover').popover('destroy');
+    Ember.$(element).parents(".jsplumb-overlay").css("z-index", "4");
+    this.$(element).attr('data-toggle','popover');
+    this.$(element).popover({
+      html : true,
+      title : "Add Node <button type='button' class='close'>&times;</button>",
+      placement: 'right',
+      trigger : 'focus',
+      content : function(){
+        return self.$('#workflow-actions').html();
+      }
+    });
+    this.$(element).popover("show");
+    this.$('.popover .close').on('click',function(){
+      Ember.$(".jsplumb-overlay").css("z-index", "");
+      this.$('.popover').popover('destroy');
+    }.bind(this));
   },
+
   layout(){
-    var nodes = Ember.$(".nodecontainer");
-    //var edges = this.designerPlumb.getConnections();
-    var edges=this.workflowConnections;
-    this.layoutManager.doLayout(this,nodes,edges,this.get("workflow"));
-    this.designerPlumb.repaintEverything();
-    var endNodeTop=this.$("#node-end").offset().top;
-    var endNodeLeft=this.$("#node-end").offset().left;
-    
this.$("#killnodes-container").offset({top:endNodeTop+50,left:endNodeLeft-50});
-    var top = this.$("#killnodes-container").offset().top + 40;
-    var left = this.$("#killnodes-container").offset().left - 28;
-    this.$('.kill').each(function(index,value){
-      this.$(value).offset({top:top,left:left});
-      top = this.$(value).offset().top+70 ;
-    }.bind(this));
+    this.flowRenderer.refresh();
   },
   doValidation(){
-    this.set('validationErrors',[]);
-    this.validate().then(() => {
-      this.set('validationErrors',[]);
-    }).catch(() => {
-      this.get('flattenedNodes').filterBy('errors',true).forEach((node)=>{
-        this.get('validationErrors').pushObjects(node.errorMsgs);
-      }.bind(this));
-
-    }.bind(this));
+    this.validate();
+  },
+  getStackTrace(data){
+    if(data){
+     try{
+      var stackTraceMsg = JSON.parse(data).stackTrace;
+      if(!stackTraceMsg){
+        return "";
+      }
+     if(stackTraceMsg instanceof Array){
+       return stackTraceMsg.join("").replace(/\tat /g, 
'&nbsp;&nbsp;&nbsp;&nbsp;at&nbsp;');
+     } else {
+       return stackTraceMsg.replace(/\tat /g, 
'<br/>&nbsp;&nbsp;&nbsp;&nbsp;at&nbsp;');
+     }
+     } catch(err){
+       return "";
+     }
+    }
+    return "";
   },
   importWorkflow(filePath){
     var self = this;
@@ -352,17 +296,34 @@ export default Ember.Component.extend(EmberValidations,{
     workflowXmlDefered.promise.then(function(data){
       this.importWorkflowFromString(data);
       this.set("isWorkflowImporting", false);
-    }.bind(this)).catch(function(e){
+    }.bind(this)).catch(function(data){
+    var stackTraceMsg = self.getStackTrace(data.responseText);
+    if(stackTraceMsg.length){
+      self.set("isStackTraceVisible", true);
+      self.set("stackTrace", stackTraceMsg);
+      self.set("isStackTraceAvailable", true);
+    } else {
+      self.set("isStackTraceVisible", false);
+      self.set("isStackTraceAvailable", false);
+    }
       self.set("isWorkflowImporting", false);
       self.set("isImportingSuccess", false);
     });
   },
   importWorkflowFromString(data){
     var workflow=this.get("workflowImporter").importWorkflow(data);
-    this.resetDesigner();
-    this.set("workflow",workflow);
-    this.rerender();
-    this.doValidation();
+    if(this.get('workflow')){
+      this.resetDesigner();
+      this.set("workflow",workflow);
+      this.initAndRenderWorkflow();
+      this.rerender();
+      this.doValidation();
+    }else{
+      this.workflow.initialize();
+      this.set("workflow",workflow);
+      this.initAndRenderWorkflow();
+      this.$('#wf_title').focus();
+    }
   },
   getWorkflowFromHdfs(filePath){
     var url = Ember.ENV.API_URL + "/readWorkflowXml?workflowXmlPath="+filePath;
@@ -377,16 +338,16 @@ export default Ember.Component.extend(EmberValidations,{
       }
     }).done(function(data){
       deferred.resolve(data);
-    }).fail(function(){
-      deferred.reject();
+    }).fail(function(data){
+      deferred.reject(data);
     });
     return deferred;
   },
   resetDesigner(){
     this.set("isImportingSuccess", true);
-    this.set("xmlAppPath", null)
-    this.set('errors',{});
-    this.set('validationErrors',{});
+    this.set("xmlAppPath", null);
+    this.set('errors',[]);
+    this.set('validationErrors',[]);
     this.set('workflowFilePath',"");
     this.get("workflow").resetWorfklow();
     this.set('globalConfig', {});
@@ -395,7 +356,7 @@ export default Ember.Component.extend(EmberValidations,{
       this.set('workflow.parameters', {});
     }
     this.set('parameters', {});
-    this.designerPlumb.reset();
+    this.flowRenderer.reset();
   },
   resetZoomLevel(){
     this.set("zoomLevel", 1);
@@ -423,33 +384,150 @@ export default Ember.Component.extend(EmberValidations,{
     return deferred;
   },
   persistWorkInProgress(){
-    //TODO later
+   var json=JSON.stringify(this.get("workflow"));
+   this.get('workspaceManager').saveWorkInProgress(this.get('tabInfo.id'), 
json);
+  },
+  getDraftWorkflow(){
+    var drafWorkflowJson = 
this.get('workspaceManager').restoreWorkInProgress(this.get('tabInfo.id'));
+    var workflowImporter=WorkflowJsonImporter.create({});
+    var workflow=workflowImporter.importWorkflow(drafWorkflowJson);
+    return workflow;
+  },
+  createSnapshot() {
+    this.set('undoAvailable', false);
+    this.set('workflowSnapshot', JSON.stringify(this.get("workflow")));
+  },
+  showUndo (type){
+    this.set('undoAvailable', true);
+    this.set('undoType', type);
+  },
+  deleteWorkflowNode(node){
+    this.createSnapshot();
+    if(node.isKillNode()){
+      var result=this.get("workflow").deleteKillNode(node);
+      if (result && result.status===false){
+        this.get('validationErrors').pushObject({node : node ,message 
:result.message});
+      }
+    } else {
+      this.get("workflow").deleteNode(node);
+    }
+    this.rerender();
+    this.doValidation();
+    this.showUndo('node');
+  },
+  addWorkflowBranch(node){
+    this.createSnapshot();
+    this.get("workflow").addBranch(node);
+    this.rerender();
+  },
+  openWorkflowEditor(node){
+    this.createSnapshot();
+    var validOkToNodes = 
WorkflowPathUtil.findValidTransitionsTo(this.get('workflow'), node);
+    this.set('showActionEditor', true);
+    this.set('currentAction', node.actionType);
+    var domain = node.getNodeDetail();
+    this.set('clonedDomain',Ember.copy(domain));
+    this.set('clonedErrorNode', node.errorNode);
+    this.set('clonedKillMessage',node.get('killMessage'));
+    node.set("domain", domain);
+    node.set("validOkToNodes", validOkToNodes);
+    this.set('currentNode', node);
+  },
+  openDecisionEditor(node) {
+    this.get("addBranchListener").trigger("showBranchOptions", node);
+  },
+
+  copyNode(node){
+    this.get('clipboardService').setContent(node, 'copy');
+  },
+  cutNode(node){
+    this.get('clipboardService').setContent(node, 'cut');
+    this.deleteWorkflowNode(node);
+  },
+  replaceNode(node){
+    var clipboardContent = this.get('clipboardService').getContent();
+    Ember.set(node, 'name', clipboardContent.name+'-copy');
+    Ember.set(node, 'domain', clipboardContent.domain);
+    Ember.set(node, 'actionType', clipboardContent.actionType);
+    this.rerender();
+    this.doValidation();
   },
-  restoreWorkinProgress(){
-    //TODO later
+  scrollToNewPosition(){
+    if (Constants.useCytoscape){
+      return;
+    }
+    var scroll = Ember.$(window).scrollTop();
+    Ember.$('html, body')
+    .animate({
+      scrollTop: scroll+200
+    }, 1000);
+  },
+  openSaveWorkflow (){
+    this.get('workflowContext').clearErrors();
+    var 
workflowGenerator=WorkflowGenerator.create({workflow:this.get("workflow"),
+    workflowContext:this.get('workflowContext')});
+    var workflowXml=workflowGenerator.process();
+    if(this.get('workflowContext').hasErrors()){
+      this.set('errors',this.get('workflowContext').getErrors());
+    }else{
+      var dynamicProperties = 
this.get('propertyExtractor').getDynamicProperties(workflowXml);
+      var 
configForSubmit={props:dynamicProperties,xml:workflowXml,params:this.get('workflow.parameters')};
+      this.set("workflowSubmitConfigs",configForSubmit);
+      this.set("showingSaveWorkflow",true);
+    }
+  },  
+  openJobConfig (){
+    this.get('workflowContext').clearErrors();
+    var 
workflowGenerator=WorkflowGenerator.create({workflow:this.get("workflow"),
+    workflowContext:this.get('workflowContext')});
+    var workflowXml=workflowGenerator.process();
+    if(this.get('workflowContext').hasErrors()){
+      this.set('errors',this.get('workflowContext').getErrors());
+    }else{
+      var dynamicProperties = 
this.get('propertyExtractor').getDynamicProperties(workflowXml);
+      var 
configForSubmit={props:dynamicProperties,xml:workflowXml,params:this.get('workflow.parameters')};
+      this.set("workflowSubmitConfigs",configForSubmit);
+      this.set("showingWorkflowConfigProps",true);
+    }
   },
   actions:{
+    showStackTrace(){
+      this.set("isStackTraceVisible", true);
+    },
+    hideStackTrace(){
+      this.set("isStackTraceVisible", false);
+    },
     showWorkflowSla (value) {
       this.set('showWorkflowSla', value);
     },
     showCreateKillNode (value){
-      this.set('showCreateKillNode', value);
+      this.set('showKillNodeManager', value);
+      this.set('addKillNodeMode', true);
+      this.set('editMode', false);
+    },
+    showKillNodeManager (value){
+      this.set('showKillNodeManager', value);
+      this.set('addKillNodeMode', false);
+    },
+    closeKillNodeManager(){
+      this.set("showKillNodeManager", false);
     },
     showVersionSettings(value){
       this.set('showVersionSettings', value);
     },
-    showParameterSettings(value){
+    showingParameterSettings(value){ 
       if(this.get('workflow.parameters') !== null){
         this.set('parameters', Ember.copy(this.get('workflow.parameters')));
       }else{
-        this.set('globalConfig', {});
+        this.set('parameters', {});
       }
       this.set('showParameterSettings', value);
     },
     showCredentials(value){
       this.set('showCredentials', value);
     },
-    createKillNode(){
+    createKillNode(killNode){
+      this.set("killNode", killNode);
       this.set("createKillnodeError",null);
       var 
existingKillNode=this.get('workflow').get("killNodes").findBy("name",this.get('killNode.name'));
       if (existingKillNode){
@@ -460,7 +538,7 @@ export default Ember.Component.extend(EmberValidations,{
         this.set("createKillnodeError","The kill node cannot be empty");
         return;
       }
-      
this.get("workflow").createKillNode(this.get('killNode.name'),this.get('killNode.message'));
+      
this.get("workflow").createKillNode(this.get('killNode.name'),this.get('killNode.killMessage'));
       this.set('killNode',{});
       this.rerender();
       this.layout();
@@ -469,62 +547,89 @@ export default Ember.Component.extend(EmberValidations,{
       this.set('showCreateKillNode', false);
     },
     addNode(type){
+      this.createSnapshot();
       var currentTransition=this.get("currentTransition");
-      var newNode=this.get("workflow").addNode(currentTransition,type);
-      if(currentTransition.targetNode.isPlaceholder()){
-        this.designerPlumb.remove(currentTransition.targetNode.id);
-      }
+      
this.get("workflow").addNode(this.findTransition(this.get("workflow").startNode,
 currentTransition.sourceNodeId, currentTransition.targetNode.id),type);
       this.rerender();
       this.doValidation();
-      var scroll = $(window).scrollTop();
-      Ember.$('html, body')
-      .animate({
-        scrollTop: scroll+200
-      }, 1000);
+      this.scrollToNewPosition();
     },
+
     nameChanged(){
       this.doValidation();
     },
-    deleteNode(node){
-      if(node.isKillNode()){
-        var result=this.get("workflow").deleteKillNode(node);
-        if (result && result.status===false){
-          this.get('validationErrors').pushObject({node : node ,message 
:result.message});
-        }
-      } else {
-        this.get("workflow").deleteNode(node);
+    copyNode(node){
+      this.copyNode(node);
+    },
+    pasteNode(){
+      var clipboardContent = this.get('clipboardService').getContent();
+      var currentTransition = this.get("currentTransition");
+      var node = this.get("workflow").addNode(currentTransition, 
clipboardContent.actionType);
+      if(clipboardContent.operation === 'cut'){
+        node.name = clipboardContent.name;
+      }else{
+        node.name = clipboardContent.name + '-copy';
       }
+      node.domain = clipboardContent.domain;
+      node.actionType = clipboardContent.actionType;
       this.rerender();
       this.doValidation();
+      this.scrollToNewPosition();
+    },
+    deleteNode(node){
+      this.deleteWorkflowNode(node);
     },
     openEditor(node){
-      this.set('showActionEditor', true);
-      this.set('currentAction', node.actionType);
-      var domain = node.getNodeDetail();
-      this.set('clonedDomain',Ember.copy(domain));
-      this.set('clonedErrorNode', node.errorNode);
-      this.set('clonedKillMessage',node.get('killMessage'));
-      node.set("domain", domain);
-      this.set('currentNode', node);
+      this.openWorkflowEditor(node);
+    },
+    setFilePath(filePath){
+      this.set("workflowFilePath", filePath);
+    },
+    showNotification(node){
+      this.set("showNotificationPanel", true);
+      if(node.actionType){
+        //this.set("hoveredWidget", node.actionType+"-action-info");
+        //this.set("hoveredAction", node.getNodeDetail());
+      }
+    },
+    hideNotification(){
+      this.set("showNotificationPanel", false);
     },
     addBranch(node){
-      this.get("workflow").addBranch(node);
-      this.rerender();
+      this.addWorkflowBranch(node);
     },
     addDecisionBranch(settings){
+      this.createSnapshot();
       this.get("workflow").addDecisionBranch(settings);
       this.rerender();
     },
-    addKillNode(errorNode){
+    setNodeTransitions(transition){
       var currentNode= this.get("currentNode");
-      if(errorNode && errorNode.isNew){
-        this.get("workflow").addKillNode(currentNode,errorNode);
-        this.get("workflow.killNodes").push(errorNode);
+      if(transition.errorNode && transition.errorNode.isNew){
+        this.get("workflow").addKillNode(currentNode,transition.errorNode);
+        this.get("workflow.killNodes").push(transition.errorNode);
       }else {
-        this.set('currentNode.errorNode', errorNode);
+        this.set('currentNode.errorNode', transition.errorNode);
       }
+      currentNode.transitions.forEach((trans)=>{
+        if(transition.okToNode){
+          if(trans.targetNode.id !== transition.okToNode.id){
+            trans.targetNode = transition.okToNode;
+            this.showUndo('transition');
+          }          
+        }
+      }, this);
     },
     submitWorkflow(){
+      this.set('dryrun', false);
+      this.openJobConfig();
+    },
+    saveWorkflow(){
+      this.set('dryrun', false);
+      this.openSaveWorkflow();
+    },
+    previewWorkflow(){
+      this.set("showingPreview",false);
       this.get('workflowContext').clearErrors();
       var 
workflowGenerator=WorkflowGenerator.create({workflow:this.get("workflow"),
       workflowContext:this.get('workflowContext')});
@@ -532,15 +637,11 @@ export default Ember.Component.extend(EmberValidations,{
       if(this.get('workflowContext').hasErrors()){
         this.set('errors',this.get('workflowContext').getErrors());
       }else{
-        var dynamicProperties = 
this.get('propertyExtractor').getDynamicProperties(workflowXml);
-        var 
configForSubmit={props:dynamicProperties,xml:workflowXml,params:this.get('workflow.parameters')};
-        this.set("workflowSubmitConfigs",configForSubmit);
-        this.set("showingWorkflowConfigProps",true);
+        this.set("previewXml",vkbeautify.xml(workflowXml));
+        this.set("showingPreview",true);
       }
-
     },
-    previewWorkflow(){
-      this.set("showingPreview",false);
+    downloadWorkflowXml(){
       this.get('workflowContext').clearErrors();
       var 
workflowGenerator=WorkflowGenerator.create({workflow:this.get("workflow"),
       workflowContext:this.get('workflowContext')});
@@ -548,12 +649,15 @@ export default Ember.Component.extend(EmberValidations,{
       if(this.get('workflowContext').hasErrors()){
         this.set('errors',this.get('workflowContext').getErrors());
       }else{
-        this.set("previewXml",vkbeautify.xml(workflowXml));
-        this.set("showingPreview",true);
+        this.workflowXmlDownload(workflowXml);
       }
     },
     closeWorkflowSubmitConfigs(){
       this.set("showingWorkflowConfigProps",false);
+      this.set("showingSaveWorkflow",false);
+    },
+    closeSaveWorkflow(){
+      this.set("showingSaveWorkflow",false);
     },
     importWorkflowTest(){
       var deferred = this.importSampleWorkflow();
@@ -563,6 +667,7 @@ export default Ember.Component.extend(EmberValidations,{
         this.rerender();
         this.doValidation();
       }.bind(this)).catch(function(e){
+        console.error(e);
       });
     },
     closeFileBrowser(){
@@ -625,7 +730,11 @@ export default Ember.Component.extend(EmberValidations,{
       this.resetZoomLevel();
       this.$("#flow-designer").css("transform", "scale(" + 1 + ")");
     },
+    resetLayout() {
+      this.flowRenderer.resetLayout();
+    },
     closeActionEditor (isSaved){
+      this.send("hideNotification");
       if(isSaved){
         this.currentNode.onSave();
         this.doValidation();
@@ -638,6 +747,27 @@ export default Ember.Component.extend(EmberValidations,{
       }
       this.set('showActionEditor', false);
       this.rerender();
+    },
+    saveDraft(){
+      this.persistWorkInProgress();
+    },
+
+    undoDelete () {
+      var workflowImporter = WorkflowJsonImporter.create({});
+      var workflow = 
workflowImporter.importWorkflow(this.get('workflowSnapshot'));
+      this.resetDesigner();
+      this.set("workflow", workflow);
+      this.rerender();
+      this.doValidation();
+      this.set('undoAvailable', false);
+    },
+
+    registerAddBranchAction(component){
+      this.set("addBranchListener",component);
+    },
+    dryRunWorkflow(){
+      this.set('dryrun', true);
+      this.openJobConfig();
     }
   }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action-info.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action-info.js
 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action-info.js
new file mode 100644
index 0000000..73cabac
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action-info.js
@@ -0,0 +1,26 @@
+/*
+*    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';
+export default Ember.Component.extend({
+       actions : {    
+         hideNotification(){
+                 this.sendAction("hideNotification");
+         }
+   }
+});
+ 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action.js
index 98a292d..03acbf3 100644
--- a/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action.js
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/fs-action.js
@@ -15,11 +15,19 @@
 *    limitations under the License.
 */
 import Ember from 'ember';
-import EmberValidations, {
-  validator
-} from 'ember-validations';
+import { validator, buildValidations } from 'ember-cp-validations';
 
-export default Ember.Component.extend(EmberValidations, {
+const Validations = buildValidations({
+  'actionModel.fsOps': {
+    validators: [
+      validator('fs-action-validator', {
+        dependentKeys: ['actionModel.fsOps.@each']
+      })
+    ]
+  }
+});
+
+export default Ember.Component.extend(Validations, {
   fileBrowser: Ember.inject.service('file-browser'),
   setUp: function() {
     if (this.get('actionModel.fsOps') === undefined) {
@@ -29,6 +37,8 @@ export default Ember.Component.extend(EmberValidations, {
       this.set("actionModel.configuration", {});
       this.set("actionModel.configuration.property", Ember.A([]));
     }
+    var field = 'validations.attrs.actionModel.fsOps.isDirty';
+    this.set(field, false);
     this.sendAction('register', 'fsAction', this);
   }.on('init'),
   initialize: function() {
@@ -41,52 +51,6 @@ export default Ember.Component.extend(EmberValidations, {
       this.$('#collapseOne').collapse('show');
     }
   }.on('didUpdate'),
-  validations: {
-    'actionModel': {
-      inline: validator(function() {
-        var isValidated = true,
-        msg = "";
-        if (!this.get('actionModel.fsOps')) {
-          return;
-        }
-        this.get('actionModel.fsOps').forEach(function(item, index) {
-          switch (item.type) {
-            case "mkdir":
-            case "delete":
-            case "touchz":
-            if (!item.settings.path) {
-              isValidated = false;
-              msg = "path is mandatory";
-            }
-            break;
-            case "chmod":
-            if (!item.settings.path) {
-              isValidated = false;
-              msg = "path and permissions are mandatory";
-            }
-            break;
-            case "chgrp":
-            if (!item.settings.path || !item.settings.group) {
-              isValidated = false;
-              msg = "path and group are mandatory";
-            }
-            break;
-            case "move":
-            if (!item.settings.source || !item.settings.target) {
-              isValidated = false;
-              msg = "source and target are mandatory";
-            }
-            break;
-          }
-        });
-        if (!isValidated) {
-          return "   ";
-        }
-
-      })
-    }
-  },
-  
   actions: {
     openFileBrowser(model, context) {
       if (undefined === context) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/fsaction-info.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/fsaction-info.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/fsaction-info.js
new file mode 100644
index 0000000..caf23b4
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/fsaction-info.js
@@ -0,0 +1,21 @@
+/**
+ * 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';
+
+export default Ember.Component.extend({
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/hdfs-browser.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/hdfs-browser.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hdfs-browser.js
index fc5fd37..c9be41a 100644
--- 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/hdfs-browser.js
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hdfs-browser.js
@@ -57,7 +57,7 @@ export default Ember.Component.extend({
     },
     createFolder(){
       var self=this;
-      var $elem=this.$("#selectedPath");
+      var $elem=this.$('input[name="selectedPath"]');
       //$elem.val($elem.val()+"/");
       var folderHint="<enter folder here>";
       this.set("selectedPath",this.get("selectedPath")+"/"+folderHint);
@@ -98,7 +98,8 @@ export default Ember.Component.extend({
       this.showNotification({
         "type": "error",
         "message": "Upload Failed",
-        "details":textStatus
+        "details":textStatus,
+        "errorThrown":errorThrown
       });
     },
     uploadProgress(e){

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action-info.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action-info.js
 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action-info.js
new file mode 100644
index 0000000..73cabac
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action-info.js
@@ -0,0 +1,26 @@
+/*
+*    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';
+export default Ember.Component.extend({
+       actions : {    
+         hideNotification(){
+                 this.sendAction("hideNotification");
+         }
+   }
+});
+ 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action.js
index 125aac3..ac85a9a 100644
--- 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action.js
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hive-action.js
@@ -16,9 +16,25 @@
 */
 
 import Ember from 'ember';
-import EmberValidations from 'ember-validations';
+import { validator, buildValidations } from 'ember-cp-validations';
 
-export default Ember.Component.extend(EmberValidations,{
+const Validations = buildValidations({
+  'actionModel.script': validator('presence', {
+    presence : true,
+    disabled(model, attribute) {
+      return !model.get('isScript');
+    },
+    dependentKeys : ['isScript']
+  }),
+  'actionModel.query': validator('presence', {
+    presence : true,
+    disabled(model, attribute) {
+      return model.get('isScript');
+    },
+    dependentKeys : ['isScript']
+  })
+});
+export default Ember.Component.extend(Validations, {
   hiveOptionObserver : Ember.observer('isScript',function(){
     if(this.get('isScript')){
       this.set("actionModel.query", undefined);
@@ -68,20 +84,6 @@ export default Ember.Component.extend(EmberValidations,{
       this.$('#collapseOne').collapse('show');
     }
   }.on('didUpdate'),
-  validations : {
-    'actionModel.script': {
-      presence: {
-        'if':'isScript',
-        'message' : 'You need to provide a value for Script'
-      }
-    },
-    'actionModel.query': {
-      presence: {
-        unless :'isScript',
-        'message' : 'You need to provide a value for Query'
-      }
-    }
-  },
   actions : {
     openFileBrowser(model, context){
       if(undefined === context){
@@ -95,9 +97,9 @@ export default Ember.Component.extend(EmberValidations,{
     },
     onHiveOptionChange(value){
       if(value === "script"){
-        this.set('isScript',true);
+        this.set('isScript', true);
       }else{
-        this.set('isScript',false);
+        this.set('isScript', false);
       }
     }
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action-info.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action-info.js
 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action-info.js
new file mode 100644
index 0000000..3192c72
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action-info.js
@@ -0,0 +1,25 @@
+/*
+*    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';
+export default Ember.Component.extend({
+       actions : {    
+         hideNotification(){
+                 this.sendAction("hideNotification");
+         }
+   }
+});
+ 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action.js
index f8b53c5..f23cca7 100644
--- 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action.js
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/hive2-action.js
@@ -16,9 +16,30 @@
 */
 
 import Ember from 'ember';
-import EmberValidations from 'ember-validations';
+import { validator, buildValidations } from 'ember-cp-validations';
 
-export default Ember.Component.extend(EmberValidations,{
+const Validations = buildValidations({
+  'actionModel.script': validator('presence', {
+    presence : true,
+    disabled(model, attribute) {
+      return !model.get('isScript');
+    },
+    dependentKeys : ['isScript']
+  }),
+  'actionModel.query': validator('presence', {
+    presence : true,
+    disabled(model, attribute) {
+      return model.get('isScript');
+    },
+    dependentKeys : ['isScript']
+  }),
+  'actionModel.jdbc-url': validator('presence', {
+    presence : true
+  })
+
+});
+
+export default Ember.Component.extend(Validations,{
   hiveOptionObserver : Ember.observer('isScript',function(){
     if(this.get('isScript')){
       this.set("actionModel.query", undefined);
@@ -68,25 +89,6 @@ export default Ember.Component.extend(EmberValidations,{
       this.$('#collapseOne').collapse('show');
     }
   }.on('didUpdate'),
-  validations : {
-    'actionModel.script': {
-      presence: {
-        'if':'isScript',
-        'message' : 'You need to provide a value for Script'
-      }
-    },
-    'actionModel.query': {
-      presence: {
-        unless :'isScript',
-        'message' : 'You need to provide a value for Query'
-      }
-    },
-    'actionModel.jdbc-url': {
-      presence: {
-        'message' : 'You need to provide a value for jdbc url'
-      }
-    }
-  },
   actions : {
     openFileBrowser(model, context){
       if(undefined === context){

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/info-header.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/info-header.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/info-header.js
new file mode 100644
index 0000000..73cabac
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/info-header.js
@@ -0,0 +1,26 @@
+/*
+*    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';
+export default Ember.Component.extend({
+       actions : {    
+         hideNotification(){
+                 this.sendAction("hideNotification");
+         }
+   }
+});
+ 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/instance-list-config.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/instance-list-config.js
 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/instance-list-config.js
new file mode 100644
index 0000000..c46a37f
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/instance-list-config.js
@@ -0,0 +1,54 @@
+/*
+*    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';
+
+export default Ember.Component.extend({
+  multivalued : true,
+  instance : {
+    value : '',
+    displayValue : '',
+    type : 'date'
+  },
+  initialize : function(){
+    this.sendAction('register', this, this);
+    this.on('bindInputPlaceholder',function () {
+      this.set('addUnboundValue', true);
+    }.bind(this));
+  }.on('init'),
+  bindInputPlaceholder : function () {
+    if(this.get('addUnboundValue') && !Ember.isBlank(this.get('instance'))){
+      this.addinstance();
+    }
+  }.on('willDestroyElement'),
+  addInstance (){
+    this.get('instances').pushObject(Ember.copy(this.get('instance')));
+    this.set('instance', {
+      value : '',
+      displayValue : '',
+      type : 'date'
+    });
+  },
+  actions : {
+    addInstance () {
+      this.addInstance();
+    },
+    deleteInstance (index) {
+      this.get('instances').removeAt(index);
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action-info.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action-info.js
 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action-info.js
new file mode 100644
index 0000000..3192c72
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action-info.js
@@ -0,0 +1,25 @@
+/*
+*    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';
+export default Ember.Component.extend({
+       actions : {    
+         hideNotification(){
+                 this.sendAction("hideNotification");
+         }
+   }
+});
+ 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action.js
index 873c284..9d43fe7 100644
--- 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action.js
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/java-action.js
@@ -16,9 +16,18 @@
 */
 
 import Ember from 'ember';
-import EmberValidations from 'ember-validations';
+import { validator, buildValidations } from 'ember-cp-validations';
 
-export default Ember.Component.extend(EmberValidations, {
+const Validations = buildValidations({
+  'actionModel.mainClass': validator('presence', {
+    presence : true
+  }),
+  'actionModel.jobTracker': validator('presence', {
+    presence : true
+  })  
+});
+
+export default Ember.Component.extend(Validations, {
   fileBrowser : Ember.inject.service('file-browser'),
   javaOptsObserver : Ember.observer('isSingle',function(){
     if(this.get('isSingle')){
@@ -67,18 +76,6 @@ export default Ember.Component.extend(EmberValidations, {
       this.$('#collapseOne').collapse('show');
     }
   }.on('didUpdate'),
-  validations : {
-    'actionModel.mainClass': {
-      presence: {
-        'message' : 'You need to provide a value for Main Class',
-      },
-      format: {
-        with: /([a-z][a-z_0-9]*\.)*[A-Za-z_]($[A-Za-z_]|[\w_])*/,
-        allowBlank: false,
-        message: 'You need to provide a valid value'
-      }
-    }
-  },
   actions : {
     openFileBrowser(model, context){
       if(undefined === context){

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/job-config.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/job-config.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/job-config.js
new file mode 100644
index 0000000..4fa2666
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/job-config.js
@@ -0,0 +1,303 @@
+/*
+*    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 Constants from '../utils/constants';
+import { validator, buildValidations } from 'ember-cp-validations';
+
+const Validations = buildValidations({
+  'filePath': validator('presence', {
+    presence : true
+  }),
+  'configMap': {
+    validators: [
+      validator('job-params-validator', {
+        dependentKeys: ['[email protected]', 'showErrorMessage']
+      })
+    ]
+  }
+});
+
+
+export default Ember.Component.extend(Validations, {
+  systemConfigs : Ember.A([]),
+  showingFileBrowser : false,
+  jobXml : "",
+  overwritePath : false,
+  configMap : Ember.A([]),
+  configPropsExists : false,
+  savingInProgress : false,
+  isStackTraceVisible: false,
+  isStackTraceAvailable: false,
+  alertType : "",
+  alertMessage : "",
+  alertDetails : "",
+  filePath : "",
+  showErrorMessage: false,
+  displayName : Ember.computed('type', function(){
+    if(this.get('type') === 'wf'){
+      return "Workflow";
+    }else if(this.get('type') === 'coord'){
+      return "Coordinator";
+    }else{
+      return "Bundle";
+    }
+  }),
+  initialize :function(){
+    this.set("configPropsExists", this.get("jobConfigs").props.size>0);
+    var configProperties = [];
+    configProperties.pushObjects(this.extractJobParams());
+    configProperties.pushObjects(this.extractJobProperties());
+    this.configureExecutionSettings();
+    this.set("configMap", configProperties);
+    this.set("jobXml", this.get("jobConfigs").xml);
+    this.set('filePath', Ember.copy(this.get('jobFilePath')));
+    Object.keys(this.get('validations.attrs')).forEach((attr)=>{
+    var field = 'validations.attrs.'+attr+'.isDirty';
+    this.set(field, false);
+    }, this);
+  }.on('init'),
+  rendered : function(){
+    this.$("#configureJob").on('hidden.bs.modal', function () {
+      this.sendAction('closeJobConfigs');
+    }.bind(this));
+    this.$("#configureJob").modal("show");    
+  }.on('didInsertElement'),
+  extractJobParams(){
+    var params = [];
+    var jobParams = this.get("jobConfigs").params;
+    if(jobParams && jobParams.configuration && 
jobParams.configuration.property){
+      jobParams.configuration.property.forEach((param)=>{
+        if(param && !param.value){
+          var prop= Ember.Object.create({
+            name: param.name,
+            value: null,
+            isRequired : true
+          });
+          params.push(prop);
+        }
+      });
+    }
+    return params;
+  },
+
+  extractJobProperties(){
+    var jobProperties = [];
+    var jobParams = this.get("jobConfigs").params;
+    this.get("jobConfigs").props.forEach(function(value) {
+      if (value!== Constants.defaultNameNodeValue && 
value!==Constants.rmDefaultValue){
+        var propName = value.trim().substring(2, value.length-1);
+        var isRequired = true;
+        if(jobParams && jobParams.configuration && 
jobParams.configuration.property){
+          var param = jobParams.configuration.property.findBy('name', 
propName);
+          if(param && param.value){
+            isRequired = false;
+          }else {
+            isRequired = true;
+          }
+        }
+        var prop= Ember.Object.create({
+          name: propName,
+          value: null,
+          isRequired : isRequired
+        });
+        jobProperties.push(prop);
+      }
+    });
+    return jobProperties;
+  },
+  configureExecutionSettings (){
+    this.set('systemConfigs', Ember.A([]));
+    if(this.get('type') !== 'coord' && !this.get('isDryrun')){
+      this.get('systemConfigs').pushObject({displayName: 'Run on submit', name 
: 'runOnSubmit', value: false});
+    }
+    this.get('systemConfigs').pushObjects([
+      {displayName: 'Use system lib path', name :'useSystemLibPath', 
value:true},
+      {displayName: 'Rerun on Failure', name : 'rerunOnFailure', value:true}
+    ]);
+  },
+  showNotification(data){
+    if (!data){
+      return;
+    }
+    if (data.type === "success"){
+      this.set("alertType", "success");
+    }
+    if (data.type === "error"){
+      this.set("alertType", "danger");
+    }
+    this.set("alertDetails", data.details);
+    this.set("alertMessage", data.message);
+    if(data.stackTrace.length){
+      this.set("stackTrace", data.stackTrace);
+      this.set("isStackTraceAvailable", true);
+    } else {
+      this.set("isStackTraceAvailable", false);
+    }
+  },
+  prepareJobForSubmission(isDryrun){
+    if(this.get('validations.isInvalid')){
+      return;
+    };
+    this.set('jobFilePath', Ember.copy(this.get('filePath')));
+    var url = Ember.ENV.API_URL + "/submitJob?app.path=" + 
this.get("filePath") + "&overwrite=" + this.get("overwritePath");
+    url = url + "&jobType=" + this.get('displayName').toUpperCase();
+    var submitConfigs = this.get("configMap");
+    submitConfigs.forEach(function(item) {
+      url = url + "&config." + item.name + "=" + item.value;
+    }, this); 
+    this.get('systemConfigs').forEach((config)=>{
+      if(config.name === 'runOnSubmit' && !isDryrun){
+        url = url + "&oozieparam.action=start";
+      }else if(config.name !== 'runOnSubmit'){
+        url = url + "&oozieconfig." + config.name + "=" + config.value;
+      }
+    });
+    if(isDryrun){
+      url = url + "&oozieparam.action=dryrun";
+    }
+    if ( this.get("jobConfigs").props.has("${resourceManager}")){
+      url= url + "&resourceManager=useDefault";
+    }
+    this.set("savingInProgress", true);
+    this.submitJob(url);
+  },
+  submitJob(url){
+    Ember.$.ajax({
+      url: url,
+      method: "POST",
+      dataType: "text",
+      contentType: "text/plain;charset=utf-8",
+      beforeSend: function(request) {
+        request.setRequestHeader("X-XSRF-HEADER", 
Math.round(Math.random()*100000));
+        request.setRequestHeader("X-Requested-By", "workflow-designer");
+      },
+      data: this.get("jobXml"),
+      success: function(response) {
+        var result=JSON.parse(response);
+        this.showNotification({
+          "type": "success",
+          "message": this.get('displayName') +" saved.",
+          "details": "Job id :"+result.id
+        });
+        this.set("savingInProgress",false);
+      }.bind(this),
+      error: function(response) {
+        console.log(response);
+        this.set("savingInProgress",false);
+        this.set("isStackTraceVisible",true);
+        this.showNotification({
+          "type": "error",
+          "message": "Error occurred while saving "+ 
this.get('displayName').toLowerCase(),
+          "details": this.getParsedErrorResponse(response),
+          "stackTrace": this.getStackTrace(response.responseText)
+        });
+      }.bind(this)
+    });
+  },
+  getStackTrace(data){
+    if(data){
+     try{
+      var stackTraceMsg = JSON.parse(data).stackTrace;
+      if(!stackTraceMsg){
+        return "";
+      }
+     if(stackTraceMsg instanceof Array){
+       return stackTraceMsg.join("").replace(/\tat /g, 
'<br/>&nbsp;&nbsp;&nbsp;&nbsp;at&nbsp;');
+     } else {
+       return stackTraceMsg.replace(/\tat /g, 
'<br/>&nbsp;&nbsp;&nbsp;&nbsp;at&nbsp;');
+     }
+     } catch(err){
+       return "";
+     }
+    }
+    return "";
+  },
+  startJob (jobId){
+    this.set('startingInProgress', true);
+    var url = [Ember.ENV.API_URL,
+      "/v2/job/", jobId, "?action=", 'start','&user.name=oozie'
+    ].join("");
+    Ember.$.ajax({
+      url: url,
+      method: 'PUT',
+      beforeSend: function (xhr) {
+        xhr.setRequestHeader("X-XSRF-HEADER", 
Math.round(Math.random()*100000));
+        xhr.setRequestHeader("X-Requested-By", "Ambari");
+      }
+    }).done(function(){
+      this.set('startingInProgress', false);
+      this.showNotification({
+        "type": "success",
+        "message": this.get('displayName')+" Started",
+        "details": jobId
+      });
+    }.bind(this)).fail(function(response){
+      this.set('startingInProgress', false);
+      this.showNotification({
+        "type": "error",
+        "message": "Error occurred while starting "+ 
this.get('displayName').toLowerCase(),
+        "details": this.getParsedErrorResponse(response),
+        "stackTrace": this.getStackTrace(response.responseText)
+      });
+    }.bind(this));
+  },
+  getParsedErrorResponse (response){
+    var detail;
+    if (response.responseText && response.responseText.charAt(0)==="{"){
+      var jsonResp=JSON.parse(response.responseText);
+      if (jsonResp.status==="workflow.oozie.error"){
+        detail="Oozie error. Please check the workflow.";
+      }else if(jsonResp.message && jsonResp.message.indexOf("<html>") > -1){
+        detail= "";
+      }else{
+        detail=jsonResp.message;
+      }
+    }else{
+      detail=response; 
+    }
+    return detail;
+  },
+  actions: {
+    selectFile(){
+      this.set("showingFileBrowser",true);
+    },
+    showStackTrace(){
+      this.set("isStackTraceVisible", true);
+    },
+    hideStackTrace(){
+      this.set("isStackTraceVisible", false);
+    },
+    closeFileBrowser(){
+      this.set("showingFileBrowser",false);
+    },
+    dryrun(){
+      this.set('showErrorMessage', true);
+      this.prepareJobForSubmission(true);
+    },
+    save(){
+      this.set('showErrorMessage', true);
+      this.prepareJobForSubmission(false);
+    },
+    previewXml(){
+      this.set("showingPreview",true);
+    },
+    closePreview(){
+      this.set("showingPreview",false);
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/job-details.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/job-details.js 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/job-details.js
index ce78e59..e403dc4 100644
--- 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/job-details.js
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/job-details.js
@@ -16,8 +16,12 @@
 */
 
 import Ember from 'ember';
+import {WorkflowImporter} from '../domain/workflow-importer';
+import {ActionTypeResolver} from "../domain/action-type-resolver";
 
 export default Ember.Component.extend({
+  workflowImporter: WorkflowImporter.create({}),
+  actionTypeResolver: ActionTypeResolver.create({}),
   error : {},
   errorMessage : Ember.computed('error', function() {
     if(this.get('error').status === 400){
@@ -57,85 +61,342 @@ export default Ember.Component.extend({
   }),
   initialize : function(){
     if(this.get('currentTab')){
-      this.$('.nav-tabs 
a[href="'+this.get('currentTab').attr("href")+'"]').tab('show');
+      this.$('.nav-tabs 
a[href="'+this.get('currentTab').attr("href")+'"]').click();
       if(this.get('model.actions')){
         this.set('model.actionDetails', this.get('model.actions')[0]);
       }
     }
+
+    var x2js = new X2JS();
+    var configurationObj  = x2js.xml_str2json(this.get('model.conf'));
+    this.set('model.configurationProperties', 
configurationObj.configuration.property);
+
     this.$('.nav-tabs').on('shown.bs.tab', function(event){
       this.sendAction('onTabChange', this.$(event.target));
     }.bind(this));
   }.on('didInsertElement'),
-  actions : {
-    back (){
-      this.sendAction('back');
-    },
-    close : function(){
-      this.sendAction('close');
-    },
-    doRefresh : function(){
-      this.sendAction('doRefresh');
-    },
-    getJobDefinition : function () {
-      
Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=definition&timezone=GMT',function(response){
-        this.set('model.jobDefinition', (new 
XMLSerializer()).serializeToString(response).trim());
-      }.bind(this)).fail(function(error){
-        this.set('error',error);
-      }.bind(this));
-    },
-    showFirstActionDetail : function(){
-      this.set('model.actionDetails', this.get('model.actions')[0]);
-    },
-    getJobLog : function (params){
-      var url = Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=log';
-      if(params && params.logFilter){
-        url = url + '&logfilter=' + params.logFilter;
+
+  getShape(nodeType) {
+    switch(nodeType) {
+      case 'start' :
+      case 'end' :
+      case 'kill' :
+      case 'fork' :
+      case 'join' :
+      return 'ellipse';
+      case 'action' :
+      return 'roundrectangle';
+      case 'decision' :
+      return 'diamond';
+      default :
+      return 'star';
+    }
+  },
+
+  getNodeActionByName(workflowActions, nodeName) {
+    if (Ember.isArray(workflowActions)) {
+      var actionInfo = workflowActions.filter(function (modelAction) {
+        return modelAction.name === nodeName;
+      });
+      return actionInfo.length>0 ? actionInfo[0] : null;
+    } else {
+      return workflowActions;
+    }
+  },
+
+  getActionStatus(nodeName, nodeType) {
+    if (nodeType === 'start') {
+      nodeName = ':start:';
+    }
+    var nodeAction = this.getNodeActionByName(this.get('model.actions'), 
nodeName);
+
+    if (nodeAction) {
+      return nodeAction.status;
+    }
+    return "Not-Visited";
+  },
+
+  getNodeActionType(workflowXmlString, nodeType, nodeName) {
+    if (nodeType !== 'action') {
+      return nodeType;
+    }
+
+    var workflowJson = new X2JS().xml_str2json(workflowXmlString);
+
+    if (Ember.isArray(workflowJson['workflow-app'].action)) {
+      var workflowActionJson = 
workflowJson['workflow-app'].action.filter(function (workflowAction) {
+        return workflowAction._name === nodeName;
+      });
+      if (workflowActionJson.length>0) {
+        return this.actionTypeResolver.getActionType(workflowActionJson[0]);
       }
-      if(params && params.logActionList){
-        url = url + '&type=action&scope='+ params.logActionList;
+    } else {
+      return 
this.actionTypeResolver.getActionType(workflowJson['workflow-app'].action);
+    }
+    return '';
+  },
+
+  getBgColorBasedOnStatus(nodeStatus) {
+    switch(nodeStatus) {
+      case 'Not-Visited' :
+      return '#ffffff';
+      default :
+      return '#e6e6e6';
+    }
+  },
+
+  getFontColorBasedOnStatus(nodeStatus) {
+    switch(nodeStatus) {
+      case 'ERROR' :
+      case 'KILLED' :
+      case 'FAILED' :
+      return '#a94442';
+      default :
+      return '#262626';
+    }
+  },
+
+  getBorderColorBasedOnStatus(nodeStatus) {
+    switch(nodeStatus) {
+      case 'OK' :
+        return '#5bb75b';
+      case 'ERROR' :
+      case 'KILLED' :
+      case 'FAILED' :
+        return '#a94442';
+      default :
+        return '#808080';
+    }
+  },
+
+  getNodeLabelContent(nodeType) {
+    switch(nodeType) {
+      case 'fork' :
+      return '\uf0e8';
+      case 'join' :
+      return '\uf0e8';
+      default :
+      return '';
+    }
+  },
+
+  getCyDataNodes(workflow){
+    var dataNodes = [];
+    var self=this;
+    workflow.nodeVisitor.process(workflow.startNode, function(node) {
+      if (node.type === 'kill') {
+        return;
       }
-      Ember.$.get(url,function(response){
-        this.set('model.jobLog', response);
-      }.bind(this)).fail(function(error){
-        this.set('error', error);
-      }.bind(this));
+      var nodeActionStatus = self.getActionStatus(node.name, node.type);
+      dataNodes.push({ data:
+        { id: node.id, name: node.name, type: node.type,
+        content: node.name + self.getNodeLabelContent(node.type),
+        shape: self.getShape(node.type),
+        bgColor: self.getBgColorBasedOnStatus(nodeActionStatus),
+        fontColor: self.getFontColorBasedOnStatus(nodeActionStatus),
+        borderColor: self.getBorderColorBasedOnStatus(nodeActionStatus) }
+      });
+        if (node.transitions.length > 0) {
+          node.transitions.forEach(function(tran){
+            if (tran.targetNode.type === 'kill') {
+              return;
+            }
+            dataNodes.push(
+              {
+                data: {
+                  id: tran.sourceNodeId + '_to_' + tran.targetNode.id,
+                  source:tran.sourceNodeId,
+                  target: tran.targetNode.id,
+                  borderColor: (self.getActionStatus(tran.targetNode.name, 
tran.targetNode.type) === 'Not-Visited')
+                    ? '#808080' : 
self.getBorderColorBasedOnStatus(nodeActionStatus)
+                }
+              }
+            );
+          });
+        }
+      });
+      return dataNodes;
     },
-    getErrorLog : function (){
-      
Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=errorlog',function(response){
-        this.set('model.errorLog', response);
-      }.bind(this)).fail(function(error){
-        this.set('error', error);
-      }.bind(this));
+    showActionNodeDetail(node, workflowXmlString){
+      var nodeName = node.data().name;
+      if (nodeName === 'Start') {
+        nodeName = ':start:';
+      }
+      this.set('model.nodeName', nodeName);
+      this.set('model.nodeType', this.getNodeActionType(workflowXmlString, 
node.data().type, nodeName));
+      this.set('model.actionDetails', null);
+      var actionInfo = this.getNodeActionByName(this.get('model.actions'), 
nodeName);
+      this.set('model.actionInfo', actionInfo);
     },
-    getAuditLog : function (){
-      
Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=auditlog',function(response){
-        this.set('model.auditLog', response);
-      }.bind(this)).fail(function(error){
-        this.set('error', error);
+    renderDag(xmlString){
+      var workflow = this.get("workflowImporter").importWorkflow(xmlString);
+      console.log("Workflow Object..", workflow);
+      var dataNodes=this.getCyDataNodes(workflow);
+      var cy = cytoscape({
+        container: document.getElementById('cy'),
+        elements: dataNodes,
+        style: [
+          {
+            selector: 'node',
+            style: {
+              shape: 'data(shape)',
+              'color': 'data(fontColor)',
+              'background-color': 'data(bgColor)',
+              'border-width': 1,
+              'border-color': 'data(borderColor)',
+              label: 'data(name)',
+              'text-valign': 'center',
+              'font-size': 8
+            }
+          },
+          {
+            selector: 'node[shape = "roundrectangle"]',
+            style: {
+              width: 100,
+              'border-radius': 1,
+            }
+          },
+          {
+            selector: 'node[type = "fork"]',
+            style: {
+              'background-image': 'assets/sitemap.png',
+              'text-halign': 'left'
+            }
+          },
+          {
+            selector: 'node[type = "join"]',
+            style: {
+              'background-image': 'assets/join.png',
+              'text-halign': 'left'
+            }
+          },
+          {
+            selector: 'edge',
+            style: {
+              width: 1,
+              'line-color': 'data(borderColor)',
+              'curve-style': 'bezier',
+                               'target-arrow-shape': 'triangle',
+              'target-arrow-color': 'data(borderColor)'
+            }
+          }
+        ],
+        layout: {
+          name: 'dagre'
+        }
+      });
+
+      // the default values of each option are outlined below:
+      var defaults = {
+        zoomFactor: 2.0, // zoom factor per zoom tick
+        minZoom: 0.1, // min zoom level
+        maxZoom: 10, // max zoom level
+
+        // icon class names
+        sliderHandleIcon: 'fa fa-minus',
+        zoomInIcon: 'fa fa-plus',
+        zoomOutIcon: 'fa fa-minus',
+        resetIcon: 'fa fa-expand'
+      };
+
+      cy.panzoom( defaults );
+
+      cy.on('click', 'node', function(event) {
+        var node = event.cyTarget;
+        this.showActionNodeDetail(node, xmlString);
       }.bind(this));
     },
-    getJobDag : function (){
-      this.set('model.jobDag', 
Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=graph');
+    importSampleWorkflow (){
+      var self=this;
+      Ember.$.ajax({
+        url: "/sampledata/workflow.xml",
+        dataType: "text",
+        cache:false,
+        success: function(data) {
+          self.renderDag(data);
+        }.bind(this),
+        failure : function(data){
+          console.error(data);
+        }
+      });
     },
-    getCoordActionReruns : function () {
-      var url = 
Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=allruns&type=action';
-      if(this.get('rerunActionList')){
-        url = url + '&scope=' + this.get('rerunActionList');
+    actions : {
+      back (){
+        this.sendAction('back');
+      },
+      close : function(){
+        this.sendAction('close');
+      },
+      doRefresh : function(){
+        this.sendAction('doRefresh');
+      },
+      getJobDefinition : function () {
+        
Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=definition&timezone=GMT',function(response){
+          this.set('model.jobDefinition', (new 
XMLSerializer()).serializeToString(response).trim());
+        }.bind(this)).fail(function(error){
+          this.set('error',error);
+        }.bind(this));
+      },
+      showFirstActionDetail : function(){
+        this.set('model.actionDetails', this.get('model.actions')[0]);
+      },
+      getJobLog : function (params){
+        var url = Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=log';
+        if(params && params.logFilter){
+          url = url + '&logfilter=' + params.logFilter;
+        }
+        if(params && params.logActionList){
+          url = url + '&type=action&scope='+ params.logActionList;
+        }
+        Ember.$.get(url,function(response){
+          this.set('model.jobLog', response);
+        }.bind(this)).fail(function(error){
+          this.set('error', error);
+        }.bind(this));
+      },
+      getErrorLog : function (){
+        
Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=errorlog',function(response){
+          this.set('model.errorLog', response);
+        }.bind(this)).fail(function(error){
+          this.set('error', error);
+        }.bind(this));
+      },
+      getAuditLog : function (){
+        
Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=auditlog',function(response){
+          this.set('model.auditLog', response);
+        }.bind(this)).fail(function(error){
+          this.set('error', error);
+        }.bind(this));
+      },
+      getJobDag : function (){
+        //if (true) return this.importSampleWorkflow();
+        
Ember.$.get(Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=definition&timezone=GMT',function(response){
+          var xmlString = (new 
XMLSerializer()).serializeToString(response).trim();
+          this.renderDag(xmlString);
+        }.bind(this)).fail(function(error){
+          this.set('error',error);
+        }.bind(this));
+        // this.set('model.jobDag', 
Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=graph');
+      },
+      getCoordActionReruns : function () {
+        var url = 
Ember.ENV.API_URL+'/v2/job/'+this.get('id')+'?show=allruns&type=action';
+        if(this.get('rerunActionList')){
+          url = url + '&scope=' + this.get('rerunActionList');
+        }
+        Ember.$.get(url, function(response){
+          this.set('model.coordActionReruns', response.workflows);
+        }.bind(this)).fail(function(error){
+          this.set('error', error);
+        }.bind(this));
+      },
+      getActionDetails : function (actionInfo) {
+        this.set('model.actionDetails', actionInfo);
+      },
+      showWorkflow : function(workflowId){
+        this.sendAction('showWorkflow', workflowId);
+      },
+      showCoord : function(coordId){
+        this.sendAction('showCoord', coordId);
       }
-      Ember.$.get(url, function(response){
-        this.set('model.coordActionReruns', response.workflows);
-      }.bind(this)).fail(function(error){
-        this.set('error', error);
-      }.bind(this));
-    },
-    getActionDetails : function (actionInfo) {
-      this.set('model.actionDetails', actionInfo);
-    },
-    showWorkflow : function(workflowId){
-      this.sendAction('showWorkflow', workflowId);
-    },
-    showCoord : function(coordId){
-      this.sendAction('showCoord', coordId);
     }
-  }
-});
+  });

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/killnode-config.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/killnode-config.js
 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/killnode-config.js
new file mode 100644
index 0000000..cc9eb1e
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/killnode-config.js
@@ -0,0 +1,29 @@
+/*
+*    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';
+
+export default Ember.Component.extend({
+  actions: {
+    createKillNode() {
+      this.sendAction('createKillNode');
+    },
+
+    updateNode() {
+      this.sendAction('updateNode');
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/killnode-manager.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/killnode-manager.js
 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/killnode-manager.js
new file mode 100644
index 0000000..02660be
--- /dev/null
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/killnode-manager.js
@@ -0,0 +1,62 @@
+/*
+*    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';
+
+export default Ember.Component.extend({
+  initialize : function() {
+    this.set('killNode', {});
+    this.set('editMode', false);
+  }.on('init'),
+
+  rendered : function(){
+    this.$('#killnode-manager-dialog').modal({
+      backdrop: 'static',
+      keyboard: false
+    });
+    this.$('#killnode-manager-dialog').modal('show');
+    this.$('#killnode-manager-dialog').modal().on('hidden.bs.modal', 
function() {
+      this.sendAction('closeKillNodeManager');
+    }.bind(this));
+  }.on('didInsertElement'),
+  actions: {
+    deleteNode(index) {
+      this.get('killNodes').removeAt(index);
+      this.set('editMode', false);
+    },
+    addNode() {
+      this.set('createKillnodeError',null);
+      this.set('editMode', false);
+      this.set('addKillNodeMode', true);
+      this.set('killNode', {});
+    },
+    editNode(index) {
+      this.set('createKillnodeError', null);
+      this.set('editMode', true);
+      this.set('currentKillNodeIndex', index);
+      var selectedKillNode = this.get('killNodes').objectAt(index);
+      this.set('killNode', {name : selectedKillNode.name, killMessage: 
selectedKillNode.killMessage});
+    },
+    updateNode(){
+      this.set('editMode', false);
+      
this.get('killNodes').objectAt(this.get('currentKillNodeIndex')).set('killMessage',
 this.get('killNode').killMessage);
+      this.set('killNode', {});
+    },
+    createKillNode() {
+      this.sendAction('createKillNode', this.get("killNode"));
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/map-red-action.js
----------------------------------------------------------------------
diff --git 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/map-red-action.js
 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/map-red-action.js
index c3782a3..3eb8545 100644
--- 
a/contrib/views/wfmanager/src/main/resources/ui/app/components/map-red-action.js
+++ 
b/contrib/views/wfmanager/src/main/resources/ui/app/components/map-red-action.js
@@ -16,9 +16,8 @@
 */
 
 import Ember from 'ember';
-import EmberValidations from 'ember-validations';
 
-export default Ember.Component.extend(EmberValidations, {
+export default Ember.Component.extend({
   hasStaticProps : true,
   fileBrowser : Ember.inject.service('file-browser'),
   staticProps : Ember.A([]),
@@ -65,9 +64,6 @@ export default Ember.Component.extend(EmberValidations, {
     }.bind(this));
     this.sendAction('register','mapRedAction', this);
   }.on('didInsertElement'),
-  validations : {
-
-  },
   observeError :function(){
     if(this.$('#collapseOne label.text-danger').length > 0 && 
!this.$('#collapseOne').hasClass("in")){
       this.$('#collapseOne').collapse('show');

Reply via email to