You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2015/04/17 21:10:33 UTC

[4/5] ambari git commit: AMBARI-10528. Hive View: Visual Explain, Error handling and bugfixes (alexantonenko)

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/.travis.yml
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/.travis.yml b/contrib/views/hive/src/main/resources/ui/hive-web/.travis.yml
new file mode 100644
index 0000000..5d96e28
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/.travis.yml
@@ -0,0 +1,38 @@
+# 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.
+
+---
+language: node_js
+node_js:
+  - "0.12"
+
+sudo: false
+
+cache:
+  directories:
+    - node_modules
+
+before_install:
+  - "npm config set spin false"
+  - "npm install -g npm@^2"
+
+install:
+  - npm install -g bower
+  - npm install
+  - bower install
+
+script:
+  - npm test

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/Brocfile.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/Brocfile.js b/contrib/views/hive/src/main/resources/ui/hive-web/Brocfile.js
index db65f1f..8a367c9 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/Brocfile.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/Brocfile.js
@@ -25,9 +25,13 @@ var app = new EmberApp({
     //valid values are `default`, `bootstrap2`, `bootstrap3` or false
     'theme': 'bootstrap3'
   },
+  vendorFiles: {
+    'handlebars.js': null
+  },
   hinting: false
 });
 
+app.import('bower_components/ember/ember-template-compiler.js');
 app.import('bower_components/bootstrap/dist/js/bootstrap.js');
 app.import('bower_components/bootstrap/dist/css/bootstrap.css');
 app.import('bower_components/bootstrap/dist/css/bootstrap.css.map', {
@@ -42,5 +46,6 @@ app.import('vendor/codemirror/sql-hint.js');
 app.import('vendor/codemirror/show-hint.js');
 app.import('vendor/codemirror/codemirror.css');
 app.import('vendor/codemirror/show-hint.css');
+app.import('vendor/dagre.min.js');
 
 module.exports = app.toTree();

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/components/alert-message-widget.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/alert-message-widget.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/alert-message-widget.js
index fd8c1d7..df44c37 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/alert-message-widget.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/alert-message-widget.js
@@ -19,17 +19,17 @@
 import Ember from 'ember';
 
 export default Ember.Component.extend({
-  click: function () {
-    this.toggleProperty('message.isExpanded');
-
-    if (!this.get('message.isExpanded')) {
-      this.sendAction('removeLater', this.get('message'));
-    }
-  },
-
   actions: {
     remove: function () {
       this.sendAction('removeMessage', this.get('message'));
+    },
+
+    toggleMessage: function() {
+      this.toggleProperty('message.isExpanded');
+
+      if (!this.get('message.isExpanded')) {
+        this.sendAction('removeLater', this.get('message'));
+      }
     }
   }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/components/collapsible-widget.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/collapsible-widget.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/collapsible-widget.js
index 4425b59..f2081f2 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/collapsible-widget.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/collapsible-widget.js
@@ -28,6 +28,11 @@ export default Ember.Component.extend({
       if (this.get('isExpanded')) {
         this.sendAction('expanded', this.get('heading'), this.get('toggledParam'));
       }
+    },
+
+    sendControlAction: function(action) {
+      this.set('controlAction', action);
+      this.sendAction('controlAction', this.get('heading'), this.get('toggledParam'));
     }
   }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/components/modal-widget.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/modal-widget.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/modal-widget.js
index b9179bb..790e84b 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/modal-widget.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/modal-widget.js
@@ -25,10 +25,32 @@ export default Ember.Component.extend(Ember.I18n.TranslateableProperties, {
     }.bind(this));
   }.on('didInsertElement'),
 
+  keyPress: function(e) {
+    Ember.run.debounce(this, function() {
+      if (e.which === 13) {
+        this.send('ok');
+      } else if (e.which === 27) {
+        this.send('close');
+      }
+    }, 200)
+  },
+
+  setupEvents: function() {
+    this.$(document).on('keyup', Ember.$.proxy(this.keyPress, this));
+  }.on('didInsertElement'),
+
+  destroyEvents: function() {
+    this.$(document).off('keyup', Ember.$.proxy(this.keyPress, this));
+  }.on('willDestroyElement'),
+
   actions: {
     ok: function () {
       this.$('.modal').modal('hide');
       this.sendAction('ok');
+    },
+    close: function () {
+      this.$('.modal').modal('hide');
+      this.sendAction('close');
     }
   }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/components/notify-widget.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/notify-widget.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/notify-widget.js
new file mode 100644
index 0000000..5a37f13
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/notify-widget.js
@@ -0,0 +1,32 @@
+/**
+* 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({
+  tagName: 'notifications',
+  classNames: ['notifications-container'],
+  notifications : Ember.computed.alias('notify.notifications'),
+
+  actions: {
+    removeNotification: function(notification) {
+      this.notify.removeNotification(notification);
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/components/number-range-widget.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/number-range-widget.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/number-range-widget.js
index ad98beb..4ab1dba 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/number-range-widget.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/number-range-widget.js
@@ -24,27 +24,25 @@ export default Ember.Component.extend({
 
     var slider;
 
-    this.$(function() {
-      if (!self.get('numberRange.from') && !self.get('numberRange.to')) {
-        self.get('numberRange').set('from', self.get('numberRange.min'));
-        self.get('numberRange').set('to', self.get('numberRange.max'));
-      }
+    if (!self.get('numberRange.from') && !self.get('numberRange.to')) {
+      self.get('numberRange').set('from', self.get('numberRange.min'));
+      self.get('numberRange').set('to', self.get('numberRange.max'));
+    }
 
-      slider = self.$( ".slider" ).slider({
-        range: true,
-        min: self.get('numberRange.min'),
-        max: self.get('numberRange.max'),
-        units: self.get('numberRange.units'),
-        values: [ self.get('numberRange.from'), self.get('numberRange.to') ],
-        slide: function (event, ui) {
-          self.set('numberRange.from', ui.values[0]);
-          self.set('numberRange.to', ui.values[1]);
-        },
+    slider = self.$( ".slider" ).slider({
+      range: true,
+      min: self.get('numberRange.min'),
+      max: self.get('numberRange.max'),
+      units: self.get('numberRange.units'),
+      values: [ self.get('numberRange.from'), self.get('numberRange.to') ],
+      slide: function (event, ui) {
+        self.set('numberRange.from', ui.values[0]);
+        self.set('numberRange.to', ui.values[1]);
+      },
 
-        change: function () {
-          self.sendAction('rangeChanged', self.get('numberRange'));
-        }
-      });
+      change: function () {
+        self.sendAction('rangeChanged', self.get('numberRange'));
+      }
     });
 
     this.set('slider', slider);

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/components/query-tabs.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/query-tabs.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/query-tabs.js
new file mode 100644
index 0000000..0ef6768
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/query-tabs.js
@@ -0,0 +1,121 @@
+/**
+ * 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({
+  tabClassNames : "fa queries-icon query-context-tab",
+  openOverlayAction   : 'openOverlay',
+  closeOverlayAction  : 'closeOverlay',
+
+  tabs: [
+    Ember.Object.create({
+      iconClass: 'fa-code',
+      action: 'setDefaultActive'
+    }),
+    Ember.Object.create({
+      iconClass: 'fa-gear',
+      action: 'toggleOverlay',
+      template: 'settings',
+      outlet: 'overlay',
+      into: 'open-queries'
+    }),
+    Ember.Object.create({
+      iconClass: 'fa-bar-chart',
+      action: 'toggleOverlay',
+      template: 'visual-explain',
+      outlet: 'overlay',
+      into: 'index'
+    }),
+    Ember.Object.create({
+      iconClass: 'text-icon',
+      text: 'TEZ',
+      action: 'toggleOverlay',
+      template: 'tez-ui',
+      outlet: 'overlay',
+      into: 'index'
+    }),
+    Ember.Object.create({
+      iconClass: 'fa-envelope',
+      action: 'toggleOverlay',
+      template: 'messages',
+      outlet: 'overlay',
+      into: 'open-queries',
+      badgeProperty: 'count'
+    })
+  ],
+
+  setDefaultTab: function() {
+    var defaultTab = this.get('tabs.firstObject');
+
+    defaultTab.set('active', true);
+    this.set('default', defaultTab);
+    this.set('active', defaultTab);
+  }.on('init'),
+
+  setupTabsBadges: function() {
+    var tabs = this.get('tabs');
+    var self = this;
+
+    tabs.map(function(tab) {
+      if (tab.get('badgeProperty')) {
+        var controller = self.container.lookup('controller:' + tab.get('template'));
+        tab.set('controller', controller);
+
+        Ember.oneWay(tab, 'badge', 'controller.count');
+      }
+    });
+  }.on('init'),
+
+  closeActiveOverlay: function() {
+    this.sendAction('closeOverlayAction', this.get('active'));
+  },
+
+  openOverlay: function(tab) {
+    this.closeActiveOverlay();
+    this.set('active.active', false);
+    tab.set('active', true);
+    this.set('active', tab);
+    this.sendAction('openOverlayAction', tab);
+  },
+
+  setDefaultActive: function() {
+    var active     = this.get('active');
+    var defaultTab = this.get('default');
+
+    if (active !== defaultTab) {
+      this.closeActiveOverlay();
+      defaultTab.set('active', true);
+      active.set('active', false);
+      this.set('active', defaultTab);
+    }
+  },
+
+  actions: {
+    toggleOverlay: function(tab) {
+      if (tab !== this.get('default') && tab.get('active')) {
+        this.setDefaultActive();
+      } else {
+        this.openOverlay(tab);
+      }
+    },
+
+    setDefaultActive: function() {
+      this.setDefaultActive();
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/components/typeahead-widget.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/typeahead-widget.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/typeahead-widget.js
index 34c1f4b..51f09be 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/components/typeahead-widget.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/components/typeahead-widget.js
@@ -23,15 +23,60 @@ export default Typeahead.extend(Ember.I18n.TranslateableProperties, {
   didInsertElement: function() {
     this._super();
 
-    if(!this.get('selection') && this.get('content.firstObject')) {
+    if (!this.get('selection') && this.get('content.firstObject')) {
       this.set('selection', this.get('content.firstObject'));
     }
 
     this.selectize.on('dropdown_close', Ember.$.proxy(this.onClose, this));
   },
 
+  removeExcludedObserver: function() {
+    var self    = this;
+    var options = this.get('content');
+
+    if (!options) {
+      options = this.removeExcluded(true);
+      this.set('content', options);
+    } else {
+      this.removeExcluded();
+    }
+  }.observes('excluded.@each.key').on('init'),
+
+  removeExcluded: function(shouldReturn) {
+    var self            = this;
+    var excluded        = this.get('excluded') || [];
+    var options         = this.get('options');
+    var selection       = this.get('selection');
+    var objectToModify  = this.get('content');
+    var objectsToRemove = [];
+    var objectsToAdd    = [];
+
+    if (!options) {
+      return;
+    }
+
+    if (shouldReturn) {
+      objectToModify = Ember.copy(options);
+    }
+
+    if (options) {
+      options.forEach(function(option, index) {
+        if (excluded.contains(option) && option !== selection) {
+          objectsToRemove.push(option);
+        } else if (!objectToModify.contains(option)) {
+          objectsToAdd.push(option);
+        }
+      });
+    }
+
+    objectToModify.removeObjects(objectsToRemove);
+    objectToModify.pushObjects(objectsToAdd);
+
+    return objectToModify;
+  },
+
   onClose: function() {
-    if(!this.get('selection') && this.get('prevSelection')) {
+    if (!this.get('selection') && this.get('prevSelection')) {
       this.set('selection', this.get('prevSelection'));
     }
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/alerts.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/alerts.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/alerts.js
deleted file mode 100644
index be70566..0000000
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/alerts.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * 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.ArrayController.extend({
-  pushObject: function (object) {
-    object.typeClass = 'alert-' + object.type;
-    this._super(object);
-    this.removeLater(object);
-  },
-
-  removeLater: function (object) {
-    var self = this;
-
-    Ember.run.later(function() {
-      if (!object.isExpanded) {
-        self.removeObject(object);
-      }
-    }, 5000);
-  },
-
-  actions: {
-    remove: function (message) {
-      this.removeObject(message);
-    },
-
-    removeLater: function (message) {
-      this.removeLater(message);
-    }
-  }
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/databases.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/databases.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/databases.js
index 6a259ba..0b103cd 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/databases.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/databases.js
@@ -30,13 +30,13 @@ export default Ember.ArrayController.extend({
   dbTables: Ember.computed.alias('controllers.' + constants.namingConventions.tables),
   dbColumns: Ember.computed.alias('controllers.' + constants.namingConventions.columns),
 
-  _handleTablesError: function (err) {
-    this.send('addAlert', constants.alerts.error, err.responseText, "alerts.errors.get.tables");
+  _handleTablesError: function (error) {
+    this.notify.error(error.responseJSON.message, error.responseJSON.trace);
     this.set('isLoading', false);
   },
 
-  _handleColumnsError: function (err) {
-    this.send('addAlert', constants.alerts.error, err.responseText, "alerts.errors.get.columns");
+  _handleColumnsError: function (error) {
+    this.notify.error(error.responseJSON.message, error.responseJSON.trace);
     this.set('isLoading', false);
   },
 
@@ -191,7 +191,52 @@ export default Ember.ArrayController.extend({
     return defer.promise;
   },
 
+  tableControls: Ember.A([
+    Ember.Object.create({
+      icon: 'fa-list',
+      action: 'loadSampleData',
+      tooltip: Ember.I18n.t('tooltips.loadSample')
+    })
+  ]),
+
+  panelIconActions: function () {
+    return [
+      Ember.Object.create({
+        icon: 'fa-refresh',
+        action: 'refreshDatabaseExplorer'
+      })
+    ];
+  }.property(),
+
   actions: {
+    refreshDatabaseExplorer: function() {
+      var self = this;
+      var selectedDatabase = this.get('selectedDatabase');
+
+      this.store.unloadAll('database');
+      this.store.fetchAll('database').then(function() {
+        var database = self.get('model').findBy('id', selectedDatabase.get('id'));
+        self.set('selectedDatabase', database);
+      }).catch(function(response) {
+        self.notify.error(response.responseJSON.message, response.responseJSON.trace);
+      });
+    },
+
+    loadSampleData: function(tableName, database) {
+      var self = this;
+      this.send('addQuery', Ember.I18n.t('titles.tableSample', { tableName: tableName }));
+
+      Ember.run.later(function() {
+        var query = constants.sampleDataQuery.fmt(tableName);
+
+        self.set('selectedDatabase', database);
+        self.get('openQueries.currentQuery')
+          .set('fileContent', query);
+
+        self.send('executeQuery');
+      });
+    },
+
     getTables: function (dbName) {
       var database = this.findBy('name', dbName),
           tables = database.tables,
@@ -257,6 +302,7 @@ export default Ember.ArrayController.extend({
           resultsTab = this.get('tabs').findBy('view', constants.namingConventions.databaseSearch),
           tableSearchResults = this.get('tableSearchResults');
 
+      this.set('tablesSearchTerm', searchTerm);
       resultsTab.set('visible', true);
       this.set('selectedTab', resultsTab);
       this.set('columnSearchTerm', '');
@@ -351,4 +397,4 @@ export default Ember.ArrayController.extend({
       });
     }
   }
-});
\ No newline at end of file
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index.js
index 954b42b..df2d088 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index.js
@@ -42,10 +42,19 @@ export default Ember.Controller.extend({
   visualExplain: Ember.computed.alias('controllers.' + constants.namingConventions.visualExplain),
   tezUI: Ember.computed.alias('controllers.' + constants.namingConventions.tezUI),
 
+  isQueryTabActive: function() {
+    return !this.get('tezUI.showOverlay') && !this.get('visualExplain.showOverlay') && !this.get('settings.showOverlay');
+  }.property('tezUI.showOverlay', 'visualExplain.showOverlay', 'settings.showOverlay'),
+
   shouldShowTez: function() {
     return this.get('model.dagId') && this.get('tezUI.isTezViewAvailable');
   }.property('model.dagId', 'tezUI.isTezViewAvailable'),
 
+  shouldShowVisualExplain: function () {
+    return this.get('openQueries.currentQuery.fileContent');
+  }.property('openQueries.currentQuery.fileContent'),
+
+
   canExecute: function () {
     var isModelRunning = this.get('model.isRunning');
     var hasParams = this.get('queryParams.length');
@@ -84,11 +93,12 @@ export default Ember.Controller.extend({
     currentParams.setObjects(updatedParams);
   }.observes('openQueries.currentQuery.fileContent'),
 
-  _executeQuery: function (shouldExplain) {
+  _executeQuery: function (shouldExplain, shouldGetVisualExplain) {
     var queryId,
         query,
         finalQuery,
         job,
+        defer = Ember.RSVP.defer(),
         originalModel = this.get('model');
 
     job = this.store.createRecord(constants.namingConventions.job, {
@@ -110,15 +120,55 @@ export default Ember.Controller.extend({
 
     query = this.get('openQueries').getQueryForModel(originalModel);
 
-    finalQuery = this.buildQuery(query, shouldExplain);
+    query = this.buildQuery(query, shouldExplain, shouldGetVisualExplain);
+
+    // for now we won't support multiple queries
+    // buildQuery will return false it multiple queries
+    // are selected
+    if (!query) {
+      originalModel.set('isRunning', false);
+      defer.reject({
+        responseJSON: {
+          message: 'Running multiple queries is not supported.'
+        }
+      });
+
+      return defer.promise;
+    }
+
+    finalQuery = query;
     finalQuery = this.bindQueryParams(finalQuery);
     finalQuery = this.prependQuerySettings(finalQuery);
 
     job.set('forcedContent', finalQuery);
 
+    if (shouldGetVisualExplain) {
+      return this.getVisualExplainJson(job, originalModel);
+    }
+
     return this.saveQuery(job, originalModel);
   },
 
+  getVisualExplainJson: function (job, originalModel) {
+    var self = this;
+    var defer = Ember.RSVP.defer();
+
+    job.save().then(function () {
+      self.get('results').getResultsJson(job).then(function (json) {
+        defer.resolve(json);
+        originalModel.set('isRunning', undefined);
+      }, function (err) {
+        defer.reject(err);
+        originalModel.set('isRunning', undefined);
+      });
+    }, function (err) {
+      defer.reject(err);
+        originalModel.set('isRunning', undefined);
+    });
+
+    return defer.promise;
+  },
+
   saveQuery: function (job, originalModel) {
     var defer = Ember.RSVP.defer(),
         self = this,
@@ -158,35 +208,41 @@ export default Ember.Controller.extend({
     return query;
   },
 
-  buildQuery: function (query, shouldExplain) {
+  buildQuery: function (query, shouldExplain, shouldGetVisualExplain) {
     var selections = this.get('openQueries.highlightedText'),
         isQuerySelected = selections && selections[0] !== "",
-        queryComponents = this.extractComponents(query.get('fileContent')),
+        queryContent = query ? query.get('fileContent') : '',
+        queryComponents = this.extractComponents(queryContent),
         finalQuery = '',
-        queries;
+        queries = null;
 
     if (isQuerySelected) {
-      queries = selections.map(function (s) {
-        return s.replace(";", "");
-      });
-    } else {
-      queries = queryComponents.queryString.split(';');
-      queries = queries.filter(Boolean);
+      queryComponents.queryString = selections.join('');
+    }
+
+    queries = queryComponents.queryString.split(';');
+    queries = queries.map(function(s) {
+      return s.trim();
+    });
+    queries = queries.filter(Boolean);
+
+    // return false if multiple queries are selected
+    // @FIXME: Remove this to support multiple queries
+    if (queries.length > 1) {
+      return false;
     }
 
     queries = queries.map(function (query) {
       if (shouldExplain) {
-        if (query.indexOf(constants.namingConventions.explainPrefix) === -1) {
+        query = query.replace(/explain|formatted/gi, '').trim();
+
+        if (shouldGetVisualExplain) {
+          return constants.namingConventions.explainFormattedPrefix + query;
+        } else {
           return constants.namingConventions.explainPrefix + query;
         }
-
-        return query;
       } else {
-        if (query.indexOf(constants.namingConventions.explainPrefix) > -1) {
-          return query.replace(constants.namingConventions.explainPrefix, '');
-        }
-
-        return query;
+        return query.replace(/explain|formatted/gi, '').trim();
       }
     });
 
@@ -199,6 +255,7 @@ export default Ember.Controller.extend({
     }
 
     finalQuery += queries.join(";");
+    finalQuery += ";";
     return finalQuery;
   },
 
@@ -278,6 +335,10 @@ export default Ember.Controller.extend({
     });
   }.observes('content'),
 
+  selectedDatabaseChanged: function() {
+    this.set('content.dataBase', this.get('databases.selectedDatabase.name'));
+  }.observes('databases.selectedDatabase'),
+
   csvUrl: function () {
     if (this.get('content.constructor.typeKey') !== constants.namingConventions.job) {
       return;
@@ -309,7 +370,7 @@ export default Ember.Controller.extend({
         items.push(
           Ember.Object.create({
             title: Ember.I18n.t('buttons.saveCsv'),
-            href: this.get('csvUrl')
+            action: 'downloadAsCSV'
           })
         );
       }
@@ -350,7 +411,7 @@ export default Ember.Controller.extend({
     }).then(function (response) {
       self.pollSaveToHDFS(response);
     }, function (response) {
-      self.send('addAlert', constants.alerts.error, response.message, "alerts.errors.save.results");
+      self.notify.error(response.responseJSON.message, response.responseJSON.trace);
     });
   },
 
@@ -367,7 +428,7 @@ export default Ember.Controller.extend({
           self.set('content.isRunning', false);
         }
       }, function (response) {
-        self.send('addAlert', constants.alerts.error, response.message, "alerts.errors.save.results");
+        self.notify.error(response.responseJSON.message, response.responseJSON.trace);
       });
     }, 2000);
   },
@@ -386,6 +447,25 @@ export default Ember.Controller.extend({
       this.saveToHDFS();
     },
 
+    downloadAsCSV: function() {
+      var self = this,
+          defer = Ember.RSVP.defer();
+
+      this.send('openModal', 'modal-save', {
+        heading: "modals.download.csv",
+        text: this.get('content.title'),
+        defer: defer
+      });
+
+      defer.promise.then(function (text) {
+        // download file ...
+        var urlString = "%@/?fileName=%@.csv";
+        var url = self.get('csvUrl');
+        url = urlString.fmt(url, text);
+        window.open(url);
+      });
+    },
+
     insertUdf: function (item) {
       var query = this.get('openQueries').getQueryForModel(this.get('model'));
 
@@ -418,17 +498,16 @@ export default Ember.Controller.extend({
     addQuery: (function () {
       var idCounter = 0;
 
-      return function () {
+      return function (workSheetName) {
         var model = this.store.createRecord(constants.namingConventions.savedQuery, {
           dataBase: this.get('databases.selectedDatabase.name'),
-          title: 'New Query',
-          type: constants.namingConventions.savedQuery,
+          title: workSheetName ? workSheetName : Ember.I18n.t('titles.query.tab'),
           queryFile: '',
           id: 'fixture_' + idCounter
         });
 
-        if (idCounter) {
-          model.set('title', model.get('title') + ' (' + idCounter + ')')
+        if (idCounter && !workSheetName) {
+          model.set('title', model.get('title') + ' (' + idCounter + ')');
         }
 
         idCounter++;
@@ -438,26 +517,34 @@ export default Ember.Controller.extend({
     }()),
 
     saveQuery: function () {
+      //case 1. Save a new query from a new query tab -> route changes to new id
+      //case 2. Save a new query from an existing query tab -> route changes to new id
+      //case 3. Save a new query from a job tab -> route doesn't change
+      //case 4. Update an existing query tab. -> route doesn't change
+
       var self = this,
-          wasNew = this.get('model.isNew'),
           defer = Ember.RSVP.defer();
 
       this.set('model.dataBase', this.get('databases.selectedDatabase.name'));
 
-      this.send('openModal', 'modal-save', {
-        heading: "modals.save.heading",
+      this.send('openModal', 'modal-save-query', {
+        heading: 'modals.save.heading',
+        message: 'modals.save.overwrite',
         text: this.get('content.title'),
+        content: this.get('content'),
         defer: defer
       });
 
-      defer.promise.then(function (text) {
-        self.get('content').set('title', text);
-
-        self.get('openQueries').save(self.get('content')).then(function () {
-          if (wasNew) {
-            self.transitionToRoute(constants.namingConventions.subroutes.savedQuery, self.get('model.id'));
-          }
-        });
+      defer.promise.then(function (result) {
+        if (result.get('overwrite')) {
+          self.get('openQueries').save(self.get('content'), null, true, result.get('text'));
+        } else {
+          self.get('openQueries').save(self.get('content'), null, false, result.get('text')).then(function (newId) {
+            if (self.get('model.constructor.typeKey') !== constants.namingConventions.job) {
+              self.transitionToRoute(constants.namingConventions.subroutes.savedQuery, newId);
+            }
+          });
+        }
       });
     },
 
@@ -476,7 +563,8 @@ export default Ember.Controller.extend({
 
         self.transitionToRoute(constants.namingConventions.subroutes.historyQuery, job.get('id'));
       }, function (err) {
-        self.send('addAlert', constants.alerts.error, err.responseText, "alerts.errors.save.query");
+        var errorBody = err.responseJSON.trace ? err.responseJSON.trace : false;
+        self.notify.error(err.responseJSON.message, errorBody);
       });
     },
 
@@ -488,11 +576,13 @@ export default Ember.Controller.extend({
 
         self.transitionToRoute(constants.namingConventions.subroutes.historyQuery, job.get('id'));
       }, function (err) {
-        self.send('addAlert', constants.alerts.error, err.responseText, "alerts.errors.save.query");
+        this.notify.error(err.responseJSON.message, err.responseJSON.trace);
       });
     },
 
     toggleOverlay: function (targetController) {
+      var self = this;
+
       if (this.get('visualExplain.showOverlay') && targetController !== 'visualExplain') {
         this.set('visualExplain.showOverlay', false);
       } else if (this.get('tezUI.showOverlay') && targetController !== 'tezUI') {
@@ -501,12 +591,28 @@ export default Ember.Controller.extend({
         this.set('settings.showOverlay', false);
       }
 
+      if (!targetController) {
+        return;
+      }
+
       if (targetController !== 'settings') {
         //set content for visual explain and tez ui.
         this.set(targetController + '.content', this.get('content'));
       }
 
-      this.toggleProperty(targetController + '.showOverlay');
+      if (targetController === 'visualExplain' && !this.get(targetController + '.showOverlay')) {
+        this._executeQuery(true, true).then(function (json) {
+          //this condition should be changed once we change the way of retrieving this json
+          if (json['STAGE PLANS']['Stage-1']) {
+            self.set(targetController + '.json', json);
+            self.toggleProperty(targetController + '.showOverlay');
+          }
+        }, function (err) {
+          self.notify.error(err.responseJSON.message, err.responseJSON.trace);
+        });
+      } else {
+        this.toggleProperty(targetController + '.showOverlay');
+      }
     }
   }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/explain.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/explain.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/explain.js
index dc75280..e61df51 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/explain.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/explain.js
@@ -39,30 +39,44 @@ export default Ember.ObjectController.extend({
     if (cachedExplain) {
       this.formatExplainResults(cachedExplain);
     } else {
-      this.getExplain();
+      this.getExplain(true);
     }
   }.observes('content'),
 
-  getExplain: function () {
+  getExplain: function (firstPage, rows) {
     var self = this;
     var url = this.container.lookup('adapter:application').buildURL();
-    url += '/' + constants.namingConventions.jobs + '/' + this.get('content.id') + '/results?first=true';
+    url += '/' + constants.namingConventions.jobs + '/' + this.get('content.id') + '/results';
+
+    if (firstPage) {
+      url += '?first=true';
+    }
 
     Ember.$.getJSON(url).then(function (data) {
-      var explainSet = self.get('cachedExplains').pushObject(Ember.Object.create({
-        id: self.get('content.id'),
-        explain: data
-      }));
+      var explainSet;
+
+      //if rows from a previous page read exist, prepend them
+      if (rows) {
+        data.rows.unshiftObjects(rows);
+      }
 
-      self.set('content.explain', explainSet);
+      if (!data.hasNext) {
+        explainSet = self.get('cachedExplains').pushObject(Ember.Object.create({
+          id: self.get('content.id'),
+          explain: data
+        }));
 
-      self.formatExplainResults(explainSet);
+        self.set('content.explain', explainSet);
+
+        self.formatExplainResults(explainSet);
+      } else {
+        self.getExplain(false, data.rows);
+      }
     });
   },
 
   formatExplainResults: function (explainSet) {
     var formatted = [],
-        orderedNodes = [],
         currentNode,
         currentNodeWhitespace,
         previousNode,
@@ -116,4 +130,4 @@ export default Ember.ObjectController.extend({
 
     this.set('formattedExplain', formatted);
   }
-});
\ No newline at end of file
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/logs.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/logs.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/logs.js
index 02edc86..59c0892 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/logs.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/logs.js
@@ -28,13 +28,22 @@ export default Ember.ObjectController.extend({
   reloadJobLogs: function (job) {
     var self = this,
         defer = Ember.RSVP.defer(),
-        handleError = function (err) {
-          self.send('addAlert', constants.namingConventions.alerts.error, err.responseText);
+        handleError = function (error) {
+          job.set('isRunning', false);
+
+          if (typeof error === "string") {
+            self.notify.error(error);
+          } else {
+            self.notify.error(error.responseJSON.message, error.responseJSON.trace);
+          }
           defer.reject();
         };
 
     job.reload().then(function () {
-      self.get('files').reload(job.get('logFile')).then(function (file) {
+      if (utils.insensitiveCompare(job.get('status'), constants.statuses.error)) {
+        handleError(job.get('statusMessage'));
+      } else {
+        self.get('files').reload(job.get('logFile')).then(function (file) {
         var fileContent = file.get('fileContent');
 
         if (fileContent) {
@@ -42,9 +51,10 @@ export default Ember.ObjectController.extend({
         }
 
         defer.resolve();
-      },function (err) {
-        handleError(err);
-      });
+        },function (err) {
+          handleError(err);
+        });
+      }
     }, function (err) {
       handleError(err);
     });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/results.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/results.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/results.js
index 7977541..9a50f27 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/results.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/results.js
@@ -22,6 +22,25 @@ import utils from 'hive/utils/functions';
 
 export default Ember.ObjectController.extend({
   cachedResults: [],
+  formattedResults: [],
+
+  processResults: function() {
+    var results = this.get('results');
+
+    if (!results || !results.schema || !results.rows) {
+      return;
+    }
+
+    var schema = results.schema.map(function(column) {
+      return {
+        name: column[0],
+        type: column[1],
+        index: column[2]
+      }
+    });
+
+    this.set('formattedResults', { schema: schema, rows: results.rows });
+  }.observes('results'),
 
   keepAlive: function (job) {
     Ember.run.later(this, function () {
@@ -73,6 +92,20 @@ export default Ember.ObjectController.extend({
     return this.cachedResults.findBy('id', this.get('content.id')).results.indexOf(this.get('results')) <= 0;
   }.property('results'),
 
+  getResultsJson: function (job) {
+    var defer = Ember.RSVP.defer();
+    var url = this.container.lookup('adapter:application').buildURL();
+    url += '/' + constants.namingConventions.jobs + '/' + job.get('id') + '/results?first=true';
+
+    Ember.$.getJSON(url).then(function (results) {
+      defer.resolve(JSON.parse(results.rows[0][0]));
+    }, function (err) {
+      defer.reject(err);
+    });
+
+    return defer.promise;
+  },
+
   actions: {
     getNextPage: function (firstPage, job) {
       var self = this;
@@ -115,6 +148,7 @@ export default Ember.ObjectController.extend({
         if (firstPage) {
           self.keepAlive(job || self.get('content'));
         }
+
       }, function (err) {
         self.set('error', err.responseText);
       });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/messages.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/messages.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/messages.js
new file mode 100644
index 0000000..6de1c64
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/messages.js
@@ -0,0 +1,33 @@
+/**
+* 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.Controller.extend({
+  messages: Ember.computed.alias('notify.messages'),
+  count: Ember.computed.alias('messages.length'),
+
+  actions: {
+    removeMessage: function(message) {
+      this.notify.removeMessage(message);
+    },
+
+    removeAllMessages: function() {
+      this.notify.removeAllMessages();
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/modal-save-query.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/modal-save-query.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/modal-save-query.js
new file mode 100644
index 0000000..d878bc7
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/modal-save-query.js
@@ -0,0 +1,42 @@
+/**
+ * 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 ModalSave from '../controllers/modal-save';
+import constants from '../utils/constants';
+
+export default ModalSave.extend({
+  showMessage: function () {
+    var content = this.get('content');
+
+    return !content.get('isNew') &&
+            content.get('title') === this.get('text') &&
+            content.get('constructor.typeKey') !== constants.namingConventions.job;
+  }.property('content.isNew', 'text'),
+
+  actions: {
+    save: function () {
+      this.send('closeModal');
+
+      this.defer.resolve(Ember.Object.create({
+        text: this.get('text'),
+        overwrite: this.get('showMessage')
+      }));
+    }
+  }
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/open-queries.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/open-queries.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/open-queries.js
index 2abfff6..a0c033e 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/open-queries.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/open-queries.js
@@ -154,7 +154,7 @@ export default Ember.ArrayController.extend({
     return defer.promise;
   },
 
-  save: function (model, query) {
+  save: function (model, query, isUpdating, newTitle) {
     var tab = this.getTabForModel(model),
         self = this,
         wasNew,
@@ -168,6 +168,7 @@ export default Ember.ArrayController.extend({
 
     if (model.get('isNew')) {
       wasNew = true;
+      model.set('title', newTitle);
       model.set('id', null);
     }
 
@@ -175,16 +176,30 @@ export default Ember.ArrayController.extend({
     if (model.get('constructor.typeKey') === constants.namingConventions.job) {
       model = this.store.createRecord(constants.namingConventions.savedQuery, {
         dataBase: this.get('databases.selectedDatabase.name'),
-        title: model.get('title'),
+        title: newTitle,
         queryFile: model.get('queryFile'),
         owner: model.get('owner')
       });
+    } else {
+      tab.set('name', newTitle);
+    }
+
+    //if saving a new query from an existing one create a new record and save it
+    if (!isUpdating && !model.get('isNew') && model.get('constructor.typeKey') !== constants.namingConventions.job) {
+      model = this.store.createRecord(constants.namingConventions.savedQuery, {
+        dataBase: this.get('databases.selectedDatabase.name'),
+        title: newTitle,
+        owner: model.get('owner')
+      });
+
+      wasNew = true;
     }
 
     model.save().then(function (updatedModel) {
-      tab.set('name', updatedModel.get('title'));
       jobModel.set('queryId', updatedModel.get('id'));
 
+      tab.set('isDirty', false);
+
       var content = self.get('index').prependQuerySettings(query.get('fileContent'));
       //update query tab path with saved model id if its a new record
       if (wasNew) {
@@ -198,7 +213,7 @@ export default Ember.ArrayController.extend({
             self.pushObject(updatedFile);
             self.set('currentQuery', updatedFile);
 
-            defer.resolve();
+            defer.resolve(updatedModel.get('id'));
           }, function (err) {
             defer.reject(err);
           });
@@ -209,7 +224,7 @@ export default Ember.ArrayController.extend({
         query.set('fileContent', content);
         query.save().then(function () {
           self.toggleProperty('tabUpdated');
-          defer.resolve();
+          defer.resolve(updatedModel.get('id'));
         }, function (err) {
           defer.reject(err);
         });
@@ -259,34 +274,83 @@ export default Ember.ArrayController.extend({
          hasSettings;
   },
 
+  isDirty: function(model) {
+    var query = this.getQueryForModel(model);
+
+    if (model.get('isNew') && !query.get('fileContent')) {
+      return false;
+    }
+
+    if (query && query.get('isDirty')) {
+      return true;
+    }
+
+    return !!(!model.get('queryId') && model.get('isDirty'));
+
+
+  },
+
+  updatedDeletedQueryTab: function (model) {
+    var tab = this.getTabForModel(model);
+
+    if (tab) {
+      this.closeTab(tab);
+    }
+  },
+
+  dirtyObserver: function () {
+    var tab;
+    var model = this.get('index.model');
+
+    if (model) {
+      tab = this.getTabForModel(model);
+
+      if (tab) {
+        tab.set('isDirty', this.isDirty(model));
+      }
+    }
+  }.observes('currentQuery.isDirty', 'currentQuery.fileContent'),
+
+  closeTab: function (tab, goToNextTab) {
+    var remainingTabs = this.get('queryTabs').without(tab);
+
+    this.set('queryTabs', remainingTabs);
+
+    //remove cached results set
+    if (tab.type === constants.namingConventions.job) {
+      this.get('jobResults').clearCachedResultsSet(tab.id);
+      this.get('jobExplain').clearCachedExplainSet(tab.id);
+    }
+
+    if (goToNextTab) {
+      this.navigateToLastTab();
+    }
+  },
+
+  navigateToLastTab: function () {
+    var lastTab = this.get('queryTabs.lastObject');
+
+    if (lastTab) {
+      if (lastTab.type === constants.namingConventions.job) {
+        this.transitionToRoute(constants.namingConventions.subroutes.historyQuery, lastTab.id);
+      } else {
+        this.transitionToRoute(constants.namingConventions.subroutes.savedQuery, lastTab.id);
+      }
+    } else {
+      this.get('index').send('addQuery');
+    }
+  },
+
   actions: {
     removeQueryTab: function (tab) {
       var self = this,
-          defer,
-          remainingTabs = this.get('queryTabs').without(tab),
-          lastTab = remainingTabs.get('lastObject'),
-          closeTab = function () {
-            self.set('queryTabs', remainingTabs);
-
-            //remove cached results set
-            if (tab.type === constants.namingConventions.job) {
-              self.get('jobResults').clearCachedResultsSet(tab.id);
-              self.get('jobExplain').clearCachedExplainSet(tab.id);
-            }
-
-            if (lastTab.type === constants.namingConventions.job) {
-              self.transitionToRoute(constants.namingConventions.subroutes.historyQuery, lastTab.id);
-            } else {
-              self.transitionToRoute(constants.namingConventions.subroutes.savedQuery, lastTab.id);
-            }
-          };
+          defer;
 
       this.store.find(tab.type, tab.id).then(function (model) {
         var query = self.getQueryForModel(model);
 
-        if ((model.get('isNew') && !query.get('fileContent')) ||
-            (!model.get('isNew') && !query.get('isDirty'))) {
-          closeTab();
+        if (!self.isDirty(model)) {
+          self.closeTab(tab, true);
         } else {
           defer = Ember.RSVP.defer();
           self.send('openModal',
@@ -300,11 +364,11 @@ export default Ember.ArrayController.extend({
           defer.promise.then(function (text) {
             model.set('title', text);
             self.save(model, query).then(function () {
-              closeTab();
+              self.closeTab(tab, true);
             });
           }, function () {
             model.rollback();
-            closeTab();
+            self.closeTab(tab, true);
           });
         }
       });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/queries.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/queries.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/queries.js
index 0195dc2..aec2273 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/queries.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/queries.js
@@ -21,7 +21,11 @@ import FilterableMixin from 'hive/mixins/filterable';
 import constants from 'hive/utils/constants';
 
 export default Ember.ArrayController.extend(FilterableMixin, {
-  needs: [ constants.namingConventions.routes.history ],
+  needs: [ constants.namingConventions.routes.history,
+           constants.namingConventions.openQueries ],
+
+  history: Ember.computed.alias('controllers.' + constants.namingConventions.routes.history),
+  openQueries: Ember.computed.alias('controllers.' + constants.namingConventions.openQueries),
 
   sortAscending: true,
   sortProperties: [],
@@ -68,9 +72,11 @@ export default Ember.ArrayController.extend(FilterableMixin, {
 
   actions: {
     executeAction: function (action, savedQuery) {
+      var self = this;
+
       switch (action) {
         case "buttons.history":
-          this.get('controllers.' + constants.namingConventions.routes.history).filterBy('queryId', savedQuery.get('id'), true);
+          this.get('history').filterBy('queryId', savedQuery.get('id'), true);
           this.transitionToRoute(constants.namingConventions.routes.history);
           break;
         case "buttons.delete":
@@ -85,6 +91,7 @@ export default Ember.ArrayController.extend(FilterableMixin, {
 
           defer.promise.then(function () {
             savedQuery.destroyRecord();
+            self.get('openQueries').updatedDeletedQueryTab(savedQuery);
           });
 
           break;

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/settings.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/settings.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/settings.js
index 6ac0828..ddc5b1e 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/settings.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/settings.js
@@ -27,9 +27,22 @@ export default Ember.ArrayController.extend({
 
   index: Ember.computed.alias('controllers.' + constants.namingConventions.index),
   openQueries: Ember.computed.alias('controllers.' + constants.namingConventions.openQueries),
+  sessionTag: Ember.computed.alias('index.model.sessionTag'),
+  sessionActive: Ember.computed.alias('index.model.sessionActive'),
+
+  canInvalidateSession: Ember.computed.and('sessionTag', 'sessionActive'),
 
   predefinedSettings: constants.hiveParameters,
 
+  selectedSettings: function() {
+    var predefined = this.get('predefinedSettings');
+    var current = this.get('currentSettings.settings');
+
+    return predefined.filter(function(setting) {
+      return current.findBy('key.name', setting.name);
+    });
+  }.property('currentSettings.settings.@each.key'),
+
   currentSettings: function () {
     var currentId = this.get('index.model.id');
     var targetSettings = this.findBy('id', currentId);
@@ -150,9 +163,14 @@ export default Ember.ArrayController.extend({
         return;
       }
 
+      if (!predefined.validate) {
+        setting.set('valid', true);
+        return;
+      }
+
       setting.set('valid', false);
     });
-  }.observes('currentSettings.[]', 'currentSettings.settings.@each.value', 'currentSettings.settings.@each.key'),
+  }.observes('currentSettings.[]', 'currentSettings.settings.[]', 'currentSettings.settings.@each.value', 'currentSettings.settings.@each.key'),
 
   currentSettingsAreValid: function() {
     var currentSettings = this.get('currentSettings.settings');
@@ -161,6 +179,24 @@ export default Ember.ArrayController.extend({
     return invalid.length ? false : true;
   }.property('currentSettings.settings.@each.value', 'currentSettings.settings.@each.key'),
 
+  loadSessionStatus: function() {
+    var model         = this.get('index.model');
+    var sessionActive = this.get('sessionActive');
+    var sessionTag    = this.get('sessionTag');
+    var adapter       = this.container.lookup('adapter:application');
+    var url           = adapter.buildURL() + '/jobs/sessions/' + sessionTag;
+
+    if (sessionTag && sessionActive === undefined) {
+      adapter.ajax(url, 'GET')
+        .then(function(response) {
+          model.set('sessionActive', response.session.actual);
+        })
+        .catch(function() {
+          model.set('sessionActive', false);
+        });
+    }
+  }.observes('index.model', 'index.model.status'),
+
   actions: {
     add: function () {
       var currentId = this.get('index.model.id'),
@@ -185,6 +221,31 @@ export default Ember.ArrayController.extend({
       });
 
       this.get('currentSettings.settings').findBy('key', null).set('key', newKey);
+    },
+
+    removeAll: function() {
+      var currentId = this.get('index.model.id'),
+          querySettings = this.findBy('id', currentId);
+
+      querySettings.set('settings', []);
+    },
+
+    invalidateSession: function() {
+      var self       = this;
+      var sessionTag = this.get('sessionTag');
+      var adapter    = this.container.lookup('adapter:application');
+      var url        = adapter.buildURL() + '/jobs/sessions/' + sessionTag;
+      var model = this.get('index.model');
+
+      // @TODO: Split this into then/catch once the BE is fixed
+      adapter.ajax(url, 'DELETE').catch(function(response) {
+        if ([200, 404].contains(response.status)) {
+          model.set('sessionActive', false);
+          self.notify.success('alerts.success.sessions.deleted');
+        } else {
+          self.notify.error(response.responseJSON.message, response.responseJSON.trace);
+        }
+      });
     }
   }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/udf.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/udf.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/udf.js
index 657743c..3f8d3ed 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/udf.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/udf.js
@@ -50,8 +50,8 @@ export default Ember.ObjectController.extend({
           this.send('openModal',
                     'modal-delete',
                      {
-                        heading: Ember.I18n.translations.modals.delete.heading,
-                        text: Ember.I18n.translations.modals.delete.message,
+                        heading: 'modals.delete.heading',
+                        text: 'modals.delete.message',
                         defer: defer
                      });
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/all-uppercase.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/all-uppercase.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/all-uppercase.js
index e5ea321..f068ed0 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/all-uppercase.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/all-uppercase.js
@@ -19,7 +19,7 @@
 import Ember from 'ember';
 
 export function allUppercase(input) {
-  return input.toUpperCase();
+  return input ? input.toUpperCase() : input;
 };
 
 export default Ember.Handlebars.makeBoundHelper(allUppercase);

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/preformatted-string.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/preformatted-string.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/preformatted-string.js
new file mode 100644
index 0000000..bb3d006
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/preformatted-string.js
@@ -0,0 +1,28 @@
+/**
+* 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 function preformattedString(string) {
+  string = string.replace(/\\n/g, '&#10;'); // newline
+  string = string.replace(/\\t/g, '&#09;'); // tabs
+  string = string.replace(/^\s+|\s+$/g, ''); // trim
+
+  return new Ember.Handlebars.SafeString(string);
+}
+
+export default Ember.Handlebars.makeBoundHelper(preformattedString);

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/index.html
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/index.html b/contrib/views/hive/src/main/resources/ui/hive-web/app/index.html
index 4f609bd..2cbf9f0 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/index.html
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/index.html
@@ -28,11 +28,15 @@
 
     <link rel="stylesheet" href="assets/vendor.css">
     <link rel="stylesheet" href="assets/hive.css">
+
+    {{content-for 'head-footer'}}
   </head>
   <body>
     {{content-for 'body'}}
 
     <script src="assets/vendor.js"></script>
     <script src="assets/hive.js"></script>
+
+    {{content-for 'body-footer'}}
   </body>
 </html>

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/i18n.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/i18n.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/i18n.js
index 8d60248..b637c5e 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/i18n.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/i18n.js
@@ -28,9 +28,13 @@ export default {
     Ember.I18n.translations = TRANSLATIONS;
     Ember.TextField.reopen(Ember.I18n.TranslateableAttributes);
   }
-};
+}
 
 TRANSLATIONS = {
+  tooltips: {
+    refresh: 'Refresh database',
+    loadSample: 'Load sample data'
+  },
   alerts: {
     errors: {
       save: {
@@ -40,6 +44,17 @@ TRANSLATIONS = {
       get: {
         tables: 'Error when trying to retrieve the tables for the selected database',
         columns: 'Error when trying to retrieve the table columns.'
+      },
+      sessions: {
+        delete: 'Error invalidating sessions'
+      },
+      job: {
+        status: "An error occured while processing the job."
+      }
+    },
+    success: {
+      sessions: {
+        deleted: 'Session invalidated'
       }
     }
   },
@@ -53,7 +68,12 @@ TRANSLATIONS = {
     save: {
       heading: 'Saving item',
       saveBeforeCloseHeading: "Save item before closing?",
-      message: 'Enter name:'
+      message: 'Enter name:',
+      overwrite: 'Saving will overwrite previously saved query'
+    },
+
+    download: {
+      csv: 'Download results as CSV'
     }
   },
   titles: {
@@ -62,13 +82,15 @@ TRANSLATIONS = {
     results: 'Search Results',
     settings: 'Database Settings',
     query: {
+      tab: 'Worksheet',
       editor: 'Query Editor',
       process: 'Query Process Results',
       parameters: 'Parameters',
       visualExplain: 'Visual Explain',
       tez: 'TEZ'
     },
-    download: 'Save results...'
+    download: 'Save results...',
+    tableSample: '{{tableName}} sample'
   },
   placeholders: {
     search: {
@@ -130,7 +152,7 @@ TRANSLATIONS = {
     explain: 'Explain',
     saveAs: 'Save as...',
     save: 'Save',
-    newQuery: 'New Query',
+    newQuery: 'New Worksheet',
     newUdf: 'New UDF',
     history: 'History',
     ok: 'OK',
@@ -147,9 +169,13 @@ TRANSLATIONS = {
     runOnTez: 'Run on Tez'
   },
   labels: {
-    noTablesMatches: 'No tables matches for'
+    noTablesMatch: 'No tables match',
+    table: 'Table '
   },
   popover: {
+    visualExplain: {
+      statistics: "Statistics"
+    },
     queryEditorHelp: {
       title: "Did you know?",
       content: {
@@ -163,7 +189,10 @@ TRANSLATIONS = {
   tez: {
     errors: {
       'not.deployed': "Tez View isn't deployed.",
-      'no.instance': "No instance of Tez View found."
+      'no.instance': "No instance of Tez View found.",
+      'no.dag': "No DAG available"
     }
-  }
+  },
+
+  generalError: 'Unexpected error'
 };

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/notify.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/notify.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/notify.js
new file mode 100644
index 0000000..be9c359
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/notify.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.
+*/
+export default {
+  name: 'notify',
+  initialize: function(container, app) {
+    app.inject('route', 'notify', 'service:notify');
+    app.inject('controller', 'notify', 'service:notify');
+    app.inject('component', 'notify', 'service:notify');
+    app.inject('views', 'notify', 'service:notify');
+  }
+};

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/models/file.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/models/file.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/models/file.js
index f8f1b9b..c13d4e1 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/models/file.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/models/file.js
@@ -21,6 +21,6 @@ import DS from 'ember-data';
 export default DS.Model.extend({
   fileContent: DS.attr(),
   hasNext: DS.attr(),
-  page: DS.attr,
+  page: DS.attr('number'),
   pageCount: DS.attr()
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/models/job.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/models/job.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/models/job.js
index 06bdc2e..472f824 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/models/job.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/models/job.js
@@ -26,12 +26,17 @@ export default DS.Model.extend({
   dataBase: DS.attr('string'),
   duration: DS.attr(),
   status: DS.attr('string'),
+  statusMessage: DS.attr('string'),
   dateSubmitted: DS.attr('date'),
   forcedContent: DS.attr('string'),
   logFile: DS.attr('string'),
   dagName:  DS.attr('string'),
   dagId: DS.attr('string'),
   sessionTag: DS.attr('string'),
+  page: DS.attr(),
+  statusDir: DS.attr('string'),
+  applicationId: DS.attr(),
+  confFile: DS.attr('string'),
 
   dateSubmittedTimestamp: function () {
     var date = this.get('dateSubmitted');

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/application.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/application.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/application.js
index 2f9a5ae..bf413a3 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/application.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/application.js
@@ -33,6 +33,8 @@ export default Ember.Route.extend({
   actions: {
     openModal: function (modalTemplate, options) {
       this.controllerFor(modalTemplate).setProperties({
+        content: options.content || {},
+        message: options.message,
         heading: options.heading,
         text: options.text,
         defer: options.defer
@@ -51,11 +53,16 @@ export default Ember.Route.extend({
       });
     },
 
-    addAlert: function (type, message, title) {
-      this.controllerFor(constants.namingConventions.alerts).pushObject({
-        type: type,
-        title: title,
-        content: message
+    openOverlay: function(overlay) {
+      return this.render(overlay.template, {
+        outlet: overlay.outlet,
+        into: overlay.into
+      });
+    },
+    closeOverlay: function(overlay) {
+      return this.disconnectOutlet({
+        outlet: overlay.outlet,
+        parentView: overlay.into
       });
     }
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/index/index.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/index/index.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/index/index.js
index f2be946..120a102 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/index/index.js
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/routes/index/index.js
@@ -23,14 +23,14 @@ export default Ember.Route.extend({
   beforeModel: function () {
     var model = this.controllerFor(constants.namingConventions.routes.index).get('model');
 
-    if (model) {
+    if (model && !model.get('isDeleted')) {
       if (model.get('constructor.typeKey') === constants.namingConventions.job) {
         this.transitionTo(constants.namingConventions.subroutes.historyQuery, model);
       } else {
         this.transitionTo(constants.namingConventions.subroutes.savedQuery, model);
       }
     } else {
-      this.controllerFor(constants.namingConventions.routes.index).send('addQuery');
+      this.controllerFor(constants.namingConventions.openQueries).navigateToLastTab();
     }
   }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/services/notify.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/services/notify.js b/contrib/views/hive/src/main/resources/ui/hive-web/app/services/notify.js
new file mode 100644
index 0000000..fbd50cd
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/services/notify.js
@@ -0,0 +1,88 @@
+/**
+* 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 'hive/utils/constants';
+
+export default Ember.Service.extend({
+  types: constants.notify,
+
+  messages      : Ember.ArrayProxy.create({ content : [] }),
+  notifications : Ember.ArrayProxy.create({ content : [] }),
+
+  add: function(type, message, body) {
+    var formattedBody = this.formatMessageBody(body);
+
+    var notification = Ember.Object.create({
+      type    : type,
+      message : message,
+      body    : formattedBody
+    });
+
+    this.messages.pushObject(notification);
+    this.notifications.pushObject(notification);
+  },
+
+  info: function(message, body) {
+    this.add(this.types.INFO, message, body);
+  },
+
+  warn: function(message, body) {
+    this.add(this.types.WARN, message, body);
+  },
+
+  error: function(message, body) {
+    this.add(this.types.ERROR, message, body);
+  },
+
+  success: function(message, body) {
+    this.add(this.types.SUCCESS, message, body);
+  },
+
+  formatMessageBody: function(body) {
+    if (!body) {
+      return;
+    }
+
+    if (typeof body === "string") {
+      return body;
+    }
+
+    if (typeof body === "object") {
+      var formattedBody = "";
+      for (var key in body) {
+        formattedBody += "\n\n%@:\n%@".fmt(key, body[key]);
+      }
+
+      return formattedBody;
+    }
+  },
+
+  removeMessage: function(message) {
+    this.messages.removeObject(message);
+    this.notifications.removeObject(message);
+  },
+
+  removeNotification: function(notification) {
+    this.notifications.removeObject(notification);
+  },
+
+  removeAllMessages: function() {
+    this.messages.removeAt(0, this.messages.get('length'));
+  }
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/app.scss
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/app.scss b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/app.scss
index 3f49713..7fdf096 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/app.scss
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/app.scss
@@ -16,11 +16,15 @@
  * limitations under the License.
  */
 
+@import 'vars';
 @import 'dropdown-submenu';
+@import 'mixins';
+@import 'notifications';
+@import 'query-tabs';
 
-$panel-background: #f5f5f5;
-$placeholder-color: #aaa;
-$border-color: #ddd;
+a {
+  word-wrap: break-word;
+}
 
 @-webkit-keyframes fadeIn {
   0% {opacity: 0;}
@@ -60,6 +64,10 @@ $border-color: #ddd;
   display: flex;
 }
 
+#visual-explain {
+  white-space: nowrap;
+}
+
 #visual-explain, #tez-ui {
   position: absolute;
   left: 0;
@@ -68,13 +76,6 @@ $border-color: #ddd;
   background: white;
 }
 
-#alerts-container {
-  position: absolute;
-  left: 15px;
-  right: 15px;
-  z-index: 1100;
-}
-
 aside  hr {
   margin: 10px 0;
 }
@@ -181,21 +182,10 @@ dropdown .fa-remove {
 }
 
 .main-content {
+  width: 90%;
   flex-grow: 1;
 }
 
-.query-menu {
-  margin-top: 57px;
-
-  span, popover {
-    cursor: pointer;
-    overflow: hidden;
-    display: block;
-    border-bottom: 1px solid $border-color;
-    padding: 10px;
-  }
-}
-
 .queries-icon {
   font-size: 20px;
 
@@ -209,11 +199,23 @@ dropdown .fa-remove {
   }
 }
 
+.query-context-tab {
+  background: #f1f1f1;
+
+  &.active {
+    background: white;
+  }
+}
+
 .alert {
   margin-bottom: 5px;
   padding-bottom: 10px;
   padding-top: 10px;
 
+  strong {
+    text-decoration: underline;
+  }
+
   .alert-message {
     max-height: 250px;
     overflow-y: auto;
@@ -317,22 +319,8 @@ body {
 }
 
 .settings-container {
-  width: 100%;
-  overflow-y: scroll;
-  height: calc(100% - 41px);
-  top: 41px;
-  left: 0;
-  background-color: #fff;
-  position: absolute;
-  padding: 0 15px;
-  z-index: 1000;
-
-  border: 1px solid $border-color;
-  -webkit-animation-duration: .5s;
-          animation-duration: .5s;
-  -webkit-animation-fill-mode: both;
-          animation-fill-mode: both;
 }
+
 .settings-container .close-settings {
   float: right;
   font-size: 18px;
@@ -377,3 +365,77 @@ tree-view ul li {
   height: 822px;
   border: none;
 }
+
+.edge {
+  text-align: center;
+  font-size: 10px;
+  font-weight: 800;
+
+  .edge-path {
+    height: 2px;
+    background-color: #dedede;
+    position: absolute;
+  }
+
+  .edge-arrow {
+    position: absolute;
+    width: 0;
+    height: 0;
+    border-top: 5px solid transparent;
+    border-bottom: 5px solid transparent;
+    border-right: 10px solid black;
+  }
+}
+
+.nodes {
+  width: 100%;
+  position: relative;
+
+  .node-container {
+    text-align: center;
+
+    .node {
+      border: 1px solid #bbb;
+      background: #fefefe;
+      font-size: 12px;
+      box-sizing: border-box;
+      text-align: left;
+      max-width: 200px;
+      margin: 0 25px 100px 0;
+      display: inline-block;
+      vertical-align: top;
+
+      @include box-shadow(1px, 1px, 15px, #888888);
+
+      &.table-node, &.output-node {
+        background-color: ghostwhite;
+        color: gray;
+        padding: 5px;
+        text-align: center;
+        min-width: 100px;
+        line-height: 8px;
+        vertical-align: bottom;
+        margin-bottom: 50px;
+      }
+
+      .node-heading {
+        padding: 5px;
+        text-align: center;
+        background-color: lightslategrey;
+        color: white;
+      }
+
+      .node-content {
+        max-height: 300px;
+        white-space: normal;
+        padding: 5px;
+        overflow-y: auto;
+        overflow-x: hidden;
+
+        .fa {
+          color: green;
+        }
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/mixins.scss
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/mixins.scss b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/mixins.scss
new file mode 100644
index 0000000..95e4ae8
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/mixins.scss
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+
+@mixin box-shadow($horizontal, $vertical, $blur, $color) {
+  -webkit-box-shadow: $horizontal $vertical $blur $color;
+     -moz-box-shadow: $horizontal $vertical $blur $color;
+          box-shadow: $horizontal $vertical $blur $color;
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/notifications.scss
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/notifications.scss b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/notifications.scss
new file mode 100644
index 0000000..c676e4e
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/notifications.scss
@@ -0,0 +1,36 @@
+/**
+* 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.
+*/
+.notifications-container {
+  position: absolute;
+  top: 4px;
+  right: 20px;
+  width: 600px;
+  z-index: 9999;
+}
+
+.notification > .fa {
+  width: 15px;
+  text-align: center;
+  margin-right: 10px;
+}
+
+.notifications-container .notification {
+  word-wrap: break-word;
+  max-height: 200px;
+  overflow-x: auto;
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/query-tabs.scss
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/query-tabs.scss b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/query-tabs.scss
new file mode 100644
index 0000000..d23a751
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/query-tabs.scss
@@ -0,0 +1,68 @@
+/**
+* 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.
+*/
+.query-menu {
+  margin-top: 57px;
+
+  span, popover {
+    cursor: pointer;
+    display: block;
+    border-bottom: 1px solid $border-color;
+    padding: 10px;
+  }
+}
+
+.fa.panel-action-icon {
+  line-height: 22px;
+  font-size: 16px;
+}
+
+.editor-overlay {
+  width: 100%;
+  overflow-y: scroll;
+  height: calc(100% - 41px);
+  top: 41px;
+  left: 0;
+  background-color: #fff;
+  position: absolute;
+  padding: 0 15px;
+  z-index: 1000;
+
+  border: 1px solid $border-color;
+  -webkit-animation-duration: .5s;
+          animation-duration: .5s;
+  -webkit-animation-fill-mode: both;
+          animation-fill-mode: both;
+}
+
+.message-body {
+  margin-top: 10px;
+}
+
+.query-menu-tab {
+  position: relative;
+}
+.query-menu-tab .badge {
+  position: absolute;
+  top: -4px;
+  left: -4px;
+  background-color: red;
+  color: #fff;
+  padding: 2px 4px;
+  font-weight: bold;
+  z-index: 9999;
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/vars.scss
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/vars.scss b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/vars.scss
new file mode 100644
index 0000000..eec2277
--- /dev/null
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/styles/vars.scss
@@ -0,0 +1,20 @@
+/**
+* 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.
+*/
+$panel-background: #f5f5f5;
+$placeholder-color: #aaa;
+$border-color: #ddd;

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/alerts.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/alerts.hbs b/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/alerts.hbs
deleted file mode 100644
index 0fbf34a..0000000
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/alerts.hbs
+++ /dev/null
@@ -1,23 +0,0 @@
-{{!
-* 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.
-}}
-
-<div id="alerts-container">
-  {{#each alert in this}}
-    {{alert-message-widget message=alert removeMessage="remove" removeLater="removeLater"}}
-  {{/each}}
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/672eee34/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/application.hbs
----------------------------------------------------------------------
diff --git a/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/application.hbs b/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/application.hbs
index 055a87b..99662dd 100644
--- a/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/application.hbs
+++ b/contrib/views/hive/src/main/resources/ui/hive-web/app/templates/application.hbs
@@ -16,10 +16,11 @@
 * limitations under the License.
 }}
 
+{{notify-widget}}
 {{render 'navbar'}}
 
 <div id="content">
   {{outlet}}
 
   {{outlet "modal"}}
-</div>
\ No newline at end of file
+</div>