You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jl...@apache.org on 2016/06/27 23:36:31 UTC

[05/34] ambari git commit: AMBARI-17355 & AMBARI-17354: POC: FE & BE changes for first class support for Yarn hosted services

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/databases-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/databases-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/databases-test.js
new file mode 100644
index 0000000..c3ac272
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/databases-test.js
@@ -0,0 +1,276 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+var controller;
+var store;
+
+moduleFor('controller:databases', 'DatabasesController', {
+  needs: [ 'adapter:database',
+           'service:database',
+           'service:notify',
+           'model:database' ],
+
+  setup: function () {
+    //mock getDatabases which is called on controller init
+    this.container.lookup('service:database').getDatabases = function () {
+      var defer = Ember.RSVP.defer();
+
+      defer.resolve();
+
+      return defer.promise;
+    };
+
+    //mock getDatabasesFromServer which is called by the poller
+    this.container.lookup('service:database').getDatabasesFromServer = function () {
+     var defer = Ember.RSVP.defer();
+
+     var databases = [ "database_a", "database_b"];
+
+     defer.resolve(databases);
+     return defer.promise;
+     };
+
+    store = this.container.lookup('store:main');
+    controller = this.subject();
+    controller.store = store;
+
+  },
+
+  teardown: function () {
+    Ember.run(controller, controller.destroy);
+  }
+});
+
+test('controller is initialized properly.', function () {
+  expect(5);
+
+  var controller = this.subject();
+
+  ok(controller.get('tableSearchResults'), 'table search results collection was initialized.');
+  ok(controller.get('tabs'), 'tabs collection was initialized.');
+  equal(controller.get('tabs.length'), 2, 'tabs collection contains two tabs');
+  equal(controller.get('tabs').objectAt(0).get('name'), Ember.I18n.t('titles.explorer'), 'first tab is database explorer.');
+  equal(controller.get('tabs').objectAt(1).get('name'), Ember.I18n.t('titles.results'), 'second tab is search results');
+});
+
+test('setTablePageAvailability sets canGetNextPage true if given database hasNext flag is true.', function () {
+  expect(1);
+
+  var database = Ember.Object.create( { hasNext: true } );
+
+  controller.setTablePageAvailability(database);
+
+  equal(database.get('canGetNextPage'), true);
+});
+
+test('setTablePageAvailability sets canGetNextPage true if given database has more loaded tables than the visible ones.', function () {
+  expect(1);
+
+  var database = Ember.Object.create({
+    tables: [1],
+    visibleTables: []
+  });
+
+  controller.setTablePageAvailability(database);
+
+  equal(database.get('canGetNextPage'), true);
+});
+
+test('setTablePageAvailability sets canGetNextPage falsy if given database hasNext flag is falsy and all loaded tables are visible.', function () {
+  expect(1);
+
+  var database = Ember.Object.create({
+    tables: [1],
+    visibleTables: [1]
+  });
+
+  controller.setTablePageAvailability(database);
+
+  ok(!database.get('canGetNextPage'));
+});
+
+test('setColumnPageAvailability sets canGetNextPage true if given table hasNext flag is true.', function () {
+  expect(1);
+
+  var table = Ember.Object.create( { hasNext: true } );
+
+  controller.setColumnPageAvailability(table);
+
+  equal(table.get('canGetNextPage'), true);
+});
+
+test('setColumnPageAvailability sets canGetNextPage true if given table has more loaded columns than the visible ones.', function () {
+  expect(1);
+
+  var table = Ember.Object.create({
+    columns: [1],
+    visibleColumns: []
+  });
+
+  controller.setColumnPageAvailability(table);
+
+  equal(table.get('canGetNextPage'), true);
+});
+
+test('setColumnPageAvailability sets canGetNextPage true if given database hasNext flag is falsy and all loaded columns are visible.', function () {
+  expect(1);
+
+  var table = Ember.Object.create({
+    columns: [1],
+    visibleColumns: [1]
+  });
+
+  controller.setColumnPageAvailability(table);
+
+  ok(!table.get('canGetNextPage'));
+});
+
+test('getTables sets the visibleTables as the first page of tables if they are already loaded', function () {
+  expect(2);
+
+  var database = Ember.Object.create({
+    name: 'test_db',
+    tables: [1, 2, 3]
+  });
+
+  controller.get('databases').pushObject(database);
+  controller.set('pageCount', 2);
+
+  controller.send('getTables', 'test_db');
+
+  equal(database.get('visibleTables.length'), controller.get('pageCount'), 'there are 2 visible tables out of 3.');
+  equal(database.get('canGetNextPage'), true, 'user can get next tables page.');
+});
+
+test('getColumns sets the visibleColumns as the first page of columns if they are already loaded.', function () {
+  expect(2);
+
+  var table = Ember.Object.create({
+    name: 'test_table',
+    columns: [1, 2, 3]
+  });
+
+  var database = Ember.Object.create({
+    name: 'test_db',
+    tables: [ table ],
+    visibleTables: [ table ]
+  });
+
+  controller.set('pageCount', 2);
+
+  controller.send('getColumns', 'test_table', database);
+
+  equal(table.get('visibleColumns.length'), controller.get('pageCount'), 'there are 2 visible columns out of 3.');
+  equal(table.get('canGetNextPage'), true, 'user can get next columns page.');
+});
+
+test('showMoreTables pushes more tables to visibleTables if there are still hidden tables loaded.', function () {
+  expect(2);
+
+  var database = Ember.Object.create({
+    name: 'test_db',
+    tables: [1, 2, 3],
+    visibleTables: [1]
+  });
+
+  controller.get('databases').pushObject(database);
+  controller.set('pageCount', 1);
+
+  controller.send('showMoreTables', database);
+
+  equal(database.get('visibleTables.length'), controller.get('pageCount') * 2, 'there are 2 visible tables out of 3.');
+  equal(database.get('canGetNextPage'), true, 'user can get next tables page.');
+});
+
+test('showMoreColumns pushes more columns to visibleColumns if there are still hidden columns loaded.', function () {
+  expect(2);
+
+  var table = Ember.Object.create({
+    name: 'test_table',
+    columns: [1, 2, 3],
+    visibleColumns: [1]
+  });
+
+  var database = Ember.Object.create({
+    name: 'test_db',
+    tables: [ table ],
+    visibleTables: [ table ]
+  });
+
+  controller.set('pageCount', 1);
+
+  controller.send('showMoreColumns', table, database);
+
+  equal(table.get('visibleColumns.length'), controller.get('pageCount') * 2, 'there are 2 visible columns out of 3.');
+  equal(table.get('canGetNextPage'), true, 'user can get next columns page.');
+});
+
+test('syncDatabases pushed more databases when new databases are added in the backend', function() {
+  expect(3);
+
+  var databaseA = {
+    id: "database_a",
+    name: "database_a"
+  };
+
+  Ember.run(function() {
+    store.createRecord('database', databaseA);
+    controller.syncDatabases();
+  });
+
+  var latestDbNames = store.all('database').mapBy('name');
+  equal(latestDbNames.length, 2, "There is 1 additional database added to hive");
+  equal(latestDbNames.contains("database_a"), true, "New database list should contain the old database name.");
+  equal(latestDbNames.contains("database_b"), true, "New database list should contain the new database name.");
+});
+
+test('syncDatabases removed database when old databases are removed in the backend', function() {
+  expect(4);
+
+  var latestDbNames;
+
+  var databaseA = {
+    id: "database_a",
+    name: "database_a"
+  };
+  var databaseB = {
+    id: "database_b",
+    name: "database_b"
+  };
+  var databaseC = {
+    id: "database_c",
+    name: "database_c"
+  };
+
+  Ember.run(function() {
+    store.createRecord('database', databaseA);
+    store.createRecord('database', databaseB);
+    store.createRecord('database', databaseC);
+    controller.syncDatabases();
+  });
+
+  latestDbNames = store.all('database').mapBy('name');
+  equal(latestDbNames.length, 2, "One database is removed from hive");
+  equal(latestDbNames.contains("database_a"), true, "New database list should contain the old database name.");
+  equal(latestDbNames.contains("database_b"), true, "New database list should contain the old database name.");
+  equal(latestDbNames.contains("database_c"), false, "New database list should not contain the database name removed in the backend.");
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/history-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/history-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/history-test.js
new file mode 100644
index 0000000..ab45214
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/history-test.js
@@ -0,0 +1,117 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:history', 'HistoryController', {
+  needs: [ 'service:file', 'service:job' ]
+});
+
+test('controller is initialized correctly', function () {
+  expect(1);
+
+  var component = this.subject();
+
+  equal(component.get('columns.length'), 4, 'Columns are initialized');
+});
+
+test('date range is set correctly', function () {
+  expect(2);
+
+  var component = this.subject();
+  var min = parseInt(Date.now() / 1000) - (60 * 60 * 24 * 60);
+  var max = parseInt(Date.now() / 1000);
+
+  var history = Ember.ArrayProxy.create({ content: [
+    Ember.Object.create({
+      dateSubmittedTimestamp: min
+    }),
+    Ember.Object.create({
+      dateSubmittedTimestamp: max
+    })
+  ]});
+
+  Ember.run(function() {
+    component.set('history', history);
+  });
+
+  var dateColumn = component.get('columns').find(function (column) {
+    return column.get('caption') === 'columns.date';
+  });
+
+  equal(dateColumn.get('dateRange.min'), min, 'Min date is set correctly');
+  equal(dateColumn.get('dateRange.max'), max, 'Max date is set correctly');
+});
+
+test('interval duration is set correctly', function () {
+  expect(2);
+
+  var component = this.subject();
+
+  var history = Ember.ArrayProxy.create({ content: [
+    Ember.Object.create({
+      duration: 20
+    }),
+    Ember.Object.create({
+      duration: 300
+    })
+  ]});
+
+  Ember.run(function() {
+    component.set('history', history);
+  });
+
+  var durationColumn = component.get('columns').find(function (column) {
+    return column.get('caption') === 'columns.duration';
+  });
+
+  equal(durationColumn.get('numberRange.min'), 20, 'Min value is set correctly');
+  equal(durationColumn.get('numberRange.max'), 300, 'Max value is set correctly');
+});
+
+test('history filtering', function() {
+  expect(2);
+
+  var component = this.subject();
+
+  var history = Ember.ArrayProxy.create({
+    content: [
+      Ember.Object.create({
+        name: 'HISTORY',
+        status: 1
+      }),
+      Ember.Object.create({
+        name: '1HISTORY',
+        status: 2
+      })
+    ]
+  });
+
+  Ember.run(function() {
+    component.set('history', history);
+  });
+
+  equal(component.get('model.length'), 2, 'No filters applied we have 2 models');
+
+  Ember.run(function() {
+    component.filterBy('name', 'HISTORY', true);
+  });
+
+  equal(component.get('model.length'), 1, 'Filter by name we have 1 filtered model');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/index-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/index-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/index-test.js
new file mode 100644
index 0000000..290f61e
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/index-test.js
@@ -0,0 +1,328 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+import constants from 'hive/utils/constants';
+
+moduleFor('controller:index', 'IndexController', {
+  needs: [
+          'controller:open-queries',
+          'controller:udfs',
+          'controller:index/history-query/logs',
+          'controller:index/history-query/results',
+          'controller:index/history-query/explain',
+          'controller:settings',
+          'controller:visual-explain',
+          'controller:tez-ui',
+          'service:job',
+          'service:file',
+          'service:database',
+          'service:notify',
+          'service:job-progress',
+          'service:session',
+          'service:settings',
+          'adapter:application',
+          'adapter:database'
+        ]
+});
+
+test('modelChanged calls update on the open-queries cotnroller.', function () {
+  expect(1);
+
+  var controller = this.subject();
+
+  controller.set('openQueries.update', function () {
+    var defer = Ember.RSVP.defer();
+
+    ok(true, 'index model has changed. update was called on open-queries controller.');
+
+    defer.resolve();
+
+    return defer.promise;
+  });
+
+  Ember.run(function () {
+    controller.set('model', Ember.Object.create());
+  });
+});
+
+test('bindQueryParams replaces param placeholder with values', function() {
+  expect(1);
+
+  var controller = this.subject();
+  var queryParams = [
+    { name: '$what', value: 'color' },
+    { name: '$where', value: 'z'}
+  ];
+
+  var query = "select $what from $where";
+  var replacedQuery = "select color from z";
+
+  Ember.run(function() {
+    controller.get('queryParams').setObjects(queryParams);
+  });
+
+  equal(controller.bindQueryParams(query), replacedQuery, 'Params replaced correctly');
+});
+
+test('bindQueryParams replaces same param multiple times', function() {
+  expect(1);
+
+  var controller = this.subject();
+  var queryParams = [
+    { name: '$what', value: 'color' },
+    { name: '$where', value: 'z'}
+  ];
+
+  var query = "select $what from $where as $what";
+  var replacedQuery = "select color from z as color";
+
+  Ember.run(function() {
+    controller.get('queryParams').setObjects(queryParams);
+  });
+
+  equal(controller.bindQueryParams(query), replacedQuery, 'Params replaced correctly');
+});
+
+test('parseQueryParams sets queryParams when query changes', function() {
+  expect(4);
+
+
+  var query = Ember.Object.create({
+    id: 1,
+    fileContent: "select $what from $where"
+  });
+  var updatedQuery = "select $what from $where and $where";
+
+  var controller = this.subject({
+    model: query
+  });
+
+  Ember.run(function() {
+    controller.set('openQueries.queryTabs', [query]);
+    controller.set('openQueries.currentQuery', query);
+  });
+
+  equal(controller.get('queryParams.length'), 2, '2 queryParams parsed');
+  equal(controller.get('queryParams').objectAt(0).name, '$what', 'First param parsed correctly');
+  equal(controller.get('queryParams').objectAt(1).name, '$where', 'Second param parsed correctly');
+
+  Ember.run(function() {
+    controller.set('openQueries.currentQuery.fileContent', updatedQuery);
+  });
+
+  equal(controller.get('queryParams.length'), 2, 'Can use same param multiple times');
+});
+
+test('canExecute return false if query is executing', function() {
+  expect(2);
+  var controller = this.subject();
+
+  Ember.run(function() {
+    controller.set('openQueries.update', function () {
+      var defer = Ember.RSVP.defer();
+      defer.resolve();
+      return defer.promise;
+    });
+
+    controller.set('model', Ember.Object.create({ 'isRunning': false }));
+    controller.set('queryParams', []);
+  });
+
+  ok(controller.get('canExecute'), 'Query is not executing => canExecute return true');
+
+  Ember.run(function() {
+    controller.set('model', Ember.Object.create({ 'isRunning': true }));
+  });
+
+  ok(!controller.get('canExecute'), 'Query is executing => canExecute return false');
+});
+
+test('canExecute return false if queryParams doesnt\'t have values', function() {
+  expect(2);
+  var controller = this.subject();
+
+  var paramsWithoutValues = [
+    { name: '$what', value: '' },
+    { name: '$where', value: '' }
+  ];
+
+  var paramsWithValues = [
+    { name: '$what', value: 'value1' },
+    { name: '$where', value: 'value2' }
+  ];
+
+  Ember.run(function() {
+    controller.set('openQueries.update', function () {
+      var defer = Ember.RSVP.defer();
+      defer.resolve();
+      return defer.promise;
+    });
+    controller.set('model', Ember.Object.create({ 'isRunning': false }));
+    controller.get('queryParams').setObjects(paramsWithoutValues);
+  });
+
+  ok(!controller.get('canExecute'), 'Params without value => canExecute return false');
+
+  Ember.run(function() {
+    controller.get('queryParams').setObjects(paramsWithValues);
+  });
+
+  ok(controller.get('canExecute'), 'Params with values => canExecute return true');
+});
+
+test('Execute EXPLAIN type query', function() {
+  expect(1);
+
+  var query = Ember.Object.create({
+    id: 1,
+    fileContent: "explain select 1" // explain type query
+  });
+
+  var controller = this.subject({
+    model: query,
+    _executeQuery: function (referer) {
+      equal(referer, constants.jobReferrer.explain, 'Explain type query successful.');
+      return {then: function() {}};
+    }
+  });
+
+  Ember.run(function() {
+      controller.set('openQueries.queryTabs', [query]);
+      controller.set('openQueries.currentQuery', query);
+      controller.send('executeQuery');
+  });
+
+});
+
+test('Execute non EXPLAIN type query', function() {
+  expect(1);
+
+  var query = Ember.Object.create({
+    id: 1,
+    fileContent: "select 1" //non explain type query
+  });
+
+  var controller = this.subject({
+    model: query,
+    _executeQuery: function (referer) {
+      equal(referer, constants.jobReferrer.job , 'non Explain type query successful.');
+      return {then: function() {}};
+    }
+  });
+
+  Ember.run(function() {
+      controller.set('openQueries.queryTabs', [query]);
+      controller.set('openQueries.currentQuery', query);
+      controller.send('executeQuery');
+  });
+
+});
+
+
+test('csvUrl returns if the current query is not a job', function() {
+  expect(1);
+  var content = Ember.Object.create({
+      constructor: {
+        typeKey: 'notJob'
+      }
+  });
+
+  var controller = this.subject({ content: content });
+  ok(!controller.get('csvUrl'), 'returns if current query is not a job');
+});
+
+test('csvUrl returns is status in not SUCCEEDED', function() {
+  expect(1);
+  var content= Ember.Object.create({
+      constructor: {
+        typeKey: 'job'
+      },
+      status: 'notSuccess'
+  });
+
+  var controller = this.subject({ content: content });
+  ok(!controller.get('csvUrl'), 'returns if current status is not success');
+});
+
+test('csvUrl return the download results as csv link', function() {
+  expect(1);
+  var content = Ember.Object.create({
+      constructor: {
+        typeKey: 'job'
+      },
+      status: 'SUCCEEDED',
+      id: 1
+  });
+
+  var controller = this.subject({ content: content });
+  ok(controller.get('csvUrl'));
+});
+
+test('donwloadMenu returns null if status is not succes and results are not visible ', function() {
+  expect(1);
+  var content = Ember.Object.create({
+      status: 'notSuccess',
+      queryProcessTabs: [{
+        path: 'index.historyQuery.results',
+        visible: false
+      }]
+  });
+
+  var controller = this.subject({ content: content });
+  ok(!controller.get('downloadMenu'), 'Returns null');
+});
+
+test('donwloadMenu returns only saveToHDFS if csvUrl is false', function() {
+  expect(1);
+  var content = Ember.Object.create({
+      constructor: {
+        typeKey: 'notjob'
+      },
+      status: 'SUCCEEDED',
+  });
+
+  var controller = this.subject({ content: content });
+  Ember.run(function() {
+    var tabs = controller.get('queryProcessTabs');
+    var results = tabs.findBy('path', 'index.historyQuery.results');
+    results.set('visible', true);
+  });
+
+  equal(controller.get('downloadMenu.length'), 1, 'Returns only saveToHDFS');
+});
+
+test('donwloadMenu returns saveToHDFS and csvUrl', function() {
+  expect(1);
+  var content = Ember.Object.create({
+      constructor: {
+        typeKey: 'job'
+      },
+      status: 'SUCCEEDED',
+  });
+
+  var controller = this.subject({ content: content });
+  Ember.run(function() {
+    var tabs = controller.get('queryProcessTabs');
+    var results = tabs.findBy('path', 'index.historyQuery.results');
+    results.set('visible', true);
+  });
+
+  equal(controller.get('downloadMenu.length'), 2, 'Returns saveToHDFS and csvUrl');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/insert-udfs-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/insert-udfs-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/insert-udfs-test.js
new file mode 100644
index 0000000..e770bdd
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/insert-udfs-test.js
@@ -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.
+ */
+
+import Ember from 'ember';
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:insert-udfs', 'InsertUdfsController', {
+  needs: 'controller:udfs'
+});
+
+test('controller is initialized correctly', function () {
+  expect(1);
+
+  var udfs = Ember.A([
+    Ember.Object.create({ fileResource: { id: 1 } }),
+    Ember.Object.create({ fileResource: { id: 1 } }),
+    Ember.Object.create({ fileResource: { id: 2 } }),
+    Ember.Object.create({ fileResource: { id: 2 } })
+  ]);
+
+  var component = this.subject();
+
+  Ember.run(function() {
+    component.set('udfs', udfs);
+  });
+
+  equal(component.get('length'), 2, 'should contain unique file resources');
+});
+
+test('controller updates on new udfs', function () {
+  expect(2);
+
+  var udfs = Ember.A([
+    Ember.Object.create({ fileResource: { id: 1 } }),
+    Ember.Object.create({ fileResource: { id: 2 } }),
+  ]);
+
+  var component = this.subject();
+
+  Ember.run(function() {
+    component.set('udfs', udfs);
+  });
+
+  equal(component.get('length'), 2, '');
+
+  var newUdf = Ember.Object.create({ isNew: true, fileResource: { id: 3 } });
+
+  Ember.run(function() {
+    component.get('udfs').pushObject(newUdf);
+  });
+
+  equal(component.get('length'), 3, '');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/messages-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/messages-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/messages-test.js
new file mode 100644
index 0000000..b0cdf16
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/messages-test.js
@@ -0,0 +1,53 @@
+/**
+ * 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';
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:messages', 'MessagesController', {
+});
+
+test('Controller is initialized', function() {
+  var controller = this.subject();
+
+  ok(controller, 'Controller is initialized');
+});
+
+test('Controller action', function() {
+  var controller = this.subject({
+    notifyService: Ember.Object.create({
+      removeMessage: function(message) {
+        ok(1, 'removeMessage action called');
+      },
+      removeAllMessages: function() {
+        ok(1, 'removeAllMessages action called');
+      },
+      markMessagesAsSeen: function(message) {
+        ok(1, 'markMessagesAsSeen action called');
+      }
+    })
+  });
+
+  Ember.run(function() {
+    controller.send('removeMessage');
+    controller.send('removeAllMessages');
+    controller.send('markMessagesAsSeen');
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/open-queries-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/open-queries-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/open-queries-test.js
new file mode 100644
index 0000000..c46134d
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/open-queries-test.js
@@ -0,0 +1,102 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:open-queries', 'OpenQueriesController', {
+  needs: [ 'controller:index/history-query/results',
+           'controller:index/history-query/explain',
+           'controller:index',
+           'controller:settings',
+           'service:file',
+           'service:database'
+         ]
+});
+
+test('when initialized, controller sets the queryTabs.', function () {
+  expect(1);
+
+  var controller = this.subject();
+
+  ok(controller.get('queryTabs', 'queryTabs is initialized.'));
+});
+
+test('pushObject override creates a new queryFile mock and adds it to the collection if none provided.', function () {
+  expect(3);
+
+  var controller = this.subject();
+
+  var model = Ember.Object.create({
+    id: 5
+  });
+
+  controller.pushObject(null, model);
+
+  equal(controller.get('length'), 1, 'a new object was added to the open queries collection.');
+  equal(controller.objectAt(0).id, model.get('id'), 'the object id was set to the model id.');
+  equal(controller.objectAt(0).get('fileContent'), '', 'the object fileContent is initialized with empty string.');
+});
+
+test('getTabForModel retrieves the tab that has the id and the type equal to the ones of the given model.', function () {
+  expect(1);
+
+  var controller = this.subject();
+
+  var model = Ember.Object.create({
+    id: 1
+  });
+
+  controller.get('queryTabs').pushObject(Ember.Object.create({
+    id: model.get('id')
+  }));
+
+  equal(controller.getTabForModel(model), controller.get('queryTabs').objectAt(0), 'retrieves correct tab for the given model.');
+});
+
+test('getQueryForModel retrieves the query by id equality if a new record is given', function () {
+  expect(1);
+
+  var controller = this.subject();
+
+  var model = Ember.Object.create({
+    id: 1,
+    isNew: true
+  });
+
+  controller.pushObject(null, model);
+
+  equal(controller.getQueryForModel(model).get('id'), model.get('id'), 'a new record was given, the method retrieves the query by id equality');
+});
+
+test('getQueryForModel retrieves the query by record id equality with model queryFile path if a saved record is given', function () {
+  expect(1);
+
+  var controller = this.subject();
+
+  var model = Ember.Object.create({
+    id: 1,
+    queryFile: 'some/path'
+  });
+
+  controller.pushObject(Ember.Object.create({
+    id: model.get('queryFile')
+  }));
+
+  equal(controller.getQueryForModel(model).get('id'), model.get('queryFile'), 'a saved record was given, the method retrieves the query by id equality with record queryFile path.');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/queries-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/queries-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/queries-test.js
new file mode 100644
index 0000000..2578c33
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/queries-test.js
@@ -0,0 +1,35 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:queries', 'QueriesController', {
+  needs: [
+    'controller:history',
+    'controller:open-queries'
+  ]
+});
+
+test('controller is initialized', function() {
+  expect(1);
+
+  var component = this.subject();
+
+  equal(component.get('columns.length'), 4, 'Columns are initialized correctly');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/settings-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/settings-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/settings-test.js
new file mode 100644
index 0000000..366d18c
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/settings-test.js
@@ -0,0 +1,136 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:settings', 'SettingsController', {
+  needs: [
+    'controller:databases',
+    'controller:index',
+    'controller:open-queries',
+    'controller:index/history-query/results',
+    'controller:index/history-query/explain',
+    'controller:udfs',
+    'controller:index/history-query/logs',
+    'controller:visual-explain',
+    'controller:tez-ui',
+    'adapter:database',
+    'adapter:application',
+    'service:settings',
+    'service:notify',
+    'service:database',
+    'service:file',
+    'service:session',
+    'service:job',
+    'service:job-progress'
+  ]
+});
+
+test('can add a setting', function() {
+  var controller = this.subject();
+
+  ok(!controller.get('settings.length'), 'No initial settings');
+
+  Ember.run(function() {
+    controller.send('add');
+  });
+
+  equal(controller.get('settings.length'), 1, 'Can add settings');
+});
+
+test('validate', function() {
+  var predefinedSettings = [
+    {
+      name: 'some.key',
+      validate: new RegExp(/^\d+$/) // digits
+    }
+  ];
+
+  var controller = this.subject({
+    predefinedSettings: predefinedSettings
+  });
+
+  controller.set('openQueries.update', function () {
+    var defer = Ember.RSVP.defer();
+    defer.resolve();
+
+    return defer.promise;
+  });
+
+  var settings = [
+    Ember.Object.create({key: { name: 'some.key' }, value: 'value'}),
+    Ember.Object.create({key: { name: 'some.key' }, value: '123'})
+  ];
+
+  Ember.run(function() {
+    controller.set('settings', settings);
+  });
+
+  var currentSettings = controller.get('settings');
+  ok(!currentSettings.get('firstObject.valid'), "First setting doesn\' pass validataion");
+  ok(currentSettings.get('lastObject.valid'), 'Second setting passes validation');
+});
+
+test('Actions', function(assert) {
+  assert.expect(5);
+
+  var settingsService = Ember.Object.create({
+    add: function() {
+      assert.ok(true, 'add called');
+    },
+    remove: function(setting) {
+      assert.ok(setting, 'Setting param is sent');
+    },
+    createKey: function(name) {
+      assert.ok(name, 'Name param is sent');
+    },
+    removeAll: function() {
+      assert.ok(true, 'removeAll called');
+    },
+    saveDefaultSettings: function() {
+      assert.ok(true, 'saveDefaultSettings called');
+    }
+  });
+
+  var controller = this.subject();
+  controller.set('settingsService', settingsService);
+
+  Ember.run(function() {
+    controller.send('add');
+    controller.send('remove', {});
+    controller.send('addKey', {});
+    controller.send('removeAll');
+    controller.send('saveDefaultSettings');
+  });
+});
+
+
+test('Excluded settings', function(assert) {
+  var controller = this.subject();
+
+  console.log(controller.get('predefinedSettings'));
+  assert.equal(controller.get('excluded').length, 0, 'Initially there are no excluded settings');
+
+  Ember.run(function() {
+    controller.get('settings').pushObject(Ember.Object.create({ key: { name: 'hive.tez.container.size' }}));
+    controller.get('settings').pushObject(Ember.Object.create({ key: { name: 'hive.prewarm.enabled' }}));
+  });
+
+  assert.equal(controller.get('excluded').length, 2, 'Two settings are excluded');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/tez-ui-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/tez-ui-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/tez-ui-test.js
new file mode 100644
index 0000000..fdf4a89
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/tez-ui-test.js
@@ -0,0 +1,98 @@
+/**
+ * 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 DS from 'ember-data';
+import { moduleFor, test } from 'ember-qunit';
+
+var container;
+
+moduleFor('controller:tez-ui', 'TezUIController', {
+  needs: [
+    'controller:index',
+    'service:job',
+    'service:file',
+    'controller:open-queries',
+    'controller:databases',
+    'controller:udfs',
+    'controller:index/history-query/logs',
+    'controller:index/history-query/results',
+    'controller:index/history-query/explain',
+    'controller:settings',
+    'controller:visual-explain',
+    'adapter:database',
+    'service:database',
+    'service:notify',
+    'service:job-progress',
+    'service:session',
+    'service:settings'
+  ],
+
+  setup: function() {
+    container = new Ember.Container();
+    container.register('store:main', Ember.Object.extend({
+      find: Ember.K
+    }));
+  }
+});
+
+test('controller is initialized properly.', function () {
+  expect(1);
+
+  var controller = this.subject();
+
+  ok(controller);
+});
+
+test('dagId returns false if there is  no tez view available', function() {
+  var controller = this.subject();
+
+  ok(!controller.get('dagId'), 'dagId is false without a tez view available');
+});
+
+// test('dagId returns the id if there is view available', function() {
+//   var controller = this.subject({
+//   });
+
+//   Ember.run(function() {
+//     controller.set('index.model', Ember.Object.create({
+//       id: 2,
+//       dagId: 3
+//     }));
+
+//     controller.set('isTezViewAvailable', true);
+//   });
+
+//   equal(controller.get('dagId'), 3, 'dagId is truthy');
+// });
+
+test('dagURL returns false if no dag id is available', function() {
+  var controller = this.subject();
+
+  ok(!controller.get('dagURL'), 'dagURL is false');
+});
+
+test('dagURL returns the url if dag id is available', function() {
+  var controller = this.subject({
+    tezViewURL: '1',
+    tezDagPath: '2',
+    dagId: '3'
+  });
+
+  equal(controller.get('dagURL'), '123');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/udfs-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/udfs-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/udfs-test.js
new file mode 100644
index 0000000..5bd369e
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/controllers/udfs-test.js
@@ -0,0 +1,60 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:udfs', 'UdfsController', {});
+
+test('controller is initialized', function() {
+  expect(3);
+
+  var component = this.subject();
+
+  equal(component.get('columns.length'), 2, 'Columns are initialized correctly');
+  ok(component.get('sortAscending'), 'Sort ascending is true');
+  equal(component.get('sortProperties.length'), 0, 'sortProperties is empty');
+});
+
+test('sort', function() {
+ expect(2);
+
+  var component = this.subject();
+
+  Ember.run(function () {
+    component.send('sort', 'prop');
+  });
+
+  ok(component.get('sortAscending'), 'New sort prop sortAscending is set to true');
+  equal(component.get('sortProperties').objectAt(0), "prop", 'sortProperties is set to prop');
+});
+
+test('add', function() {
+  expect(1);
+
+  var store = {
+    createRecord: function(name) {
+      ok(name, 'store.createRecord called');
+    }
+  };
+  var component = this.subject({ store: store });
+
+  Ember.run(function () {
+    component.send('add');
+  });
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/helpers/path-binding-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/helpers/path-binding-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/helpers/path-binding-test.js
new file mode 100644
index 0000000..22ba58a
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/helpers/path-binding-test.js
@@ -0,0 +1,35 @@
+/**
+ * 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 {
+  pathBinding
+} from 'hive/helpers/path-binding';
+
+import Ember from 'ember';
+
+module('PathBindingHelper');
+
+// Replace this with your real tests.
+test('it should retrieve property value for a given object.', function() {
+  var obj = Ember.Object.extend({
+    name: 'some name'
+  }).create();
+
+  var result = pathBinding(obj, 'name');
+  equal(result, obj.get('name'));
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/services/notify-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/services/notify-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/services/notify-test.js
new file mode 100644
index 0000000..383bf31
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/services/notify-test.js
@@ -0,0 +1,155 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('service:notify', 'NotifyService');
+
+test('Service initialized correctly', function () {
+  expect(3);
+
+  var service = this.subject();
+  service.removeAllMessages();
+  service.markMessagesAsSeen();
+
+  equal(service.get('messages.length'), 0, 'No messages');
+  equal(service.get('notifications.length'), 0, 'No notifications');
+  equal(service.get('unseenMessages.length'), 0, 'No unseenMessages');
+});
+
+test('Can add notification', function() {
+  expect(3);
+  var service = this.subject();
+
+  service.add('notif', 'message', 'body');
+
+  equal(service.get('messages.length'), 1, 'one message added');
+  equal(service.get('notifications.length'), 1, 'one notifications added');
+  equal(service.get('unseenMessages.length'), 1, 'one unseenMessages added');
+});
+
+test('Can add info notification', function() {
+  expect(1);
+  var service = this.subject();
+
+  service.info('message', 'body');
+  equal(service.get('messages.lastObject.type.typeClass'), 'alert-info', 'Info notification added');
+});
+
+test('Can add warn notification', function() {
+  expect(1);
+  var service = this.subject();
+
+  service.warn('message', 'body');
+  equal(service.get('messages.lastObject.type.typeClass'), 'alert-warning', 'Warn notification added');
+});
+
+test('Can add error notification', function() {
+  expect(1);
+  var service = this.subject();
+
+  service.error('message', 'body');
+  equal(service.get('messages.lastObject.type.typeClass'), 'alert-danger', 'Error notification added');
+});
+
+test('Can add success notification', function() {
+  expect(1);
+  var service = this.subject();
+
+  service.success('message', 'body');
+  equal(service.get('messages.lastObject.type.typeClass'), 'alert-success', 'Success notification added');
+});
+
+test('Can format message body', function() {
+  expect(3);
+
+  var objectBody = {
+    k1: 'v1',
+    k2: 'v2'
+  };
+  var formatted = "\n\nk1:\nv1\n\nk2:\nv2";
+  var service = this.subject();
+
+  ok(!service.formatMessageBody(), 'Return nothing if no body is passed');
+  equal(service.formatMessageBody('some string'), 'some string', 'Return the body if it is a string');
+  equal(service.formatMessageBody(objectBody), formatted, 'Parse the keys and return a string if it is an object');
+});
+
+test('Can removeMessage', function() {
+  expect(4);
+
+  var service = this.subject();
+  var messagesCount = service.get('messages.length');
+  var notificationCount = service.get('notifications.length');
+
+  service.add('type', 'message', 'body');
+
+  equal(service.get('messages.length'), messagesCount + 1, 'Message added');
+  equal(service.get('notifications.length'), notificationCount + 1, 'Notification added');
+
+  var message = service.get('messages.lastObject');
+  service.removeMessage(message);
+
+  equal(service.get('messages.length'), messagesCount, 'Message removed');
+  equal(service.get('notifications.length'), notificationCount, 'Notification removed');
+});
+
+test('Can removeNotification', function() {
+  expect(2);
+
+  var service = this.subject();
+  var notificationCount = service.get('notifications.length');
+
+  service.add('type', 'message', 'body');
+
+  equal(service.get('notifications.length'), notificationCount + 1, 'Notification added');
+
+  var notification = service.get('notifications.lastObject');
+  service.removeNotification(notification);
+
+  equal(service.get('notifications.length'), notificationCount, 'Notification removed');
+});
+
+test('Can removeAllMessages', function() {
+  expect(2);
+
+  var service = this.subject();
+
+  service.add('type', 'message', 'body');
+  service.add('type', 'message', 'body');
+  service.add('type', 'message', 'body');
+
+  ok(service.get('messages.length'), 'Messages are present');
+  service.removeAllMessages();
+  equal(service.get('messages.length'), 0, 'No messages found');
+});
+
+test('Can markMessagesAsSeen', function() {
+  expect(2);
+
+  var service = this.subject();
+
+  service.add('type', 'message', 'body');
+  service.add('type', 'message', 'body');
+  service.add('type', 'message', 'body');
+
+  ok(service.get('unseenMessages.length'), 'There are unseen messages');
+  service.markMessagesAsSeen();
+  equal(service.get('unseenMessages.length'), 0, 'No unseen messages');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/services/settings-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/services/settings-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/services/settings-test.js
new file mode 100644
index 0000000..cb99882
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/services/settings-test.js
@@ -0,0 +1,155 @@
+/**
+ * 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 { moduleFor, test } from 'ember-qunit';
+
+moduleFor('service:settings', 'SettingsService');
+
+test('Init', function(assert) {
+  var service = this.subject();
+  assert.ok(service);
+});
+
+test('Can create a setting object', function(assert) {
+  assert.expect(2);
+
+  var service = this.subject();
+
+  var setting = service._createSetting('sName', 'sValue');
+
+  assert.equal(setting.get('key.name'), 'sName', 'Settign has the correct name');
+  assert.equal(setting.get('value'), 'sValue', 'Settign has the correct value');
+
+  service.removeAll();
+});
+
+test('Can create default settings', function(assert) {
+  assert.expect(2);
+
+  var service = this.subject();
+
+  var settings = {
+    'sName1': 'sValue1',
+    'sName2': 'sValue2',
+    'sName3': 'sValue3'
+  };
+
+  service._createDefaultSettings();
+
+  assert.equal(service.get('settings.length'), 0, '0 settings created');
+
+  service._createDefaultSettings(settings);
+
+  assert.equal(service.get('settings.length'), 3, '3 settings created');
+
+  service.removeAll();
+});
+
+test('Can add a setting', function(assert) {
+  assert.expect(2);
+
+  var service = this.subject();
+  assert.equal(service.get('settings.length'), 0, 'No settings');
+  service.add();
+  service.add();
+  assert.equal(service.get('settings.length'), 2, '2 settings added');
+
+  service.removeAll();
+});
+
+test('Can remove a setting', function(assert) {
+  assert.expect(2);
+
+  var service = this.subject();
+
+  service.add();
+  service.add();
+
+  assert.equal(service.get('settings.length'), 2, '2 settings added');
+  var firstSetting = service.get('settings.firstObject');
+  service.remove(firstSetting);
+  assert.equal(service.get('settings.length'), 1, 'Setting removed');
+
+  service.removeAll();
+});
+
+test('Can create key', function(assert) {
+  assert.expect(2);
+  var service = this.subject();
+
+  assert.ok(!service.get('predefinedSettings').findBy('name', 'new.key.name'), 'Key doesn\'t exist');
+
+  var setting = service._createSetting();
+  setting.set('key', null);
+  service.get('settings').pushObject(setting);
+  service.createKey('new.key.name');
+
+  assert.ok(service.get('predefinedSettings').findBy('name', 'new.key.name'), 'Key created');
+
+  service.removeAll();
+});
+
+test('Can get settings string', function(assert) {
+  var service = this.subject();
+
+  var noSettings = service.getSettings();
+  assert.equal(noSettings, "", 'An empty string is returned if there are no settings');
+
+  var settings = {
+    'sName1': 'sValue1',
+    'sName2': 'sValue2'
+  };
+
+  service._createDefaultSettings(settings);
+
+  var expectedWithSettings = "set sName1=sValue1;\nset sName2=sValue2;\n--Global Settings--\n\n";
+  var withSettings = service.getSettings();
+
+  assert.equal(withSettings, expectedWithSettings, 'Returns correct string');
+});
+
+test('It can parse global settings', function(assert) {
+  var service = this.subject();
+
+  assert.ok(!service.parseGlobalSettings(), 'It returns if query or model is not passed');
+
+  var settings = {
+    'sName1': 'sValue1',
+    'sName2': 'sValue2'
+  };
+
+
+  var globalSettingsString = "set sName1=sValue1;\nset sName2=sValue2;\n--Global Settings--\n\n";
+
+  var model = Ember.Object.create({
+    globalSettings: globalSettingsString
+  });
+
+  var query = Ember.Object.create({
+    fileContent: globalSettingsString + "{{match}}"
+  });
+
+  assert.ok(!service.parseGlobalSettings(query, model), 'It returns if current settings don\'t match models global settings');
+
+  service._createDefaultSettings(settings);
+
+  service.parseGlobalSettings(query, model);
+
+  assert.equal(query.get('fileContent'), "{{match}}", 'It parsed global settings');
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/views/visual-explain-test.js
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/views/visual-explain-test.js b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/views/visual-explain-test.js
new file mode 100644
index 0000000..2fa23df
--- /dev/null
+++ b/contrib/views/hive-next/src/main/resources/ui/hive-web/tests/unit/views/visual-explain-test.js
@@ -0,0 +1,106 @@
+/**
+ * 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 {
+  moduleFor,
+  test
+} from 'ember-qunit';
+
+var view;
+
+moduleFor('view:visual-explain', 'VisualExplainView', {
+  setup: function() {
+    var controller = Ember.Controller.extend({}).create();
+
+    view = this.subject({
+      controller: controller
+    });
+
+    Ember.run(function() {
+      view.appendTo('#ember-testing');
+    });
+  },
+
+  teardown: function() {
+    Ember.run(view, view.destroy);
+  },
+});
+
+//select count (*) from power
+var selectCountJson = {"STAGE PLANS":{"Stage-1":{"Tez":{"DagName:":"hive_20150608120000_b930a285-dc6a-49b7-86b6-8bee5ecdeacd:96","Vertices:":{"Reducer 2":{"Reduce Operator Tree:":{"Group By Operator":{"mode:":"mergepartial","aggregations:":["count(VALUE._col0)"],"outputColumnNames:":["_col0"],"children":{"Select Operator":{"expressions:":"_col0 (type: bigint)","outputColumnNames:":["_col0"],"children":{"File Output Operator":{"Statistics:":"Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: COMPLETE","compressed:":"false","table:":{"serde:":"org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe","input format:":"org.apache.hadoop.mapred.TextInputFormat","output format:":"org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"}}},"Statistics:":"Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: COMPLETE"}},"Statistics:":"Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: COMPLETE"}}},"Map 1":{"Map Operator Tree:":[{"TableScan":{"alias:":"power","childre
 n":{"Select Operator":{"children":{"Group By Operator":{"mode:":"hash","aggregations:":["count()"],"outputColumnNames:":["_col0"],"children":{"Reduce Output Operator":{"sort order:":"","value expressions:":"_col0 (type: bigint)","Statistics:":"Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: COMPLETE"}},"Statistics:":"Num rows: 1 Data size: 8 Basic stats: COMPLETE Column stats: COMPLETE"}},"Statistics:":"Num rows: 0 Data size: 132960632 Basic stats: PARTIAL Column stats: COMPLETE"}},"Statistics:":"Num rows: 0 Data size: 132960632 Basic stats: PARTIAL Column stats: COMPLETE"}}]}},"Edges:":{"Reducer 2":{"parent":"Map 1","type":"SIMPLE_EDGE"}}}},"Stage-0":{"Fetch Operator":{"limit:":"-1","Processor Tree:":{"ListSink":{}}}}},"STAGE DEPENDENCIES":{"Stage-1":{"ROOT STAGE":"TRUE"},"Stage-0":{"DEPENDENT STAGES":"Stage-1"}}};
+
+//select power.adate, power.atime from power join power2 on power.adate = power2.adate
+var joinJson = {"STAGE PLANS":{"Stage-1":{"Tez":{"DagName:":"hive_20150608124141_acde7f09-6b72-4ad4-88b0-807d499724eb:107","Vertices:":{"Reducer 2":{"Reduce Operator Tree:":{"Merge Join Operator":{"outputColumnNames:":["_col0","_col1"],"children":{"Select Operator":{"expressions:":"_col0 (type: string), _col1 (type: string)","outputColumnNames:":["_col0","_col1"],"children":{"File Output Operator":{"Statistics:":"Num rows: 731283 Data size: 73128349 Basic stats: COMPLETE Column stats: NONE","compressed:":"false","table:":{"serde:":"org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe","input format:":"org.apache.hadoop.mapred.TextInputFormat","output format:":"org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"}}},"Statistics:":"Num rows: 731283 Data size: 73128349 Basic stats: COMPLETE Column stats: NONE"}},"Statistics:":"Num rows: 731283 Data size: 73128349 Basic stats: COMPLETE Column stats: NONE","condition map:":[{"":"Inner Join 0 to 1"}],"condition expressions:":{"1":"",
 "0":"{KEY.reducesinkkey0} {VALUE._col0}"}}}},"Map 1":{"Map Operator Tree:":[{"TableScan":{"filterExpr:":"adate is not null (type: boolean)","alias:":"power2","children":{"Filter Operator":{"predicate:":"adate is not null (type: boolean)","children":{"Reduce Output Operator":{"Map-reduce partition columns:":"adate (type: string)","sort order:":"+","Statistics:":"Num rows: 664803 Data size: 66480316 Basic stats: COMPLETE Column stats: NONE","key expressions:":"adate (type: string)"}},"Statistics:":"Num rows: 664803 Data size: 66480316 Basic stats: COMPLETE Column stats: NONE"}},"Statistics:":"Num rows: 1329606 Data size: 132960632 Basic stats: COMPLETE Column stats: NONE"}}]},"Map 3":{"Map Operator Tree:":[{"TableScan":{"filterExpr:":"adate is not null (type: boolean)","alias:":"power","children":{"Filter Operator":{"predicate:":"adate is not null (type: boolean)","children":{"Reduce Output Operator":{"Map-reduce partition columns:":"adate (type: string)","sort order:":"+","value expr
 essions:":"atime (type: string)","Statistics:":"Num rows: 332402 Data size: 66480416 Basic stats: COMPLETE Column stats: NONE","key expressions:":"adate (type: string)"}},"Statistics:":"Num rows: 332402 Data size: 66480416 Basic stats: COMPLETE Column stats: NONE"}},"Statistics:":"Num rows: 664803 Data size: 132960632 Basic stats: COMPLETE Column stats: NONE"}}]}},"Edges:":{"Reducer 2":[{"parent":"Map 1","type":"SIMPLE_EDGE"},{"parent":"Map 3","type":"SIMPLE_EDGE"}]}}},"Stage-0":{"Fetch Operator":{"limit:":"-1","Processor Tree:":{"ListSink":{}}}}},"STAGE DEPENDENCIES":{"Stage-1":{"ROOT STAGE":"TRUE"},"Stage-0":{"DEPENDENT STAGES":"Stage-1"}}};
+
+// Replace this with your real tests.
+test('it renders dag when controller.json changes.', function (assert) {
+  assert.expect(1);
+
+  view.renderDag = function () {
+    assert.ok(true, 'dag rendering has been called on json set.');
+  };
+
+  view.set('controller.json', selectCountJson);
+});
+
+test('renderDag generates correct number of nodes and edges.', function (assert) {
+  assert.expect(4);
+
+  Ember.run(function () {
+    view.set('controller.json', selectCountJson);
+
+    assert.equal(view.get('graph').nodes().length, 4);
+    assert.equal(view.get('graph').edges().length, 3);
+
+    view.set('controller.json', joinJson);
+
+    assert.equal(view.get('graph').nodes().length, 7);
+    assert.equal(view.get('graph').edges().length, 6);
+  });
+});
+
+test('progress gets updated for each node.', function (assert) {
+  expect(2);
+
+  Ember.run(function () {
+    view.set('controller.json', selectCountJson);
+
+    var targetNode;
+    var verticesGroups = view.get('verticesGroups');
+
+    verticesGroups.some(function (verticesGroup) {
+      var node = verticesGroup.contents.findBy('label', 'Map 1');
+
+      if (node) {
+        targetNode = node;
+        return true;
+      }
+    });
+
+    assert.equal(targetNode.get('progress'), undefined, 'initial progress is falsy.');
+
+    view.set('controller.verticesProgress', [
+      Ember.Object.create({
+        name: 'Map 1',
+        value: 1
+      })
+    ]);
+
+    assert.equal(targetNode.get('progress'), 1, 'progress gets updated to given value.');
+  });
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88db3cc/contrib/views/hive-next/src/main/resources/ui/hive-web/vendor/.gitkeep
----------------------------------------------------------------------
diff --git a/contrib/views/hive-next/src/main/resources/ui/hive-web/vendor/.gitkeep b/contrib/views/hive-next/src/main/resources/ui/hive-web/vendor/.gitkeep
new file mode 100644
index 0000000..e69de29