You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2016/10/28 17:45:51 UTC

[12/38] ambari git commit: AMBARI-18691. Improve and Update Workflow designer to support coordinators and bundles. (Belliraj HB via dipayanb)

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/conditional-data-input.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/conditional-data-input.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/conditional-data-input.js
new file mode 100644
index 0000000..83d8a8e
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/conditional-data-input.js
@@ -0,0 +1,78 @@
+/*
+*    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 { validator, buildValidations } from 'ember-cp-validations';
+
+const Validations = buildValidations({
+  'condition.operator': validator('presence', {
+    presence : true
+  }),
+  'condition.operands': {
+    validators: [
+      validator('operand-length', {
+        min : 2,
+        dependentKeys: ['condition.operands.[]','condition.operator']
+      })
+    ]
+  }
+});
+
+export default Ember.Component.extend(Validations, {
+  initialize : function(){
+    this.set('conditionsList', Ember.A([]));
+    this.get('conditionsList').pushObjects([
+      {value : 'and', displayName: 'All'},
+      {value : 'or', displayName: 'Any'}
+    ]);
+    if(!this.get('isToplevel')){
+      this.get('conditionsList').pushObject({value : 'combine', displayName: 'Combine'});
+    }
+    this.sendAction('register', this, this);
+  }.on('init'),
+  onDestroy : function(){
+    this.sendAction('deregister', this);
+  }.on('willDestroyElement'),
+  actions : {
+    registerChild (key, context){
+      this.sendAction('register', key, context);
+    },
+    deregisterChild(key){
+      this.sendAction('deregister', key);
+    },
+    addCondition(){
+      if(!this.get('condition.operands')){
+        this.set('condition.operands', Ember.A([]));
+      }
+      this.get('condition.operands').pushObject({operands : Ember.A([]), type:'condition'});
+    },
+    addDataInput(){
+      if(!this.get('condition.operands')){
+        this.set('condition.operands', Ember.A([]));
+      }
+      this.get('condition.operands').pushObject({type:'dataInput'});
+    },
+    deleteOperand(index){
+      this.get('condition.operands').removeAt(index);
+    },
+    showAdvanced(){
+      this.set('showAdvanced', true);
+    },
+    hideAdvanced(){
+      this.set('showAdvanced', false);
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/confirmation-dialog.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/confirmation-dialog.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/confirmation-dialog.js
new file mode 100644
index 0000000..6f726f0
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/confirmation-dialog.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 : {
+    onOk (){
+      this.sendAction('onOk');
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/coord-config.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/coord-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/coord-config.js
new file mode 100644
index 0000000..57fcdf8
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/coord-config.js
@@ -0,0 +1,521 @@
+/*
+*    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 {Coordinator} from '../domain/coordinator/coordinator';
+import {CoordinatorGenerator} from '../domain/coordinator/coordinator-xml-generator';
+import {CoordinatorXmlImporter} from '../domain/coordinator/coordinator-xml-importer';
+import {SlaInfo} from '../domain/sla-info';
+import Constants from '../utils/constants';
+import { validator, buildValidations } from 'ember-cp-validations';
+
+const Validations = buildValidations({
+  'coordinator.name': validator('presence', {
+    presence : true
+  }),
+  'coordinator.workflow.appPath': validator('presence', {
+    presence : true
+  }),
+  'coordinator.frequency.value': validator('presence', {
+    presence : true
+  }),
+  'coordinator.frequency.type': validator('presence', {
+    presence : true
+  }),
+  'coordinator.timezone': validator('presence', {
+    presence : true
+  })
+});
+
+export default Ember.Component.extend(Validations, Ember.Evented, {
+  coordinator : null,
+  childComponents : new Map(),
+  fileBrowser : Ember.inject.service('file-browser'),
+  propertyExtractor : Ember.inject.service('property-extractor'),
+  workspaceManager : Ember.inject.service('workspace-manager'),
+  showErrorMessage: Ember.computed.alias('saveAttempted'),
+  datasetsForInputs : Ember.computed('coordinator.datasets.[]','coordinator.dataOutputs.[]',function(){
+    var datasetsForInputs = Ember.copy(this.get('coordinator.datasets'));
+    this.get('coordinator.dataOutputs').forEach((dataOutput)=>{
+      var existing = datasetsForInputs.findBy('name', dataOutput.dataset);
+      if(existing){
+        datasetsForInputs = datasetsForInputs.without(existing);
+      }
+    }.bind(this));
+    return datasetsForInputs;
+  }),
+  datasetsForOutputs : Ember.computed('coordinator.datasets.[]','coordinator.dataInputs.[]',function(){
+    var datasetsForOutputs = Ember.copy(this.get('coordinator.datasets'));
+    this.get('coordinator.dataInputs').forEach((dataInput)=>{
+      var existing = datasetsForOutputs.findBy('name', dataInput.dataset);
+      if(existing){
+        datasetsForOutputs = datasetsForOutputs.without(existing);
+      }
+    }.bind(this));
+    return datasetsForOutputs;
+  }),
+  onDestroy : function(){
+    Ember.run.cancel(this.schedulePersistWorkInProgress);
+    this.persistWorkInProgress();
+  }.on('willDestroyElement'),
+  initialize : function(){
+    var draftCoordinator = this.get('workspaceManager').restoreWorkInProgress(this.get('tabInfo.id'));
+    if(draftCoordinator){
+      this.set('coordinator', JSON.parse(draftCoordinator));
+    }else{
+      this.set('coordinator', this.createNewCoordinator());
+    }
+    this.set('timeUnitOptions',Ember.A([]));
+    this.get('timeUnitOptions').pushObject({value:'',displayName:'Select'});
+    this.get('timeUnitOptions').pushObject({value:'months',displayName:'Months'});
+    this.get('timeUnitOptions').pushObject({value:'endOfMonths',displayName:'End of Months'});
+    this.get('timeUnitOptions').pushObject({value:'days',displayName:'Days'});
+    this.get('timeUnitOptions').pushObject({value:'endOfDays',displayName:'End of Days'});
+    this.get('timeUnitOptions').pushObject({value:'hours',displayName:'Hours'});
+    this.get('timeUnitOptions').pushObject({value:'minutes',displayName:'Minutes'});
+    this.get('timeUnitOptions').pushObject({value:'cron',displayName:'Cron'});
+    this.set('coordinator.slaInfo', SlaInfo.create({}));
+
+    this.get('fileBrowser').on('fileBrowserOpened',function(context){
+      this.get('fileBrowser').setContext(context);
+    }.bind(this));
+    this.on('fileSelected',function(fileName){
+      this.set(this.get('filePathModel'), fileName);
+    }.bind(this));
+    this.set('coordinatorControls',[
+      {'name':'timeout', 'displayName':'Timeout', 'value':''},
+      {'name':'concurrency', 'displayName':'Concurrency', 'value':''},
+      {'name':'execution', 'displayName':'Execution', 'value':''},
+      {'name':'throttle', 'displayName':'Throttle', 'value':''}
+    ]);
+    this.set('timezoneList', Ember.copy(Constants.timezoneList));
+    if(Ember.isBlank(this.get('coordinator.name'))){
+      this.set('coordinator.name', Ember.copy(this.get('tabInfo.name')));
+    }
+    this.schedulePersistWorkInProgress();
+  }.on('init'),
+  conditionalDataInExists :false,
+  elementsInserted : function(){
+    this.$("input[name=dataInputType][value=" + this.get('coordinator.dataInputType') + "]").prop('checked','checked');
+  }.on('didInsertElement'),
+  observeXmlAppPath : Ember.observer('xmlAppPath', function(){
+    if(!this.get('xmlAppPath') || null === this.get('xmlAppPath')){
+      return;
+    } else {
+      this.showExistingWorkflow();
+    }
+  }),
+  observeFilePath : Ember.observer('coordinatorFilePath', function(){
+    if(!this.get('coordinatorFilePath') || null === this.get('coordinatorFilePath')){
+      return;
+    }else{
+      this.sendAction('changeFilePath', this.get('tabInfo'), this.get('coordinatorFilePath'));
+    }
+  }),
+  nameObserver : Ember.observer('coordinator.name', function(){
+    if(!this.get('coordinator')){
+      return;
+    }else if(this.get('coordinator') && Ember.isBlank(this.get('coordinator.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('coordinator.name'));
+    }
+  }),
+  schedulePersistWorkInProgress (){
+    Ember.run.later(function(){
+      this.persistWorkInProgress();
+      this.schedulePersistWorkInProgress();
+    }.bind(this), Constants.persistWorkInProgressInterval);
+  },
+  persistWorkInProgress(){
+    if(!this.get('coordinator')){
+      return;
+    }
+    var json = JSON.stringify(this.get("coordinator"));
+    this.get('workspaceManager').saveWorkInProgress(this.get('tabInfo.id'), json);
+  },
+  showExistingWorkflow  : function(){
+    if(!this.get('xmlAppPath')){
+      return;
+    }
+    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.importCoordinator(relXmlPath);
+  }.on('didInsertElement'),
+  createNewCoordinator(){
+    return Coordinator.create({
+      workflow : {
+        appPath : undefined,
+        configuration :{
+          property : Ember.A([])
+        }
+      },
+      frequency : {
+        type : undefined,
+        value : undefined
+      },
+      start : {
+        value : undefined,
+        displayValue : undefined,
+        type : 'date'
+      },
+      end : {
+        value : undefined,
+        displayValue : undefined,
+        type : 'date'
+      },
+      timezone : 'UTC',
+      datasets : Ember.A([]),
+      dataInputs : Ember.A([]),
+      dataOutputs : Ember.A([]),
+      dataInputType : 'simple',
+      parameters : {
+        configuration :{
+          property : Ember.A([])
+        }
+      },
+      controls : Ember.A([]),
+      slainfo : SlaInfo.create({})
+    });
+  },
+  importSampleCoordinator (){
+    var deferred = Ember.RSVP.defer();
+    Ember.$.ajax({
+      url: "/sampledata/coordinator.xml",
+      dataType: "text",
+      cache:false,
+      success: function(data) {
+        var coordinatorXmlImporter = CoordinatorXmlImporter.create({});
+        var coordinator = coordinatorXmlImporter.importCoordinator(data);
+        deferred.resolve(coordinator);
+      }.bind(this),
+      failure : function(data){
+        deferred.reject(data);
+      }
+    });
+    return deferred;
+  },
+  importSampleWorkflow (){
+    var deferred = Ember.RSVP.defer();
+    Ember.$.ajax({
+      url: "/sampledata/workflow.xml",
+      dataType: "text",
+      cache:false,
+      success: function(data) {
+        deferred.resolve(data);
+      }.bind(this),
+      failure : function(data){
+        deferred.reject(data);
+      }
+    });
+    return deferred;
+  },
+  importCoordinator (filePath){
+    this.set("coordinatorFilePath", filePath);
+    this.set("isImporting", false);
+    var deferred = this.readFromHdfs(filePath);
+    deferred.promise.then(function(data){
+      this.getCoordinatorFromXml(data);
+      this.set("isImporting", false);
+    }.bind(this)).catch(function(){
+      this.set("isImporting", false);
+      this.set("isImportingSuccess", false);
+    }.bind(this));
+  },
+  readFromHdfs(filePath){
+    var url =  Ember.ENV.API_URL + "/readWorkflowXml?workflowXmlPath="+filePath;
+    var deferred = Ember.RSVP.defer();
+    Ember.$.ajax({
+      url: url,
+      method: 'GET',
+      dataType: "text",
+      beforeSend: function (xhr) {
+        xhr.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000));
+        xhr.setRequestHeader("X-Requested-By", "Ambari");
+      }
+    }).done(function(data){
+      deferred.resolve(data);
+    }).fail(function(){
+      deferred.reject();
+    });
+    return deferred;
+  },
+  getCoordinatorFromXml(coordinatorXml){
+    var coordinatorXmlImporter = CoordinatorXmlImporter.create({});
+    var coordinator = coordinatorXmlImporter.importCoordinator(coordinatorXml);
+    this.set("coordinator", coordinator);
+    this.$('input[name="dataInputType"][value="'+ coordinator.get('dataInputType')+'"]').prop('checked', true);
+    if(coordinator.get('dataInputType') === 'logical'){
+      this.set('conditionalDataInExists', true);
+    }
+  },
+  validateChildComponents(){
+    var isChildComponentsValid = true;
+    this.get('childComponents').forEach((context)=>{
+      if(context.get('validations') && context.get('validations.isInvalid')){
+        isChildComponentsValid =  false;
+        context.set('showErrorMessage', true);
+      }
+    }.bind(this));
+    return isChildComponentsValid;
+  },
+  actions : {
+    registerChild(key, context){
+      this.get('childComponents').set(key, context);
+    },
+    deregisterChild(key){
+      this.get('childComponents').delete(key);
+    },
+    createDataset(){
+      this.set('datasetEditMode', false);
+      this.set('datasetCreateMode', true);
+      this.set('currentDataset',{});
+    },
+    editDataset(index){
+      this.set('datasetEditMode', true);
+      this.set('datasetCreateMode', false);
+      this.set('currentDatasetIndex', index);
+      this.set('currentDataset', Ember.copy(this.get('coordinator.datasets').objectAt(index)));
+    },
+    addDataset(){
+      this.get('coordinator.datasets').pushObject(Ember.copy(this.get('currentDataset')));
+      this.set('datasetCreateMode', false);
+    },
+    updateDataset(){
+      this.get('coordinator.datasets').replace(this.get('currentDatasetIndex'), 1, Ember.copy(this.get('currentDataset')));
+      this.set('datasetEditMode', false);
+    },
+    cancelDatasetOperation(){
+      this.set('datasetCreateMode', false);
+      this.set('datasetEditMode', false);
+    },
+    deleteDataset(index){
+      this.get('coordinator.datasets').removeAt(index);
+      if(index === this.get('currentDatasetIndex')){
+        this.set('datasetEditMode', false);
+      }
+    },
+    createDataInput(){
+      this.set('dataInputEditMode', false);
+      this.set('dataInputCreateMode', true);
+      this.set('currentDataInput', {});
+    },
+    addDataInput(){
+      this.get('coordinator.dataInputs').pushObject(Ember.copy(this.get('currentDataInput')));
+      this.set('dataInputCreateMode', false);
+    },
+    editDataInput(index){
+      this.set('dataInputCreateMode', false);
+      this.set('dataInputEditMode', true);
+      this.set('currentDataInputIndex', index);
+      this.set('currentDataInput', Ember.copy(this.get('coordinator.dataInputs').objectAt(index)));
+    },
+    updateDataInput(){
+      this.get('coordinator.dataInputs').replace(this.get('currentDataInputIndex'), 1, Ember.copy(this.get('currentDataInput')));
+      this.set('dataInputEditMode', false);
+    },
+    deleteDataInput(index){
+      this.get('coordinator.dataInputs').removeAt(index);
+      if(index === this.get('currentDataInputIndex')){
+        this.set('dataInputEditMode', false);
+      }
+    },
+    cancelDataInputOperation(){
+      this.set('dataInputEditMode', false);
+      this.set('dataInputCreateMode', false);
+    },
+    createDataOutput(){
+      this.set('dataOutputEditMode', false);
+      this.set('dataOutputCreateMode', true);
+      this.set('currentDataOutput', {});
+    },
+    addDataOutput(){
+      this.get('coordinator.dataOutputs').pushObject(Ember.copy(this.get('currentDataOutput')));
+      this.set('dataOutputCreateMode', false);
+    },
+    editDataOutput(index){
+      this.set('dataOutputCreateMode', false);
+      this.set('dataOutputEditMode', true);
+      this.set('currentDataOutputIndex', index);
+      this.set('currentDataOutput', Ember.copy(this.get('coordinator.dataOutputs').objectAt(index)));
+    },
+    updateDataOutput(){
+      this.get('coordinator.dataOutputs').replace(this.get('currentDataOutputIndex'), 1, Ember.copy(this.get('currentDataOutput')));
+      this.set('dataOutputEditMode', false);
+    },
+    deleteDataOutput(index){
+      this.get('coordinator.dataOutputs').removeAt(index);
+      if(index === this.get('currentDataOutputIndex')){
+        this.set('dataOutputEditMode', false);
+      }
+    },
+    cancelDataOutputOperation(){
+      this.set('dataOutputEditMode', false);
+      this.set('dataOutputCreateMode', false);
+    },
+    submitCoordinator(){
+      var isChildComponentsValid = this.validateChildComponents();
+      if(this.get('validations.isInvalid') || !isChildComponentsValid) {
+        this.set('showErrorMessage', true);
+        return;
+      }
+      var coordGenerator=CoordinatorGenerator.create({coordinator:this.get("coordinator")});
+      var coordinatorXml=coordGenerator.process();
+      var dynamicProperties = this.get('propertyExtractor').getDynamicProperties(coordinatorXml);
+      var configForSubmit={props:dynamicProperties,xml:coordinatorXml,params:this.get('coordinator.parameters')};
+      this.set("coordinatorConfigs", configForSubmit);
+      this.set("showingJobConfig", true);
+    },
+    closeCoordSubmitConfig(){
+      this.set("showingJobConfig", false);
+    },
+    closeFileBrowser(){
+      this.set("showingFileBrowser", false);
+      this.get('fileBrowser').getContext().trigger('fileSelected', this.get('filePath'));
+      if(this.get('coordinatorFilePath')){
+        this.importCoordinator(Ember.copy(this.get('coordinatorFilePath')));
+        this.set('coordinatorFilePath', null);
+      }
+    },
+    openFileBrowser(model, context){
+      if(!context){
+        context = this;
+      }
+      this.get('fileBrowser').trigger('fileBrowserOpened',context);
+      this.set('filePathModel', model);
+      this.set('showingFileBrowser', true);
+    },
+    createCondition(){
+      this.set('coordinator.conditionalDataInput', {type:'condition', operator:'and'});
+      this.set('conditionalDataInExists', true);
+    },
+    deleteCondition(index){
+      this.set('coordinator.conditionalDataInput', undefined);
+      this.set('conditionalDataInExists', false);
+    },
+    toggleDataTnput(type){
+      this.set('coordinator.dataInputType', type);
+    },
+    createInputLogic(){
+      this.set('coordinator.inputLogic', {type:'condition', operator:'and'});
+      this.set('inputLogicExists', true);
+    },
+    deleteInputLogic(index){
+      this.set('coordinator.inputLogic', undefined);
+      this.set('inputLogicExists', false);
+    },
+    preview(){
+      var isChildComponentsValid = this.validateChildComponents();
+      if(this.get('validations.isInvalid') || !isChildComponentsValid) {
+        this.set('showErrorMessage', true);
+        return;
+      }
+      this.set("showingPreview", false);
+      var coordGenerator = CoordinatorGenerator.create({coordinator:this.get("coordinator")});
+      var coordinatorXml = coordGenerator.process();
+      this.set("previewXml", vkbeautify.xml(coordinatorXml));
+      this.set("showingPreview", true);
+    },
+    confirmReset(){
+      this.set('showingResetConfirmation', true);
+    },
+    resetCoordinator(){
+      this.set('coordinator', this.createNewCoordinator());
+    },
+    importCoordinatorTest(){
+      var deferred = this.importSampleCoordinator();
+      deferred.promise.then(function(data){
+        this.set("coordinator", data);
+        this.$('input[name="dataInputType"][value="'+ data.get('dataInputType')+'"]').prop('checked', true);
+        if(data.get('dataInputType') === 'logical'){
+          this.set('conditionalDataInExists', true);
+        }
+        console.error(this.get('coordinator'));
+      }.bind(this)).catch(function(e){
+        throw new Error(e);
+      });
+    },
+    openTab(type, path){
+      this.sendAction('openTab', type, path);
+    },
+    showParameterSettings(value){
+      if(this.get('coordinator.parameters') !== null){
+        this.set('parameters', Ember.copy(this.get('coordinator.parameters')));
+      }else{
+        this.set('parameters', {});
+      }
+      this.set('showParameterSettings', value);
+    },
+    closeWorkFlowParam(){
+      this.set("showParameterSettings", false);
+    },
+    saveWorkFlowParam(){
+      this.set('coordinator.parameters', Ember.copy(this.get('parameters')));
+      this.set("showParameterSettings", false);
+    },
+    showControlConfig(){
+      if(this.get('coordinator.controls')){
+        this.get('coordinatorControls').forEach((control)=>{
+          var coordControl = this.get('coordinator.controls').findBy('name', control.name);
+          if(coordControl){
+            Ember.set(control, 'value', coordControl.value);
+          }else{
+            Ember.set(control, 'value', '');
+          }
+        }, this);
+      }
+      this.set('showControlConfig', true);
+    },
+    saveCoordControls(){
+      this.get('coordinatorControls').forEach((control)=>{
+        var coordControl = this.get('coordinator.controls').findBy('name', control.name);
+        if(coordControl){
+          Ember.set(coordControl, 'value', control.value);
+        }else{
+          this.get('coordinator.controls').pushObject({'name':control.name, 'value':control.value});
+        }
+      }, this);
+      this.set('showControlConfig', false);
+    },
+    showWorkflowName(){
+      this.set('workflowName', null);
+      var deferred = this.readFromHdfs(this.get('coordinator.workflow.appPath'));
+      deferred.promise.then(function(data){
+        var x2js = new X2JS();
+        var workflowJson = x2js.xml_str2json(data);
+        this.set('workflowName', workflowJson["workflow-app"]._name);
+      }.bind(this)).catch(function(){
+        this.set('workflowName', null);
+      }.bind(this));
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/credentials-config.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/credentials-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/credentials-config.js
index f100808..32e2103 100644
--- a/contrib/views/wfmanager/src/main/resources/ui/app/components/credentials-config.js
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/credentials-config.js
@@ -15,29 +15,39 @@
 *    limitations under the License.
 */
 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({
+  'credential.name': validator('presence', {
+    presence : true,
+    message : 'Required'
+  }),
+  'credential.type': validator('presence', {
+    presence : true,
+    message : 'Required'
+  })
+});
+
+export default Ember.Component.extend(Validations, {
   childComponents : new Map(),
   initialize : function(){
+    if(this.get('mode') === 'create'){
+      this.set("credential", {});
+      this.set("credential.property", Ember.A([]));
+    }
+    this.initializeCredentialDetails();
     if(this.get('mode') === 'edit'){
       this.sendAction('register', this, this);
     }
     this.get('childComponents').clear();
     this.set('credentialType',Ember.A([]));
-    this.get('credentialType').pushObject({value:'',displayName:'Select'});
+    this.get('credentialType').pushObject({value:undefined,displayName:'Select'});
     this.get('credentialType').pushObject({value:'hcat',displayName:'HCat'});
     this.get('credentialType').pushObject({value:'hive2',displayName:'Hive2'});
     this.get('credentialType').pushObject({value:'hbase',displayName:'HBase'});
 
     Ember.addObserver(this, 'credential.type', this, this.credentialTypeObserver);
 
-    this.initializeCredentialDetails();
-
-    if(this.get('mode') === 'create'){
-      this.set("credential", {});
-      this.set("credential.property", Ember.A([]));
-    }
     if(this.get('credential.type') && this.get('credential.property')){
       this.set('staticProps', Ember.copy(this.get('credentialDetails').findBy('name',this.get('credential.type')).staticProps));
       var configProperties = this.get('credential.property');
@@ -50,37 +60,35 @@ export default Ember.Component.extend(EmberValidations, {
       });
     }
   }.on('init'),
-  rendered : function(){
-    if(this.get('mode') === 'create'){
-      this.$('.collapse').collapse('show');
-    }else if(this.get('mode') === 'edit'){
-      this.$('.collapse').collapse('hide');
+  bindStaticPropsOnEdit : function(){
+    if(this.get('mode') === 'edit'){
+      this.processStaticProps();
     }
-  }.on('didInsertElement'),
+  }.on('willDestroyElement'),
   initializeCredentialDetails : function(){
     this.set('credentialDetails', Ember.A([]));
     this.get('credentialDetails').pushObject({
       name:'hcat',
       staticProps:
-      [{name:'hcat.metastore.principal',displayName:'Hcat Metastore principal', value:'', belongsTo:'credential.property'},
-      {name:'hcat.metastore.uri',displayName:'Hcat Metastore uri', value:'', belongsTo:'credential.property'}]
+      [{name:'hcat.metastore.principal',displayName:'Hcat Metastore principal', value:undefined, belongsTo:'credential.property'},
+      {name:'hcat.metastore.uri',displayName:'Hcat Metastore uri', value:undefined, belongsTo:'credential.property'}]
     });
     this.get('credentialDetails').pushObject({
       name:'hive2',
       staticProps:
-      [{name:'hive2.jdbc.url',displayName:'Hive2 Jdbc Url', value:'', belongsTo:'credential.property'},
-      {name:'hive2.server.principal',displayName:'Hive2 Server principal', value:'', belongsTo:'credential.property'}]
+      [{name:'hive2.jdbc.url',displayName:'Hive2 Jdbc Url', value:undefined, belongsTo:'credential.property'},
+      {name:'hive2.server.principal',displayName:'Hive2 Server principal', value:undefined, belongsTo:'credential.property'}]
     });
     this.get('credentialDetails').pushObject({
       name:'hbase',
       staticProps:
-      [{name:'hadoop.security.authentication',displayName:'Hadoop security auth', value:'', belongsTo:'credential.property'},
-      {name:'hbase.security.authentication',displayName:'Hbase security auth', value:'', belongsTo:'credential.property'},
-      {name:'hbase.master.kerberos.principal',displayName:'Hbase Master kerberos principal', value:'', belongsTo:'credential.property'},
-      {name:'hbase.regionserver.kerberos.principal',displayName:'Hbase regionserver kerberos principal', value:'', belongsTo:'credential.property'},
-      {name:'hbase.zookeeper.quorum',displayName:'Hbase zookeeper quorum', value:'', belongsTo:'credential.property'},
-      {name:'hadoop.rpc.protection',displayName:'Hadoop Rpc protection', value:'', belongsTo:'credential.property'},
-      {name:'hbase.rpc.protection',displayName:'Hbase Rpc protection', value:'', belongsTo:'credential.property'}]
+      [{name:'hadoop.security.authentication',displayName:'Hadoop security auth', value:undefined, belongsTo:'credential.property'},
+      {name:'hbase.security.authentication',displayName:'Hbase security auth', value:undefined, belongsTo:'credential.property'},
+      {name:'hbase.master.kerberos.principal',displayName:'Hbase Master kerberos principal', value:undefined, belongsTo:'credential.property'},
+      {name:'hbase.regionserver.kerberos.principal',displayName:'Hbase regionserver kerberos principal', value:undefined, belongsTo:'credential.property'},
+      {name:'hbase.zookeeper.quorum',displayName:'Hbase zookeeper quorum', value:undefined, belongsTo:'credential.property'},
+      {name:'hadoop.rpc.protection',displayName:'Hadoop Rpc protection', value:undefined, belongsTo:'credential.property'},
+      {name:'hbase.rpc.protection',displayName:'Hbase Rpc protection', value:undefined, belongsTo:'credential.property'}]
     });
   },
   credentialTypeObserver : function(){
@@ -97,93 +105,56 @@ export default Ember.Component.extend(EmberValidations, {
       }
     });
   },
-  resetForm : function(){
-    this.set('credential', {});
-    this.set('credential.property',Ember.A([]));
-    this.get('staticProps').clear();
-    this.initializeCredentialDetails();
-  },
-  validateChildrenComponents(){
-    var validationPromises = [];
-    var deferred = Ember.RSVP.defer();
-    if(this.get('childComponents').size === 0){
-      deferred.resolve(true);
-    }else{
-      this.get('childComponents').forEach((childComponent)=>{
-        if(!childComponent.validations){
-          return;
-        }
-        var validationDeferred = Ember.RSVP.defer();
-        childComponent.validate().then(()=>{
-          validationDeferred.resolve();
-        }).catch((e)=>{
-          validationDeferred.reject(e);
-        });
-        validationPromises.push(validationDeferred.promise);
-      });
-      Ember.RSVP.Promise.all(validationPromises).then(function(){
-        deferred.resolve(true);
-      }).catch(function(e){
-        deferred.reject(e);
-      });
+  processStaticProps() {
+    var staticProps = this.get('staticProps');
+    var index = 0;
+    if(!staticProps){
+      return;
     }
-    return deferred;
-  },
-  validations : {
-    'credential.name': {
-      presence: {
-        'message' : 'Required',
+    staticProps.forEach((property)=>{
+      var existingStaticProp = this.get('credential.property').findBy('name',property.name);
+      if (existingStaticProp) {
+        Ember.set(existingStaticProp,'value',property.value);
+        index++;
+      } else {
+        var propObj = {name : property.name, value:property.value, static:true};
+        this.get('credential.property').insertAt(index++, propObj);
       }
-    },
-    'credential.type': {
-      presence: {
-        'message' : 'Required',
+    }.bind(this));
+  },
+  validateChildrenComponents(){
+    var isChildComponentsValid = true;
+    this.get('childComponents').forEach((context)=>{
+      if(context.get('validations') && context.get('validations.isInvalid')){
+        isChildComponentsValid =  false;
+        context.set('showErrorMessage', true);
       }
-    }
+    }.bind(this));
+    return isChildComponentsValid;
   },
   actions : {
     register(component, context){
-      if(this.get('mode') === 'edit'){
-        this.sendAction('register', component, context);
-      }
       this.get('childComponents').set(component, context);
     },
     add(){
-      var isFormValid = this.validateChildrenComponents();
-      isFormValid.promise.then(function(){
-        this.validate().then(function(){
-          var staticProps = this.get('staticProps');
-          var index = 0;
-          staticProps.forEach((property)=>{
-            var existingStaticProp = this.get('credential.property').findBy('name',property.name);
-            if (existingStaticProp) {
-              Ember.set(existingStaticProp,'value',property.value);
-              index++;
-            } else {
-              var propObj = {name : property.name, value:property.value, static:true};
-              this.get('credential.property').insertAt(index++, propObj);
-            }
-          });
-          this.processMultivaluedComponents();
-          this.sendAction('add',this.get('credential'));
-          this.resetForm();
-        }.bind(this)).catch(function(e){
-        }.bind(this));
-      }.bind(this)).catch(function (e) {
-      });
-
-    },
-    delete(name){
-      this.sendAction('delete',name);
-    },
-    togglePanel (){
-      this.$('.collapse').collapse('toggle');
-      if(this.$('.collapse').hasClass('in')){
-        this.set('isOpen', true);
+      var isChildComponentsValid = this.validateChildrenComponents();
+      if(this.get('validations.isInvalid') || !isChildComponentsValid) {
+        this.set('showErrorMessage', true);
+        return;
+      }
+      this.processStaticProps();
+      this.processMultivaluedComponents();
+      if(this.get('mode') === 'create'){
+        this.sendAction('add',this.get('credential'));
       }else{
-        this.set('isOpen', false);
+        this.sendAction('update');
       }
+    },
+    cancel (){
+      this.sendAction('cancel');
+    },
+    unregister(component, context){
+      this.get('childComponents').delete(component);
     }
   }
-
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/data-input-output-config.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/data-input-output-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/data-input-output-config.js
new file mode 100644
index 0000000..5ce92f5
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/data-input-output-config.js
@@ -0,0 +1,97 @@
+/*
+*    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 { validator, buildValidations } from 'ember-cp-validations';
+
+const Validations = buildValidations({
+  'data.name': validator('presence', {
+    presence : true
+  }),
+  'data.dataset': validator('presence', {
+    presence : true
+  })
+});
+
+export default Ember.Component.extend(Validations, {
+  initialize : function(){
+    if(!this.get('data.start')){
+      this.set('data.start',{
+        type : 'date',
+        value :''
+      });
+    }
+    if(!this.get('data.end')){
+      this.set('data.end',{
+        type : 'date',
+        value :''
+      });
+    }
+    if(this.get('type') === 'output'){
+      if(!this.get('data.instance')){
+        this.set('data.instance',{
+          type : 'date',
+          value :''
+        });
+      }
+    }
+    if(this.get('type') === 'input' && !this.get('data.instances')){
+      this.set('data.instances', Ember.A([]));
+      this.set('data.isList', true);
+    }
+    this.set('childComponents', new Map());
+  }.on('init'),
+  validateChildComponents(){
+    var isChildComponentsValid = true;
+    this.get('childComponents').forEach((context)=>{
+      if(context.get('validations') && context.get('validations.isInvalid')){
+        isChildComponentsValid =  false;
+        context.set('showErrorMessage', true);
+      }
+    }.bind(this));
+    return isChildComponentsValid;
+  },
+  actions : {
+    registerChild(key, context){
+      this.get('childComponents').set(key, context);
+    },
+    deregisterChild(key){
+      this.get('childComponents').delete(key);
+    },
+    onInstanceTypeChange(isList) {
+      this.set('data.isList', isList);
+    },
+    add(){
+      var isChildComponentsValid = this.validateChildComponents();
+      if(this.get('validations.isInvalid') || !isChildComponentsValid) {
+        this.set('showErrorMessage', true);
+        return;
+      }
+      this.sendAction('add');
+    },
+    update(){
+      var isChildComponentsValid = this.validateChildComponents();
+      if(this.get('validations.isInvalid') || !isChildComponentsValid) {
+        this.set('showErrorMessage', true);
+        return;
+      }
+      this.sendAction('update');
+    },
+    cancel(){
+      this.sendAction('cancel');
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/data-input.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/data-input.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/data-input.js
new file mode 100644
index 0000000..c484968
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/data-input.js
@@ -0,0 +1,41 @@
+/*
+*    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 { validator, buildValidations } from 'ember-cp-validations';
+
+const Validations = buildValidations({
+  'dataInput.dataset': validator('presence', {
+    presence : true
+  })
+});
+
+export default Ember.Component.extend(Validations, {
+  initialize : function(){
+    this.sendAction('register', this, this);
+  }.on('init'),
+  onDestroy : function(){
+    this.sendAction('deregister', this);
+  }.on('willDestroyElement'),
+  actions : {
+    showAdvanced(){
+      this.set('showAdvanced', true);
+    },
+    hideAdvanced(){
+      this.set('showAdvanced', false);
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/dataset-config.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/dataset-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/dataset-config.js
new file mode 100644
index 0000000..d5253ab
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/dataset-config.js
@@ -0,0 +1,103 @@
+/*
+*    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 { validator, buildValidations } from 'ember-cp-validations';
+import Constants from '../utils/constants';
+
+const Validations = buildValidations({
+  'dataset.name': validator('presence', {
+    presence : true
+  }),
+  'dataset.frequency.value': validator('presence', {
+    presence : true
+  }),
+  'dataset.frequency.type': validator('presence', {
+    presence : true
+  }),
+  'dataset.timezone': validator('presence', {
+    presence : true
+  }),
+  'dataset.uriTemplate': validator('presence', {
+    presence : true
+  })
+});
+
+export default Ember.Component.extend(Validations, {
+  childComponents : new Map(),
+  initialize : function(){
+    if(!this.get('dataset.initialInstance')){
+      this.set('dataset.initialInstance', {
+        type : 'date',
+        value : ''
+      });
+    }
+    if(!this.get('dataset.timezone')){
+      this.set('dataset.timezone','UTC');
+    }
+    if(!this.get('dataset.frequency')){
+      this.set('dataset.frequency',{ type : undefined, value : undefined });
+    }
+    this.set('timeUnitOptions',Ember.A([]));
+    this.get('timeUnitOptions').pushObject({value:'',displayName:'Select'});
+    this.get('timeUnitOptions').pushObject({value:'months',displayName:'Months'});
+    this.get('timeUnitOptions').pushObject({value:'endOfMonths',displayName:'End of Months'});
+    this.get('timeUnitOptions').pushObject({value:'days',displayName:'Days'});
+    this.get('timeUnitOptions').pushObject({value:'endOfDays',displayName:'End of Days'});
+    this.get('timeUnitOptions').pushObject({value:'hours',displayName:'Hours'});
+    this.get('timeUnitOptions').pushObject({value:'minutes',displayName:'Minutes'});
+    this.get('timeUnitOptions').pushObject({value:'cron',displayName:'Cron'});
+    this.set('childComponents', new Map());
+    this.set('timezoneList', Ember.copy(Constants.timezoneList));
+  }.on('init'),
+  validateChildComponents(){
+    var isChildComponentsValid = true;
+    this.get('childComponents').forEach((context)=>{
+      if(context.get('validations') && context.get('validations.isInvalid')){
+        isChildComponentsValid =  false;
+        context.set('showErrorMessage', true);
+      }
+    }.bind(this));
+    return isChildComponentsValid;
+  },
+  actions : {
+    registerChild(key, context){
+      this.get('childComponents').set(key, context);
+    },
+    deregisterChild(key){
+      this.get('childComponents').delete(key);
+    },
+    addDataset(){
+      var isChildComponentsValid = this.validateChildComponents();
+      if(this.get('validations.isInvalid') || !isChildComponentsValid) {
+        this.set('showErrorMessage', true);
+        return;
+      }
+      this.sendAction('add');
+    },
+    updateDataset(){
+      var isChildComponentsValid = this.validateChildComponents();
+      if(this.get('validations.isInvalid') || !isChildComponentsValid) {
+        this.set('showErrorMessage', true);
+        return;
+      }
+      this.sendAction('update');
+    },
+    cancelDatasetOperation(){
+      this.sendAction('cancel');
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/date-with-expr.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/date-with-expr.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/date-with-expr.js
new file mode 100644
index 0000000..dd9a9ae
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/date-with-expr.js
@@ -0,0 +1,78 @@
+/*
+*    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 { validator, buildValidations } from 'ember-cp-validations';
+const Validations = buildValidations({
+  'dateField.displayValue': {
+    validators: [
+      validator('presence', true),
+      validator('date', {
+        format: 'MM/DD/YYYY hh:mm A',
+        disabled(model, attribute) {
+          return model.get('dateField.type') === 'expr';
+        }
+      })
+    ]
+  },
+});
+export default Ember.Component.extend(Validations, {
+  initialize : function(){
+    this.sendAction('register', this, this);
+  }.on('init'),
+  elementsInserted : function(){
+    if(this.get('dateField.type') === 'date'){
+      this.$('input[name="'+this.get('inputName')+'"]').datetimepicker({
+        format: 'MM/DD/YYYY hh:mm A',
+        useCurrent: false,
+        showClose : true
+      });
+    }
+    Ember.addObserver(this, 'dateField.type', this, this.typeObserver);
+    Ember.addObserver(this, 'dateField.displayValue', this, this.timeObserver);
+  }.on('didInsertElement'),
+  onDestroy : function(){
+    this.sendAction('deregister', this);
+    Ember.removeObserver(this, 'dateField.type', this, this.typeObserver);
+    Ember.removeObserver(this, 'dateField.displayValue', this, this.timeObserver);
+  }.on('willDestroyElement'),
+  typeObserver : function(){
+    if(this.get('dateField.type') === 'date'){
+      this.$('input[name="'+this.get('inputName')+'"]').datetimepicker({
+        useCurrent: false,
+        showClose : true
+      });
+      this.set('dateField.displayValue', undefined);
+    }else{
+      var dateTimePicker = this.$('input[name="'+this.get('inputName')+'"]').data("DateTimePicker");
+      if(dateTimePicker){
+        dateTimePicker.destroy();
+      }
+    }
+  },
+  timeObserver : function(){
+    if(this.get('dateField.type') === 'date'){
+      var date = new Date(this.get('dateField.displayValue'));
+      if(isNaN(date.getTime())){
+        this.set('dateField.value', undefined);
+        return;
+      }
+      this.set('dateField.value', moment(date).format("YYYY-MM-DDTHH:mm")+'Z');
+    }else{
+      this.set('dateField.value', Ember.copy(this.get('dateField.displayValue')));
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/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 84d1393..e4b2224 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
@@ -17,9 +17,27 @@
 
 import Ember from 'ember';
 import {FindNodeMixin} from '../domain/findnode-mixin';
-import EmberValidations from 'ember-validations';
+import { validator, buildValidations } from 'ember-cp-validations';
 
-export default Ember.Component.extend(EmberValidations, FindNodeMixin,{
+const Validations = buildValidations({
+  'condition': validator('presence', {
+    presence : true,
+    disabled(model) {
+      return !model.get('canValidate');
+    },
+    'message' : 'Required',
+    dependentKeys : ['canValidate']
+  }),
+  'targetNode': validator('presence', {
+    presence : true,
+    disabled(model) {
+      return !model.get('canValidate');
+    },
+    'message' : 'Required',
+    dependentKeys : ['canValidate']
+  }),
+});
+export default Ember.Component.extend(Validations, FindNodeMixin,{
   isInsertAction: false,
   condition:"",
   targetNode:"",
@@ -27,13 +45,22 @@ export default Ember.Component.extend(EmberValidations, FindNodeMixin,{
   initialize : function(){
     var self=this;
 
-    this.on("showBranchOptions",function(){
+    this.on("showBranchOptions",function(node){
       if (self.$("#selector-content").is(":visible")){
         self.$("#selector-content").hide();
       }else{
+        if (node) {
+          self.set("node", node);
+        }
         self.set("isInsertAction",false);
         this.set("newNodeType",null);
-        this.set('descendantNodes',this.getDesendantNodes(this.get('node')));
+        var commonTarget=this.findCommonTargetNode(this.workflow.startNode,this.get('node'));
+        var descendantNodes=this.getDesendantNodes(this.get('node'));
+        if (commonTarget){
+          descendantNodes.removeObject(commonTarget);
+          descendantNodes.unshiftObject(commonTarget);
+        }
+        this.set('descendantNodes',descendantNodes);
         self.$("#selector-content").show();
       }
     });
@@ -41,20 +68,6 @@ export default Ember.Component.extend(EmberValidations, FindNodeMixin,{
   setup : function(){
     this.sendAction('registerAddBranchAction',this);
   }.on('didInsertElement'),
-  validations : {
-    'condition': {
-      presence: {
-        'if' : 'canValidate',
-        'message' : 'Required',
-      }
-    },
-    'targetNode': {
-      presence: {
-        'if' : 'canValidate',
-        'message' : 'Required',
-      }
-    }
-  },
   actions:{
     addNewNode(type){
       this.set("newNodeType",type);
@@ -65,20 +78,21 @@ export default Ember.Component.extend(EmberValidations, FindNodeMixin,{
     },
     save(){
       this.set('canValidate', true);
-      this.validate().then(function(){
-        this.sendAction("addDecisionBranch",{
-          sourceNode: this.get("node"),
-          condition:this.get("condition"),
-          targetNode:this.get("targetNode"),
-          newNodeType:this.get("newNodeType")
-        });
-        this.$("#selector-content").hide();
-        this.set('canValidate', false);
-        this.set('condition',"");
-        this.set('targetNode',"");
-        this.$('#target-node-select').prop('selectedIndex', 0);
-      }.bind(this)).catch(function(e){
-      }.bind(this));
+      if(this.get('validations.isInvalid')){
+        this.set('showErrorMessage', true);
+        return;
+      }
+      this.sendAction("addDecisionBranch",{
+        sourceNode: this.get("node"),
+        condition:this.get("condition"),
+        targetNode:this.get("targetNode"),
+        newNodeType:this.get("newNodeType")
+      });
+      this.$("#selector-content").hide();
+      this.set('canValidate', false);
+      this.set('condition',"");
+      this.set('targetNode',"");
+      this.$('#target-node-select').prop('selectedIndex', 0);
     },
     cancel(){
       this.$("#selector-content").hide();

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/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 324be66..ad664d7 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
@@ -16,35 +16,20 @@
 */
 
 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': {
+    validators: [
+      validator('decission-node-validator', {
+        dependentKeys: ['actionModel.@each.condition']
+      })
+    ]
+  }
+});
+
+export default Ember.Component.extend(Validations,{
   initialize : function(){
     this.sendAction('register','decision',this);
-  }.on('init'),
-  validations: {
-    'actionModel': {
-      inline : validator(function() {
-        var hasDefaultCond = false;
-        this.get('actionModel').forEach(function(item, index){
-          if(item.condition === "default"){
-            hasDefaultCond = true;
-            return;
-          }
-        });
-        if(!hasDefaultCond){
-          return "Decision Should have one default condition";
-        }
-        var hasEmptyCond = false;
-        this.get('actionModel').forEach(function(item, index){
-          if(item.condition === '' || item.condition === undefined || Ember.$.trim(item.condition).length === 0){
-            hasEmptyCond = true;
-            return;
-          }
-        });
-        if(hasEmptyCond){
-          return "Condition cannot be blank";
-        }
-      })}
-    }
-  });
+  }.on('init')
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/designer-workspace.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/designer-workspace.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/designer-workspace.js
new file mode 100644
index 0000000..a3d64b0
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/designer-workspace.js
@@ -0,0 +1,158 @@
+/*
+*    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({
+  workspaceManager : Ember.inject.service('workspace-manager'),
+  xmlAppPath : null,
+  appPath : null,
+  type : 'wf',
+  tabId: 0,
+  hasMultitabSupport : true,
+  tabCounter : new Map(),
+  tabs : Ember.A([]),
+  currentIndex : Ember.computed('tabs.[]', function() {
+    return this.get('tabs').length > 0 ? this.get('tabs').length - 1 : 0;
+  }),
+  tabsObserver : Ember.observer('tabs.[]', function(){
+    this.get('workspaceManager').saveTabs(this.get('tabs'));
+  }),
+  initialize : function(){
+    this.get('tabCounter').set('wf', 0);
+    this.get('tabCounter').set('coord', 0);
+    this.get('tabCounter').set('bundle', 0);
+    var tabs = this.get('workspaceManager').restoreTabs();
+    if(tabs){
+      this.set('tabs', tabs);
+    }
+    this.get('tabs').forEach((tab)=>{
+      this.get('tabCounter').set(tab.type, (this.get('tabCounter').get(tab.type)) + 1);
+    }, this);
+  }.on('init'),
+  elementsInserted : function(){
+    this.$('.nav-tabs a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+      this.get('workspaceManager').setLastActiveTab((this.$(e.target).attr('href').slice(1)));
+    }.bind(this));
+
+    if(this.get('tabs') && this.get('tabs').length > 0){
+      var lastActiveTabId = this.get('workspaceManager').getLastActiveTab();
+      var activeTab = this.get('tabs').findBy('id', lastActiveTabId);
+      if(!activeTab){
+        activeTab = this.get('tabs').objectAt(this.get('tabs').length - 1);
+      }
+      this.$('.nav-tabs a[href="#' + activeTab.id + '"]').tab('show');
+    }else{
+      if(this.get('hasMultitabSupport')){
+        this.createNewTab(this.get('type'), this.get('xmlAppPath'));
+      }else{
+         var tab = this.get('tabs').findBy('type', this.get('type'));
+        if(!tab){
+          this.createNewTab(this.get('type'), this.get('xmlAppPath'));
+        }else{
+          Ember.set(tab,'path', this.get('xmlAppPath'));
+          this.$('.nav-tabs a[href="#' + tab.id + '"]').tab('show');
+        }
+      }
+    }
+  }.on('didInsertElement'),
+  onDestroy : function(){
+    this.get('tabs').clear();
+  }.on('willDestroyElement'),
+  createNewTab : function(type, path){
+    var tab = {
+      type : type,
+      id : this.generateTabId(),
+      name : this.getDisplayName(type)+this.getTabId(type)
+    };
+    if(path){
+      tab.path = path;
+    }
+    this.$('.nav-tabs li').removeClass('active');
+    this.$('.tab-content .tab-pane').removeClass('active');
+    this.get('tabs').pushObject(tab);
+    this.set('isNew', true);
+  },
+  getDisplayName(type){
+    if(type === 'wf'){
+      return "Workflow";
+    }else if(type === 'coord'){
+      return "Coordinator";
+    }else{
+      return "Bundle";
+    }
+  },
+  getTabId(type){
+    var count = this.get('tabCounter').get(type);
+    this.get('tabCounter').set(type, ++count);
+    return count;
+  },
+  generateTabId(){
+    return 'tab-'+ Math.ceil(Math.random() * 100000);
+  },
+  actions : {
+    register(tabInfo, context){
+      var tab = this.get('tabs').findBy('id', tabInfo.id);
+      Ember.set(tab, 'context', context);
+    },
+    show(type){
+      if(this.get('hasMultitabSupport')){
+        this.createNewTab(type);
+      }else{
+        var tab = this.get('tabs').findBy('type', type);
+        if(!tab){
+          this.createNewTab(type);
+        }else{
+          this.$('.nav-tabs a[href="#' + tab.id + '"]').tab('show');
+        }
+      }
+    },
+    closeTab(index){
+      if(index < this.get('tabs').length - 1){
+        var previousTab = this.get('tabs').objectAt(index + 1);
+        this.$('.nav-tabs a[href="#'+ previousTab.id + '"]').tab('show');
+      }
+      this.get('workspaceManager').deleteWorkInProgress(this.get('tabs').objectAt(index).id);
+      this.get('tabs').removeAt(index);
+    },
+    openTab(type, path){
+      if(this.get('hasMultitabSupport')){
+        this.createNewTab(type, path);
+      }else{
+        var tab = this.get('tabs').findBy('type', type);
+        if(!tab){
+          this.createNewTab(type, path);
+        }else{
+          Ember.set(tab,'path', path);
+          this.$('.nav-tabs a[href="#' + tab.id + '"]').tab('show');
+        }
+      }
+    },
+    changeTabName(tabInfo, name){
+      var tab = this.get('tabs').findBy('id', tabInfo.id);
+      Ember.set(tab, 'name', name);
+    },
+    changeFilePath(tabInfo, path){
+      var tab = this.get('tabs').findBy('id', tabInfo.id);
+      Ember.set(tab, 'filePath', path);
+    },
+    interceptShow(tab){
+      if(tab.type === 'wf' && tab.context){
+        tab.context.resize();
+      }
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-action-info.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-action-info.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-action-info.js
new file mode 100644
index 0000000..73cabac
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-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/distcp-action.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-action.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-action.js
index 1fbe4c9..e5740e3 100644
--- a/contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-action.js
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/distcp-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({
   fileBrowser : Ember.inject.service('file-browser'),
   setUp : function(){
     if(this.get('actionModel.args') === undefined){
@@ -44,9 +43,6 @@ export default Ember.Component.extend(EmberValidations, {
       this.$('#collapseOne').collapse('show');
     }
   }.on('didUpdate'),
-  validations : {
-
-  },
   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/email-action-info.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/email-action-info.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/email-action-info.js
new file mode 100644
index 0000000..73cabac
--- /dev/null
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/email-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/email-action.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/email-action.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/email-action.js
index 357fe90..e8277f9 100644
--- a/contrib/views/wfmanager/src/main/resources/ui/app/components/email-action.js
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/email-action.js
@@ -16,9 +16,21 @@
 */
 
 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.to': validator('presence', {
+    presence : true
+  }),
+  'actionModel.subject': validator('presence', {
+    presence : true
+  }),
+  'actionModel.body': validator('presence', {
+    presence : true
+  })  
+});
+
+export default Ember.Component.extend(Validations, {
   fileBrowser : Ember.inject.service('file-browser'),
   setUp : function(){
 
@@ -34,23 +46,6 @@ export default Ember.Component.extend(EmberValidations, {
       this.$('#collapseOne').collapse('show');
     }
   }.on('didUpdate'),
-  validations : {
-    'actionModel.to': {
-      presence: {
-        'message' : 'You need to provide a value for Email To',
-      }
-    },
-    'actionModel.subject': {
-      presence: {
-        'message' : 'You need to provide a value for subject',
-      }
-    },
-    'actionModel.body': {
-      presence: {
-        'message' : 'You need to provide a value for body',
-      }
-    }
-  },
   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/file-config.js
----------------------------------------------------------------------
diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/file-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/file-config.js
index 46d30d1..c0fba4a 100644
--- a/contrib/views/wfmanager/src/main/resources/ui/app/components/file-config.js
+++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/file-config.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({
   multivalued: true,
   fileBrowser : Ember.inject.service('file-browser'),
   initialize : function(){