浏览代码

AMBARI-10997. Improve Error Handling - hive view (alexantonenko)

Alex Antonenko 10 年之前
父节点
当前提交
642fd46cbf
共有 100 个文件被更改,包括 1425 次插入563 次删除
  1. 1 1
      contrib/views/hive/pom.xml
  2. 2 2
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/BaseService.java
  3. 1 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/HelpService.java
  4. 3 2
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/client/Connection.java
  5. 1 8
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/PersonalCRUDResourceManager.java
  6. 1 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/browser/HiveBrowserService.java
  7. 1 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/files/FileService.java
  8. 2 2
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/Aggregator.java
  9. 2 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/ConnectionController.java
  10. 5 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/IOperationHandleResourceManager.java
  11. 3 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/JobResourceProvider.java
  12. 25 7
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/JobService.java
  13. 19 12
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/OperationHandleController.java
  14. 2 2
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/OperationHandleControllerFactory.java
  15. 32 8
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/OperationHandleResourceManager.java
  16. 67 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/ProgressRetriever.java
  17. 27 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/ATSParser.java
  18. 4 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/ATSRequestsDelegate.java
  19. 13 2
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/ATSRequestsDelegateImpl.java
  20. 2 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/IATSParser.java
  21. 24 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/TezVertexId.java
  22. 88 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/rm/RMParser.java
  23. 39 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/rm/RMParserFactory.java
  24. 31 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/rm/RMRequestsDelegate.java
  25. 99 0
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/rm/RMRequestsDelegateImpl.java
  26. 3 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/resources/FileResourceResourceProvider.java
  27. 1 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/resources/FileResourceService.java
  28. 3 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/savedQueries/SavedQueryResourceProvider.java
  29. 1 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/savedQueries/SavedQueryService.java
  30. 3 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/udfs/UDFResourceProvider.java
  31. 1 1
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/udfs/UDFService.java
  32. 16 4
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/utils/ServiceFormattedException.java
  33. 17 3
      contrib/views/hive/src/main/java/org/apache/ambari/view/hive/utils/SharedObjectsFactory.java
  34. 3 1
      contrib/views/hive/src/main/resources/ui/hive-web/.gitignore
  35. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/adapters/application.js
  36. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/alert-message-widget.js
  37. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/collapsible-widget.js
  38. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/column-filter-widget.js
  39. 34 11
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/date-range-widget.js
  40. 6 0
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/expander-widget.js
  41. 6 6
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/modal-widget.js
  42. 4 4
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/notify-widget.js
  43. 27 15
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/number-range-widget.js
  44. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/panel-widget.js
  45. 3 2
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/popover-widget.js
  46. 7 22
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/progress-widget.js
  47. 3 3
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/query-editor.js
  48. 10 9
      contrib/views/hive/src/main/resources/ui/hive-web/app/components/typeahead-widget.js
  49. 5 5
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/databases.js
  50. 25 0
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/history.js
  51. 34 61
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index.js
  52. 2 3
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/explain.js
  53. 9 4
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/logs.js
  54. 3 3
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/results.js
  55. 92 0
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/job-progress.js
  56. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/job.js
  57. 7 3
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/messages.js
  58. 2 4
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/open-queries.js
  59. 17 3
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/queries.js
  60. 80 37
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/query-tabs.js
  61. 69 75
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/settings.js
  62. 9 10
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/tez-ui.js
  63. 17 0
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/udfs.js
  64. 26 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/visual-explain.js
  65. 2 2
      contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/all-uppercase.js
  66. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/code-helper.js
  67. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/date-binding.js
  68. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/log-helper.js
  69. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/path-binding.js
  70. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/preformatted-string.js
  71. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/tb-helper.js
  72. 19 3
      contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/i18n.js
  73. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/notify.js
  74. 3 5
      contrib/views/hive/src/main/resources/ui/hive-web/app/mixins/filterable.js
  75. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/router.js
  76. 13 2
      contrib/views/hive/src/main/resources/ui/hive-web/app/routes/application.js
  77. 11 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/routes/index/saved-query.js
  78. 2 2
      contrib/views/hive/src/main/resources/ui/hive-web/app/serializers/database.js
  79. 19 12
      contrib/views/hive/src/main/resources/ui/hive-web/app/services/notify.js
  80. 43 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/styles/app.scss
  81. 0 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/styles/dropdown-submenu.scss
  82. 16 16
      contrib/views/hive/src/main/resources/ui/hive-web/app/styles/mixins.scss
  83. 1 0
      contrib/views/hive/src/main/resources/ui/hive-web/app/styles/notifications.scss
  84. 2 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/styles/query-tabs.scss
  85. 1 0
      contrib/views/hive/src/main/resources/ui/hive-web/app/styles/vars.scss
  86. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/templates/application.hbs
  87. 4 6
      contrib/views/hive/src/main/resources/ui/hive-web/app/templates/components/progress-widget.hbs
  88. 6 2
      contrib/views/hive/src/main/resources/ui/hive-web/app/templates/index.hbs
  89. 11 9
      contrib/views/hive/src/main/resources/ui/hive-web/app/templates/messages.hbs
  90. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/templates/query-tabs.hbs
  91. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/templates/tez-ui.hbs
  92. 56 49
      contrib/views/hive/src/main/resources/ui/hive-web/app/templates/visual-explain.hbs
  93. 31 30
      contrib/views/hive/src/main/resources/ui/hive-web/app/utils/constants.js
  94. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/app/utils/dag-rules.js
  95. 18 3
      contrib/views/hive/src/main/resources/ui/hive-web/app/utils/functions.js
  96. 2 2
      contrib/views/hive/src/main/resources/ui/hive-web/app/views/message.js
  97. 35 0
      contrib/views/hive/src/main/resources/ui/hive-web/app/views/messages.js
  98. 7 7
      contrib/views/hive/src/main/resources/ui/hive-web/app/views/notification.js
  99. 65 43
      contrib/views/hive/src/main/resources/ui/hive-web/app/views/visual-explain.js
  100. 1 1
      contrib/views/hive/src/main/resources/ui/hive-web/package.json

+ 1 - 1
contrib/views/hive/pom.xml

@@ -19,7 +19,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.apache.ambari.contrib.views</groupId>
   <artifactId>hive</artifactId>
-  <version>0.2.0-SNAPSHOT</version>
+  <version>0.3.0-SNAPSHOT</version>
   <name>Hive</name>
 
   <parent>

+ 2 - 2
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/BaseService.java

@@ -18,13 +18,13 @@
 
 package org.apache.ambari.view.hive;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.ViewContext;
-import org.apache.ambari.view.hive.utils.HdfsApi;
 import org.apache.ambari.view.hive.utils.SharedObjectsFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
+
 
 /**
  * Parent service

+ 1 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/HelpService.java

@@ -18,10 +18,10 @@
 
 package org.apache.ambari.view.hive;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.ViewContext;
 import org.apache.ambari.view.ViewResourceHandler;
 
+import javax.inject.Inject;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;

+ 3 - 2
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/client/Connection.java

@@ -18,6 +18,7 @@
 
 package org.apache.ambari.view.hive.client;
 
+import org.apache.ambari.view.hive.utils.HiveClientFormattedException;
 import org.apache.commons.codec.binary.Hex;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hive.shims.ShimLoader;
@@ -347,10 +348,10 @@ public class Connection {
   public String getLogs(TOperationHandle handle) {
     LogsCursor results = new LogsCursor(this, handle);
     results.reset(); // we have to read from FIRST line, to get
-                     // logs from beginning on every call this function
+    // logs from beginning on every call this function
     List<String> logLineList = results.getValuesInColumn(0);
     StringBuilder log = new StringBuilder();
-    for(String line : logLineList) {
+    for (String line : logLineList) {
       log.append(line);
       log.append('\n');
     }

+ 1 - 8
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/PersonalCRUDResourceManager.java

@@ -92,14 +92,7 @@ public class PersonalCRUDResourceManager<T extends PersonalResource> extends CRU
     return result;
   }
 
-  protected static String getUsername(ViewContext context) {
-    String userName = context.getProperties().get("dataworker.username");
-    if (userName == null || userName.compareTo("null") == 0 || userName.compareTo("") == 0)
-      userName = context.getUsername();
-    return userName;
-  }
-
   protected String getUsername() {
-    return getUsername(context);
+    return context.getUsername();
   }
 }

+ 1 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/browser/HiveBrowserService.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.browser;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.ViewContext;
 import org.apache.ambari.view.ViewResourceHandler;
 import org.apache.ambari.view.hive.client.ColumnDescription;
@@ -34,6 +33,7 @@ import org.json.simple.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;

+ 1 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/files/FileService.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.files;
 
-import com.google.inject.Inject;
 import com.jayway.jsonpath.JsonPath;
 import org.apache.ambari.view.ViewContext;
 import org.apache.ambari.view.ViewResourceHandler;
@@ -32,6 +31,7 @@ import org.json.simple.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.*;
 import javax.ws.rs.core.Context;

+ 2 - 2
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/Aggregator.java

@@ -130,7 +130,7 @@ public class Aggregator {
   }
 
   public Job readATSJob(Job viewJob) throws ItemNotFound {
-    TOperationHandle operationHandle = operationHandleResourceManager.getHandleForJob(viewJob);
+    TOperationHandle operationHandle = operationHandleResourceManager.getHandleForJob(viewJob).toTOperationHandle();
 
     String hexGuid = Hex.encodeHexString(operationHandle.getOperationId().getGuid());
     HiveQueryId atsHiveQuery = ats.getHiveQueryIdByOperationId(hexStringToUrlSafeBase64(hexGuid));
@@ -174,12 +174,12 @@ public class Aggregator {
     if (viewJob.getDagName() == null || viewJob.getDagName().isEmpty()) {
       if (hiveQueryId.dagNames != null && hiveQueryId.dagNames.size() > 0) {
         viewJob.setDagName(hiveQueryId.dagNames.get(0));
-        viewJob.setDagId(tezDagId.entity);
         viewJobResourceManager.update(viewJob, viewJob.getId());
       }
     }
     if ((tezDagId.status.compareToIgnoreCase(Job.JOB_STATE_UNKNOWN) != 0) &&
         !viewJob.getStatus().equals(tezDagId.status)) {
+      viewJob.setDagId(tezDagId.entity);
       viewJob.setStatus(tezDagId.status);
       viewJobResourceManager.update(viewJob, viewJob.getId());
     }

+ 2 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/ConnectionController.java

@@ -68,6 +68,7 @@ public class ConnectionController {
     } catch (HiveClientException e) {
       throw new HiveClientFormattedException(e);
     }
-    return operationHandleControllerFactory.createControllerForHandle(operationHandle);
+    StoredOperationHandle storedOperationHandle = StoredOperationHandle.buildFromTOperationHandle(operationHandle);
+    return operationHandleControllerFactory.createControllerForHandle(storedOperationHandle);
   }
 }

+ 5 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/IOperationHandleResourceManager.java

@@ -28,9 +28,13 @@ import java.util.List;
 public interface IOperationHandleResourceManager extends IResourceManager<StoredOperationHandle> {
   List<StoredOperationHandle> readJobRelatedHandles(Job job);
 
+  List<Job> getHandleRelatedJobs(StoredOperationHandle operationHandle);
+
+  Job getJobByHandle(StoredOperationHandle handle) throws ItemNotFound;
+
   void putHandleForJob(TOperationHandle h, Job job);
 
   boolean containsHandleForJob(Job job);
 
-  TOperationHandle getHandleForJob(Job job) throws ItemNotFound;
+  StoredOperationHandle getHandleForJob(Job job) throws ItemNotFound;
 }

+ 3 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/JobResourceProvider.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.jobs;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.*;
 import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
 import org.apache.ambari.view.hive.persistence.utils.OnlyOwnersFilteringStrategy;
@@ -27,6 +26,7 @@ import org.apache.ambari.view.hive.utils.SharedObjectsFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
 import java.util.Map;
@@ -61,6 +61,8 @@ public class JobResourceProvider implements ResourceProvider<Job> {
 
   @Override
   public Set<Job> getResources(ReadRequest readRequest) throws SystemException, NoSuchResourceException, UnsupportedPropertyException {
+    if (context == null)
+      return new HashSet();
     return new HashSet<Job>(getResourceManager().readAll(
         new OnlyOwnersFilteringStrategy(this.context.getUsername())));
   }

+ 25 - 7
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/JobService.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.jobs;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.ViewResourceHandler;
 import org.apache.ambari.view.hive.BaseService;
 import org.apache.ambari.view.hive.backgroundjobs.BackgroundJobController;
@@ -26,13 +25,9 @@ import org.apache.ambari.view.hive.client.Connection;
 import org.apache.ambari.view.hive.client.Cursor;
 import org.apache.ambari.view.hive.client.HiveClientException;
 import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
-import org.apache.ambari.view.hive.resources.jobs.atsJobs.ATSRequestsDelegate;
-import org.apache.ambari.view.hive.resources.jobs.atsJobs.ATSRequestsDelegateImpl;
-import org.apache.ambari.view.hive.resources.jobs.atsJobs.ATSParser;
 import org.apache.ambari.view.hive.resources.jobs.atsJobs.IATSParser;
 import org.apache.ambari.view.hive.resources.jobs.viewJobs.*;
 import org.apache.ambari.view.hive.utils.*;
-import org.apache.ambari.view.hive.utils.HdfsApi;
 import org.apache.commons.beanutils.PropertyUtils;
 import org.apache.commons.csv.CSVFormat;
 import org.apache.commons.csv.CSVPrinter;
@@ -41,6 +36,7 @@ import org.json.simple.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.*;
 import javax.ws.rs.core.*;
@@ -87,8 +83,7 @@ public class JobService extends BaseService {
 
   protected Aggregator getAggregator() {
     if (aggregator == null) {
-      ATSRequestsDelegate transport = new ATSRequestsDelegateImpl(context, "http://127.0.0.1:8188");
-      IATSParser atsParser = new ATSParser(transport);
+      IATSParser atsParser = getSharedObjectsFactory().getATSParser();
       aggregator = new Aggregator(getResourceManager(), getOperationHandleResourceManager(), atsParser);
     }
     return aggregator;
@@ -319,6 +314,29 @@ public class JobService extends BaseService {
     }
   }
 
+  /**
+   * Get progress info
+   */
+  @GET
+  @Path("{jobId}/progress")
+  @Produces(MediaType.APPLICATION_JSON)
+  public Response getProgress(@PathParam("jobId") String jobId) {
+    try {
+      final JobController jobController = getResourceManager().readController(jobId);
+
+      ProgressRetriever.Progress progress = new ProgressRetriever(jobController.getJob(), getSharedObjectsFactory()).
+          getProgress();
+
+      return Response.ok(progress).build();
+    } catch (WebApplicationException ex) {
+      throw ex;
+    } catch (ItemNotFound itemNotFound) {
+      throw new NotFoundFormattedException(itemNotFound.getMessage(), itemNotFound);
+    } catch (Exception ex) {
+      throw new ServiceFormattedException(ex.getMessage(), ex);
+    }
+  }
+
   /**
    * Delete single item
    */

+ 19 - 12
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/OperationHandleController.java

@@ -22,6 +22,7 @@ package org.apache.ambari.view.hive.resources.jobs;
 import org.apache.ambari.view.hive.client.Cursor;
 import org.apache.ambari.view.hive.client.HiveClientException;
 import org.apache.ambari.view.hive.client.IConnectionFactory;
+import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
 import org.apache.ambari.view.hive.resources.jobs.viewJobs.Job;
 import org.apache.ambari.view.hive.utils.HiveClientFormattedException;
 import org.apache.hive.service.cli.thrift.TGetOperationStatusResp;
@@ -32,23 +33,21 @@ import org.slf4j.LoggerFactory;
 public class OperationHandleController {
   private final static Logger LOG =
       LoggerFactory.getLogger(OperationHandleController.class);
+  private final IConnectionFactory connectionsFabric;
 
-  private IConnectionFactory connectionsFabric;
-  private TOperationHandle operationHandle;
-  private IOperationHandleResourceManager operationHandlesStorage;
+  private final TOperationHandle operationHandle;
+  private final StoredOperationHandle storedOperationHandle;
+  private final IOperationHandleResourceManager operationHandlesStorage;
 
-  public OperationHandleController(IConnectionFactory connectionsFabric, TOperationHandle storedOperationHandle, IOperationHandleResourceManager operationHandlesStorage) {
+  public OperationHandleController(IConnectionFactory connectionsFabric, StoredOperationHandle storedOperationHandle, IOperationHandleResourceManager operationHandlesStorage) {
     this.connectionsFabric = connectionsFabric;
-    this.operationHandle = storedOperationHandle;
+    this.storedOperationHandle = storedOperationHandle;
+    this.operationHandle = storedOperationHandle.toTOperationHandle();
     this.operationHandlesStorage = operationHandlesStorage;
   }
 
-  public TOperationHandle getStoredOperationHandle() {
-    return operationHandle;
-  }
-
-  public void setStoredOperationHandle(TOperationHandle storedOperationHandle) {
-    this.operationHandle = storedOperationHandle;
+  public StoredOperationHandle getStoredOperationHandle() {
+    return storedOperationHandle;
   }
 
   public OperationStatus getOperationStatus() throws NoOperationStatusSetException, HiveClientException {
@@ -107,7 +106,15 @@ public class OperationHandleController {
   }
 
   public String getLogs() {
-    return connectionsFabric.getHiveConnection().getLogs(operationHandle);
+    String logs;
+    try {
+      logs = connectionsFabric.getHiveConnection().getLogs(operationHandle);
+    } catch (HiveClientFormattedException ex) {
+      logs = "";
+      LOG.info(String.format("Logs are not available yet for job #%s [%s]\n%s",
+          storedOperationHandle.getJobId(), storedOperationHandle.getGuid(), ex.toString()));
+    }
+    return logs;
   }
 
   public Cursor getResults() {

+ 2 - 2
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/OperationHandleControllerFactory.java

@@ -32,12 +32,12 @@ public class OperationHandleControllerFactory {
     operationHandlesStorage = new OperationHandleResourceManager(connectionsFabric);
   }
 
-  public OperationHandleController createControllerForHandle(TOperationHandle storedOperationHandle) {
+  public OperationHandleController createControllerForHandle(StoredOperationHandle storedOperationHandle) {
     return new OperationHandleController(connectionsFabric, storedOperationHandle, operationHandlesStorage);
   }
 
   public OperationHandleController getHandleForJob(Job job) throws ItemNotFound {
-    TOperationHandle handle = operationHandlesStorage.getHandleForJob(job);
+    StoredOperationHandle handle = operationHandlesStorage.getHandleForJob(job);
     return createControllerForHandle(handle);
   }
 }

+ 32 - 8
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/OperationHandleResourceManager.java

@@ -54,6 +54,38 @@ public class OperationHandleResourceManager extends SharedCRUDResourceManager<St
     });
   }
 
+  @Override
+  public StoredOperationHandle getHandleForJob(Job job) throws ItemNotFound {
+    List<StoredOperationHandle> jobRelatedHandles = readJobRelatedHandles(job);
+    if (jobRelatedHandles.size() == 0)
+      throw new ItemNotFound();
+    return jobRelatedHandles.get(0);
+  }
+
+  @Override
+  public List<Job> getHandleRelatedJobs(final StoredOperationHandle operationHandle) {
+    return storageFactory.getStorage().loadAll(Job.class, new FilteringStrategy() {
+      @Override
+      public boolean isConform(Indexed item) {
+        Job job = (Job) item;
+        return (job.getId() != null && job.getId().equals(operationHandle.getJobId()));
+      }
+
+      @Override
+      public String whereStatement() {
+        return "id = '" + operationHandle.getJobId() + "'";
+      }
+    });
+  }
+
+  @Override
+  public Job getJobByHandle(StoredOperationHandle handle) throws ItemNotFound {
+    List<Job> handleRelatedJobs = getHandleRelatedJobs(handle);
+    if (handleRelatedJobs.size() == 0)
+      throw new ItemNotFound();
+    return handleRelatedJobs.get(0);
+  }
+
   @Override
   public void putHandleForJob(TOperationHandle h, Job job) {
     StoredOperationHandle handle = StoredOperationHandle.buildFromTOperationHandle(h);
@@ -77,12 +109,4 @@ public class OperationHandleResourceManager extends SharedCRUDResourceManager<St
     List<StoredOperationHandle> jobRelatedHandles = readJobRelatedHandles(job);
     return jobRelatedHandles.size() > 0;
   }
-
-  @Override
-  public TOperationHandle getHandleForJob(Job job) throws ItemNotFound {
-    List<StoredOperationHandle> jobRelatedHandles = readJobRelatedHandles(job);
-    if (jobRelatedHandles.size() == 0)
-      throw new ItemNotFound();
-    return jobRelatedHandles.get(0).toTOperationHandle();
-  }
 }

+ 67 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/ProgressRetriever.java

@@ -0,0 +1,67 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.view.hive.resources.jobs;
+
+import org.apache.ambari.view.hive.resources.jobs.atsJobs.TezVertexId;
+import org.apache.ambari.view.hive.resources.jobs.rm.RMParser;
+import org.apache.ambari.view.hive.resources.jobs.viewJobs.Job;
+import org.apache.ambari.view.hive.utils.ServiceFormattedException;
+import org.apache.ambari.view.hive.utils.SharedObjectsFactory;
+
+import java.util.List;
+import java.util.Map;
+
+public class ProgressRetriever {
+  private final Progress progress;
+  private final Job job;
+  private final SharedObjectsFactory sharedObjects;
+
+  public ProgressRetriever(Job job, SharedObjectsFactory sharedObjects) {
+    this.job = job;
+    this.sharedObjects = sharedObjects;
+
+    this.progress = new Progress();
+  }
+
+  public Progress getProgress() {
+    jobCheck();
+
+    progress.dagProgress = sharedObjects.getRMParser().getDAGProgress(
+        job.getApplicationId(), job.getDagId());
+
+    List<TezVertexId> vertices = sharedObjects.getATSParser().getVerticesForDAGId(job.getDagId());
+    progress.vertexProgresses = sharedObjects.getRMParser().getDAGVerticesProgress(job.getApplicationId(), job.getDagId(), vertices);
+
+    return progress;
+  }
+
+  public void jobCheck() {
+    if (job.getApplicationId() == null || job.getApplicationId().isEmpty()) {
+      throw new ServiceFormattedException("E070 ApplicationId is not defined yet");
+    }
+    if (job.getDagId() == null || job.getDagId().isEmpty()) {
+      throw new ServiceFormattedException("E080 DagID is not defined yet");
+    }
+  }
+
+  public static class Progress {
+    public Double dagProgress;
+    public List<RMParser.VertexProgress> vertexProgresses;
+  }
+}

+ 27 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/ATSParser.java

@@ -57,6 +57,24 @@ public class ATSParser implements IATSParser {
     return parsedJobs;
   }
 
+  @Override
+  public List<TezVertexId> getVerticesForDAGId(String dagId) {
+    JSONObject entities = delegate.tezVerticesListForDAG(dagId);
+    JSONArray vertices = (JSONArray) entities.get("entities");
+
+    List<TezVertexId> parsedVertices = new LinkedList<TezVertexId>();
+    for(Object vertex : vertices) {
+      try {
+        TezVertexId parsedVertex = parseVertex((JSONObject) vertex);
+        parsedVertices.add(parsedVertex);
+      } catch (Exception ex) {
+        LOG.error("Error while parsing the vertex", ex);
+      }
+    }
+
+    return parsedVertices;
+  }
+
   @Override
   public HiveQueryId getHiveQueryIdByOperationId(String guidString) {
     JSONObject entities = delegate.hiveQueryIdByOperationId(guidString);
@@ -131,6 +149,15 @@ public class ATSParser implements IATSParser {
     return parsedJob;
   }
 
+  private TezVertexId parseVertex(JSONObject vertex) {
+    TezVertexId tezVertexId = new TezVertexId();
+    tezVertexId.entity = (String)vertex.get("entity");
+    JSONObject otherinfo = (JSONObject)vertex.get("otherinfo");
+    if (otherinfo != null)
+      tezVertexId.vertexName = (String)otherinfo.get("vertexName");
+    return tezVertexId;
+  }
+
   private JSONObject getLastEvent(JSONObject atsEntity) {
     JSONArray events = (JSONArray) atsEntity.get("events");
     return (JSONObject) events.get(0);

+ 4 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/ATSRequestsDelegate.java

@@ -29,9 +29,13 @@ public interface ATSRequestsDelegate {
 
   String tezDagNameUrl(String name);
 
+  String tezVerticesListForDAGUrl(String dagId);
+
   JSONObject hiveQueryIdList(String username);
 
   JSONObject hiveQueryIdByOperationId(String operationId);
 
   JSONObject tezDagByName(String name);
+
+  JSONObject tezVerticesListForDAG(String dagId);
 }

+ 13 - 2
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/ATSRequestsDelegateImpl.java

@@ -62,6 +62,11 @@ public class ATSRequestsDelegateImpl implements ATSRequestsDelegate {
     return atsUrl + "/ws/v1/timeline/TEZ_DAG_ID?primaryFilter=dagName:" + name;
   }
 
+  @Override
+  public String tezVerticesListForDAGUrl(String dagId) {
+    return atsUrl + "/ws/v1/timeline/TEZ_VERTEX_ID?primaryFilter=TEZ_DAG_ID:" + dagId;
+  }
+
   @Override
   public JSONObject hiveQueryIdList(String username) {
     String hiveQueriesListUrl = atsUrl + "/ws/v1/timeline/HIVE_QUERY_ID?primaryFilter=requestuser:" + username;
@@ -83,10 +88,16 @@ public class ATSRequestsDelegateImpl implements ATSRequestsDelegate {
     return (JSONObject) JSONValue.parse(response);
   }
 
-  protected String readFromWithDefault(String hiveQueriesListUrl, String defaultResponse) {
+  @Override
+  public JSONObject tezVerticesListForDAG(String dagId) {
+    String response = readFromWithDefault(tezVerticesListForDAGUrl(dagId), "{ \"entities\" : [  ] }");
+    return (JSONObject) JSONValue.parse(response);
+  }
+
+  protected String readFromWithDefault(String atsUrl, String defaultResponse) {
     String response;
     try {
-      InputStream responseInputStream = context.getURLStreamProvider().readFrom(hiveQueriesListUrl, "GET",
+      InputStream responseInputStream = context.getURLStreamProvider().readFrom(atsUrl, "GET",
           null, new HashMap<String, String>());
       response = IOUtils.toString(responseInputStream);
     } catch (IOException e) {

+ 2 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/IATSParser.java

@@ -23,6 +23,8 @@ import java.util.List;
 public interface IATSParser {
   List<HiveQueryId> getHiveQueryIdsList(String username);
 
+  List<TezVertexId> getVerticesForDAGId(String dagId);
+
   HiveQueryId getHiveQueryIdByOperationId(String guidString);
 
   TezDagId getTezDAGByName(String name);

+ 24 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/atsJobs/TezVertexId.java

@@ -0,0 +1,24 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.view.hive.resources.jobs.atsJobs;
+
+public class TezVertexId {
+  public String entity;
+  public String vertexName;
+}

+ 88 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/rm/RMParser.java

@@ -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.
+ */
+
+package org.apache.ambari.view.hive.resources.jobs.rm;
+
+import org.apache.ambari.view.hive.resources.jobs.atsJobs.TezVertexId;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class RMParser {
+  private RMRequestsDelegate delegate;
+
+  public RMParser(RMRequestsDelegate delegate) {
+    this.delegate = delegate;
+  }
+
+  public Double getDAGProgress(String appId, String dagId) {
+    String dagIdx = parseDagIdIndex(dagId);
+    JSONObject dagProgress = (JSONObject) delegate.dagProgress(appId, dagIdx).get("dagProgress");
+    return (Double) (dagProgress.get("progress"));
+  }
+
+  public List<VertexProgress> getDAGVerticesProgress(String appId, String dagId, List<TezVertexId> vertices) {
+    String dagIdx = parseDagIdIndex(dagId);
+
+    Map<String, String> vertexIdToEntityMapping = new HashMap<String, String>();
+    StringBuilder builder = new StringBuilder();
+    if (vertices.size() > 0) {
+      for (TezVertexId vertexId : vertices) {
+        String[] parts = vertexId.entity.split("_");
+        String vertexIdx = parts[parts.length - 1];
+        builder.append(vertexIdx).append(",");
+
+        vertexIdToEntityMapping.put(vertexId.entity, vertexId.vertexName);
+      }
+      builder.setLength(builder.length() - 1); // remove last comma
+    }
+
+    String commaSeparatedVertices = builder.toString();
+
+    JSONArray vertexProgresses = (JSONArray) delegate.verticesProgress(
+        appId, dagIdx, commaSeparatedVertices).get("vertexProgresses");
+
+    List<VertexProgress> parsedVertexProgresses = new LinkedList<VertexProgress>();
+    for (Object vertex : vertexProgresses) {
+      JSONObject jsonObject = (JSONObject) vertex;
+
+      VertexProgress vertexProgressInfo = new VertexProgress();
+      vertexProgressInfo.id = (String) jsonObject.get("id");
+      vertexProgressInfo.name = vertexIdToEntityMapping.get(vertexProgressInfo.id);
+      vertexProgressInfo.progress = (Double) jsonObject.get("progress");
+
+      parsedVertexProgresses.add(vertexProgressInfo);
+    }
+    return parsedVertexProgresses;
+  }
+
+  public String parseDagIdIndex(String dagId) {
+    String[] dagIdParts = dagId.split("_");
+    return dagIdParts[dagIdParts.length - 1];
+  }
+
+  public static class VertexProgress {
+    public String id;
+    public String name;
+    public Double progress;
+  }
+}

+ 39 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/rm/RMParserFactory.java

@@ -0,0 +1,39 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.view.hive.resources.jobs.rm;
+
+import org.apache.ambari.view.ViewContext;
+
+public class RMParserFactory {
+
+  private ViewContext context;
+
+  public RMParserFactory(ViewContext context) {
+    this.context = context;
+  }
+
+  public RMParser getRMParser() {
+    RMRequestsDelegate delegate = new RMRequestsDelegateImpl(context, getRMUrl(context));
+    return new RMParser(delegate);
+  }
+
+  public static String getRMUrl(ViewContext context) {
+    return context.getProperties().get("yarn.resourcemanager.url");
+  }
+}

+ 31 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/rm/RMRequestsDelegate.java

@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.view.hive.resources.jobs.rm;
+
+import org.json.simple.JSONObject;
+
+public interface RMRequestsDelegate {
+  String dagProgressUrl(String appId, String dagIdx);
+
+  String verticesProgressUrl(String appId, String dagIdx, String vertices);
+
+  JSONObject dagProgress(String appId, String dagIdx);
+
+  JSONObject verticesProgress(String appId, String dagIdx, String commaSeparatedVertices);
+}

+ 99 - 0
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/jobs/rm/RMRequestsDelegateImpl.java

@@ -0,0 +1,99 @@
+/**
+ * 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.
+ */
+
+package org.apache.ambari.view.hive.resources.jobs.rm;
+
+import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.hive.utils.ServiceFormattedException;
+import org.apache.commons.io.IOUtils;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+public class RMRequestsDelegateImpl implements RMRequestsDelegate {
+  protected final static Logger LOG =
+      LoggerFactory.getLogger(RMRequestsDelegateImpl.class);
+  public static final String EMPTY_ENTITIES_JSON = "{ \"entities\" : [  ] }";
+
+  private ViewContext context;
+  private String rmUrl;
+
+  public RMRequestsDelegateImpl(ViewContext context, String rmUrl) {
+    this.context = context;
+    this.rmUrl = rmUrl;
+  }
+
+  @Override
+  public String dagProgressUrl(String appId, String dagIdx) {
+    return rmUrl + String.format("/proxy/%s/ws/v1/tez/dagProgress?dagID=%s", appId, dagIdx);
+  }
+
+  @Override
+  public String verticesProgressUrl(String appId, String dagIdx, String vertices) {
+    return rmUrl + String.format("/proxy/%s/ws/v1/tez/vertexProgresses?dagID=%s&vertexID=%s", appId, dagIdx, vertices);
+  }
+
+  @Override
+  public JSONObject dagProgress(String appId, String dagIdx) {
+    String url = dagProgressUrl(appId, dagIdx);
+    String response;
+    try {
+      InputStream responseInputStream = context.getURLStreamProvider().readFrom(url, "GET",
+          null, new HashMap<String, String>());
+      response = IOUtils.toString(responseInputStream);
+    } catch (IOException e) {
+      throw new ServiceFormattedException(
+          String.format("R010 DAG %s in app %s not found or ResourceManager is unreachable", dagIdx, appId));
+    }
+    return (JSONObject) JSONValue.parse(response);
+  }
+
+  @Override
+  public JSONObject verticesProgress(String appId, String dagIdx, String commaSeparatedVertices) {
+    String url = verticesProgressUrl(appId, dagIdx, commaSeparatedVertices);
+    String response;
+    try {
+      InputStream responseInputStream = context.getURLStreamProvider().readFrom(url, "GET",
+          null, new HashMap<String, String>());
+      response = IOUtils.toString(responseInputStream);
+    } catch (IOException e) {
+      throw new ServiceFormattedException(
+          String.format("R020 DAG %s in app %s not found or ResourceManager is unreachable", dagIdx, appId));
+    }
+    return (JSONObject) JSONValue.parse(response);
+  }
+
+  protected String readFromWithDefault(String url, String defaultResponse) {
+    String response;
+    try {
+      InputStream responseInputStream = context.getURLStreamProvider().readFrom(url, "GET",
+          null, new HashMap<String, String>());
+      response = IOUtils.toString(responseInputStream);
+    } catch (IOException e) {
+      LOG.error("Error while reading from RM", e);
+      response = defaultResponse;
+    }
+    return response;
+  }
+
+}

+ 3 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/resources/FileResourceResourceProvider.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.resources;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.*;
 import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
 import org.apache.ambari.view.hive.persistence.utils.OnlyOwnersFilteringStrategy;
@@ -26,6 +25,7 @@ import org.apache.ambari.view.hive.utils.SharedObjectsFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
 import java.util.Map;
@@ -60,6 +60,8 @@ public class FileResourceResourceProvider implements ResourceProvider<FileResour
 
   @Override
   public Set<FileResourceItem> getResources(ReadRequest readRequest) throws SystemException, NoSuchResourceException, UnsupportedPropertyException {
+    if (context == null)
+      return new HashSet();
     return new HashSet<FileResourceItem>(getResourceManager().readAll(
         new OnlyOwnersFilteringStrategy(this.context.getUsername())));
   }

+ 1 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/resources/FileResourceService.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.resources;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.ViewResourceHandler;
 import org.apache.ambari.view.hive.BaseService;
 import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
@@ -29,6 +28,7 @@ import org.json.simple.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.*;
 import javax.ws.rs.core.Context;

+ 3 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/savedQueries/SavedQueryResourceProvider.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.savedQueries;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.*;
 import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
 import org.apache.ambari.view.hive.persistence.utils.OnlyOwnersFilteringStrategy;
@@ -26,6 +25,7 @@ import org.apache.ambari.view.hive.utils.SharedObjectsFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
 import java.util.Map;
@@ -63,6 +63,8 @@ public class SavedQueryResourceProvider implements ResourceProvider<SavedQuery>
 
   @Override
   public Set<SavedQuery> getResources(ReadRequest readRequest) throws SystemException, NoSuchResourceException, UnsupportedPropertyException {
+    if (context == null)
+      return new HashSet();
     return new HashSet<SavedQuery>(getResourceManager().readAll(
         new OnlyOwnersFilteringStrategy(this.context.getUsername())));
   }

+ 1 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/savedQueries/SavedQueryService.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.savedQueries;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.ViewResourceHandler;
 import org.apache.ambari.view.hive.BaseService;
 import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
@@ -29,6 +28,7 @@ import org.json.simple.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.*;
 import javax.ws.rs.core.Context;

+ 3 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/udfs/UDFResourceProvider.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.udfs;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.*;
 import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
 import org.apache.ambari.view.hive.persistence.utils.OnlyOwnersFilteringStrategy;
@@ -26,6 +25,7 @@ import org.apache.ambari.view.hive.utils.SharedObjectsFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
 import java.util.Map;
@@ -61,6 +61,8 @@ public class UDFResourceProvider implements ResourceProvider<UDF> {
 
   @Override
   public Set<UDF> getResources(ReadRequest readRequest) throws SystemException, NoSuchResourceException, UnsupportedPropertyException {
+    if (context == null)
+      return new HashSet();
     return new HashSet<UDF>(getResourceManager().readAll(
         new OnlyOwnersFilteringStrategy(this.context.getUsername())));
   }

+ 1 - 1
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/resources/udfs/UDFService.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.hive.resources.udfs;
 
-import com.google.inject.Inject;
 import org.apache.ambari.view.ViewResourceHandler;
 import org.apache.ambari.view.hive.BaseService;
 import org.apache.ambari.view.hive.persistence.utils.ItemNotFound;
@@ -30,6 +29,7 @@ import org.json.simple.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.*;
 import javax.ws.rs.core.Context;

+ 16 - 4
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/utils/ServiceFormattedException.java

@@ -26,6 +26,8 @@ import org.slf4j.LoggerFactory;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.security.AccessControlException;
 import java.util.HashMap;
 
@@ -60,15 +62,25 @@ public class ServiceFormattedException extends WebApplicationException {
 
   protected static Response errorEntity(String message, Throwable e, int status, String header) {
     HashMap<String, Object> response = new HashMap<String, Object>();
-    response.put("message", message);
+
     String trace = null;
-    if (e != null)
-      trace = ExceptionUtils.getStackTrace(e);
+
+    response.put("message", message);
+    if (e != null) {
+      trace = e.toString() + "\n\n";
+      StringWriter sw = new StringWriter();
+      e.printStackTrace(new PrintWriter(sw));
+      trace += sw.toString();
+
+      if (message == null) {
+        response.put("message", "E090 " + e.getClass().getSimpleName());
+      }
+    }
     response.put("trace", trace);
     response.put("status", status);
 
     if(message != null) LOG.error(message);
-    if(trace != null) LOG.debug(trace);
+    if(trace != null) LOG.error(trace);
 
     Response.ResponseBuilder responseBuilder = Response.status(status).entity(new JSONObject(response)).type(MediaType.APPLICATION_JSON);
     if (header != null)

+ 17 - 3
contrib/views/hive/src/main/java/org/apache/ambari/view/hive/utils/SharedObjectsFactory.java

@@ -29,6 +29,8 @@ import org.apache.ambari.view.hive.resources.jobs.ConnectionController;
 import org.apache.ambari.view.hive.resources.jobs.OperationHandleControllerFactory;
 import org.apache.ambari.view.hive.resources.jobs.atsJobs.ATSParser;
 import org.apache.ambari.view.hive.resources.jobs.atsJobs.ATSParserFactory;
+import org.apache.ambari.view.hive.resources.jobs.rm.RMParser;
+import org.apache.ambari.view.hive.resources.jobs.rm.RMParserFactory;
 import org.apache.ambari.view.hive.resources.jobs.viewJobs.IJobControllerFactory;
 import org.apache.ambari.view.hive.resources.jobs.viewJobs.JobControllerFactory;
 import org.apache.ambari.view.hive.resources.savedQueries.SavedQueryResourceManager;
@@ -43,9 +45,10 @@ import java.util.Map;
  */
 public class SharedObjectsFactory implements IStorageFactory, IConnectionFactory {
   private ViewContext context;
-  private IConnectionFactory hiveConnectionFactory;
-  private IStorageFactory storageFactory;
-  private ATSParserFactory atsParserFactory;
+  private final IConnectionFactory hiveConnectionFactory;
+  private final IStorageFactory storageFactory;
+  private final ATSParserFactory atsParserFactory;
+  private final RMParserFactory rmParserFactory;
 
   private static final Map<Class, Map<String, Object>> localObjects = new HashMap<Class, Map<String, Object>>();
 
@@ -54,6 +57,7 @@ public class SharedObjectsFactory implements IStorageFactory, IConnectionFactory
     this.hiveConnectionFactory = new ConnectionFactory(context);
     this.storageFactory = new StorageFactory(context);
     this.atsParserFactory = new ATSParserFactory(context);
+    this.rmParserFactory = new RMParserFactory(context);
 
     synchronized (localObjects) {
       if (localObjects.size() == 0) {
@@ -64,6 +68,7 @@ public class SharedObjectsFactory implements IStorageFactory, IConnectionFactory
         localObjects.put(ATSParser.class, new HashMap<String, Object>());
         localObjects.put(SavedQueryResourceManager.class, new HashMap<String, Object>());
         localObjects.put(HdfsApi.class, new HashMap<String, Object>());
+        localObjects.put(RMParser.class, new HashMap<String, Object>());
       }
     }
   }
@@ -123,6 +128,13 @@ public class SharedObjectsFactory implements IStorageFactory, IConnectionFactory
     return (ATSParser) localObjects.get(ATSParser.class).get(getTagName());
   }
 
+  // =============================
+  public RMParser getRMParser() {
+    if (!localObjects.get(RMParser.class).containsKey(getTagName()))
+      localObjects.get(RMParser.class).put(getTagName(), rmParserFactory.getRMParser());
+    return (RMParser) localObjects.get(RMParser.class).get(getTagName());
+  }
+
   // =============================
   public HdfsApi getHdfsApi() {
     if (!localObjects.get(HdfsApi.class).containsKey(getTagName()))
@@ -135,6 +147,8 @@ public class SharedObjectsFactory implements IStorageFactory, IConnectionFactory
    * @return tag name
    */
   public String getTagName() {
+    if (context == null)
+      return "<null>";
     return String.format("%s:%s", context.getInstanceName(), context.getUsername());
   }
 

+ 3 - 1
contrib/views/hive/src/main/resources/ui/hive-web/.gitignore

@@ -32,4 +32,6 @@ tmp/
 
 public/
 
-_generators/
+_generators/
+
+coverage.json

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/adapters/application.js

@@ -21,7 +21,7 @@ import constants from 'hive/utils/constants';
 
 export default DS.RESTAdapter.extend({
   headers: {
-    "X-Requested-By": "ambari"
+    'X-Requested-By': 'ambari'
   },
 
   buildURL: function () {

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/components/alert-message-widget.js

@@ -24,7 +24,7 @@ export default Ember.Component.extend({
       this.sendAction('removeMessage', this.get('message'));
     },
 
-    toggleMessage: function() {
+    toggleMessage: function () {
       this.toggleProperty('message.isExpanded');
 
       if (!this.get('message.isExpanded')) {

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/components/collapsible-widget.js

@@ -30,7 +30,7 @@ export default Ember.Component.extend({
       }
     },
 
-    sendControlAction: function(action) {
+    sendControlAction: function (action) {
       this.set('controlAction', action);
       this.sendAction('controlAction', this.get('heading'), this.get('toggledParam'));
     }

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/components/column-filter-widget.js

@@ -27,7 +27,7 @@ export default Ember.Component.extend({
     }
   },
 
-  isSorted: (function() {
+  isSorted: (function () {
     var sortProperties = this.get('sortProperties');
 
     if (sortProperties) {

+ 34 - 11
contrib/views/hive/src/main/resources/ui/hive-web/app/components/date-range-widget.js

@@ -29,34 +29,57 @@ export default Ember.Component.extend({
     return moment(this.get('dateRange.to')).format('MM/DD/YYYY');
   }.property('dateRange.to'),
 
+  updateMinDate: function () {
+    if (this.get('rendered')) {
+      this.$('.toDate').datepicker("option", "minDate", new Date(this.get('dateRange.from')));
+    }
+  }.observes('dateRange.from'),
+
+  updateMaxDate: function () {
+    if (this.get('rendered')) {
+      this.$('.fromDate').datepicker("option", "maxDate", new Date(this.get('dateRange.to')));
+    }
+  }.observes('dateRange.to'),
+
   didInsertElement: function () {
     var self = this;
+    var dateRange = this.get('dateRange');
 
-    if (!this.get('dateRange.min') && !this.get('dateRange.max')) {
-      this.set('dateRange.max', new Date());
+    if (!dateRange.get('min') && !dateRange.get('max')) {
+      dateRange.set('max', new Date());
     }
 
-    if (!this.get('dateRange.from') && !this.get('dateRange.to')) {
-      this.set('dateRange.from', this.get('dateRange.min'));
-      this.set('dateRange.to', this.get('dateRange.max'));
+    if (!dateRange.get('from') && !dateRange.get('to')) {
+      dateRange.set('from', dateRange.get('min'));
+      dateRange.set('to', dateRange.get('max'));
     }
 
     this.$(".fromDate").datepicker({
-      defaultDate: this.get("dateRange.from"),
+      defaultDate: new Date(dateRange.get("from")),
+      maxDate: new Date(dateRange.get('to')),
+
       onSelect: function (selectedDate) {
         self.$(".toDate").datepicker("option", "minDate", selectedDate);
-        self.set('dateRange.from', new Date(selectedDate));
-        self.sendAction('rangeChanged', self.get('dateRange'));
+
+        dateRange.set('from', new Date(selectedDate).getTime());
+        self.sendAction('rangeChanged', dateRange);
       }
     });
 
     this.$(".toDate").datepicker({
-      defaultDate: this.get('dateRange.to'),
+      defaultDate: new Date(dateRange.get('to')),
+      minDate: new Date(dateRange.get('from')),
+
       onSelect: function (selectedDate) {
+        selectedDate += ' 23:59';
+
         self.$(".fromDate").datepicker("option", "maxDate", selectedDate);
-        self.set('dateRange.to', new Date(selectedDate + " 23:59"));
-        self.sendAction('rangeChanged', self.get('dateRange'));
+
+        dateRange.set('to', new Date(selectedDate).getTime());
+        self.sendAction('rangeChanged', dateRange);
       }
     });
+
+    this.set('rendered', true);
   }
 });

+ 6 - 0
contrib/views/hive/src/main/resources/ui/hive-web/app/components/expander-widget.js

@@ -21,6 +21,12 @@ import Ember from 'ember';
 export default Ember.Component.extend({
   tagName: 'expander',
 
+  didInsertElement: function () {
+    if (this.get('isExpanded')) {
+      this.$('.accordion-body').toggle();
+    }
+  },
+
   actions: {
     toggle: function () {
       this.toggleProperty('isExpanded');

+ 6 - 6
contrib/views/hive/src/main/resources/ui/hive-web/app/components/modal-widget.js

@@ -19,27 +19,27 @@
 import Ember from 'ember';
 
 export default Ember.Component.extend(Ember.I18n.TranslateableProperties, {
-  show: function() {
+  show: function () {
     this.$('.modal').modal().on('hidden.bs.modal', function () {
       this.sendAction('close');
     }.bind(this));
   }.on('didInsertElement'),
 
-  keyPress: function(e) {
-    Ember.run.debounce(this, function() {
+  keyPress: function (e) {
+    Ember.run.debounce(this, function () {
       if (e.which === 13) {
         this.send('ok');
       } else if (e.which === 27) {
         this.send('close');
       }
-    }, 200)
+    }, 200);
   },
 
-  setupEvents: function() {
+  setupEvents: function () {
     this.$(document).on('keyup', Ember.$.proxy(this.keyPress, this));
   }.on('didInsertElement'),
 
-  destroyEvents: function() {
+  destroyEvents: function () {
     this.$(document).off('keyup', Ember.$.proxy(this.keyPress, this));
   }.on('willDestroyElement'),
 

+ 4 - 4
contrib/views/hive/src/main/resources/ui/hive-web/app/components/notify-widget.js

@@ -21,12 +21,12 @@ import Ember from 'ember';
 
 export default Ember.Component.extend({
   tagName: 'notifications',
-  classNames: ['notifications-container'],
-  notifications : Ember.computed.alias('notify.notifications'),
+  classNames: [ 'notifications-container' ],
+  removeNotificationAction: 'removeNotification',
 
   actions: {
-    removeNotification: function(notification) {
-      this.notify.removeNotification(notification);
+    removeNotification: function (notification) {
+      this.sendAction('removeNotificationAction', notification);
     }
   }
 });

+ 27 - 15
contrib/views/hive/src/main/resources/ui/hive-web/app/components/number-range-widget.js

@@ -21,30 +21,42 @@ import Ember from 'ember';
 export default Ember.Component.extend({
   didInsertElement: function () {
     var self = this;
+    var numberRange = this.get('numberRange');
 
-    var slider;
-
-    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 (!numberRange.get('from') && !numberRange.get('to')) {
+      numberRange.set('from', numberRange.get('min'));
+      numberRange.set('to', numberRange.get('max'));
     }
 
-    slider = self.$( ".slider" ).slider({
+    this.$('.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') ],
+      min: numberRange.get('min'),
+      max: numberRange.get('max'),
+      units: numberRange.get('units'),
+      values: [numberRange.get('from'), numberRange.get('to')],
+
       slide: function (event, ui) {
-        self.set('numberRange.from', ui.values[0]);
-        self.set('numberRange.to', ui.values[1]);
+        numberRange.set('from', ui.values[0]);
+        numberRange.set('to', ui.values[1]);
       },
 
       change: function () {
-        self.sendAction('rangeChanged', self.get('numberRange'));
+        self.sendAction('rangeChanged', numberRange);
       }
     });
 
-    this.set('slider', slider);
-  }
+    this.set('rendered', true);
+  },
+
+  updateMin: function () {
+    if (this.get('rendered')) {
+      this.$('.slider').slider('values', 0, this.get('numberRange.from'));
+    }
+  }.observes('numberRange.from'),
+
+  updateMax: function () {
+    if (this.get('rendered')) {
+      this.$('.slider').slider('values', 1, this.get('numberRange.to'));
+    }
+  }.observes('numberRange.to')
 });

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/components/panel-widget.js

@@ -22,7 +22,7 @@ export default Ember.Component.extend(Ember.I18n.TranslateableProperties, {
   tagName: 'panel',
 
   actions: {
-    sendMenuItemAction: function(action) {
+    sendMenuItemAction: function (action) {
       this.set('menuItemAction', action);
       this.sendAction('menuItemAction');
     }

+ 3 - 2
contrib/views/hive/src/main/resources/ui/hive-web/app/components/popover-widget.js

@@ -20,8 +20,9 @@ import Ember from 'ember';
 
 export default Ember.Component.extend(Ember.I18n.TranslateableProperties, {
   tagName: 'popover',
-  attributeBindings: ['title', 'content:data-content'],
-  didInsertElement: function() {
+  attributeBindings: [ 'title', 'content:data-content' ],
+
+  didInsertElement: function () {
     this.$().popover({
       html: true,
       placement: 'left',

+ 7 - 22
contrib/views/hive/src/main/resources/ui/hive-web/app/components/progress-widget.js

@@ -12,34 +12,19 @@
  * 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
+ * See the License for the specific `language governing permissions and
  * limitations under the License.
  */
 
-// this component is a work in progress
-
 import Ember from 'ember';
 
 export default Ember.Component.extend({
-  formattedStages: Ember.ArrayProxy.create({ content: [] }),
-
-  formatStages: function() {
-    var formattedStages = this.get('formattedStages');
-    var stages = this.get('stages');
-    var currentStage;
+  tagName: 'progress-bar',
 
-    stages.forEach(function(stage) {
-      currentStage = formattedStages.findBy('name', stage.get('name'));
+  updateValue: function () {
+    var progress = this.get('value') || 0;
 
-      if(currentStage) {
-        currentStage.set('progress', 'width: %@%'.fmt(stage.get('value')))
-      } else {
-        formattedStages.pushObject(Ember.Object.create({
-          name: stage.get('name'),
-          className: stage.get('className'),
-          progress: 'width: %@%'.fmt(stage.get('value'))
-        }));
-      }
-    });
-  }.observes('stages.@each.value').on('init')
+    this.set('style', 'width: %@%'.fmt(progress));
+    this.set('percentage', '%@%'.fmt(progress));
+  }.observes('value').on('didInsertElement')
 });

+ 3 - 3
contrib/views/hive/src/main/resources/ui/hive-web/app/components/query-editor.js

@@ -87,14 +87,14 @@ export default Ember.Component.extend({
 
     editor = this.get('editor');
 
-    editor.on('cursorActivity', function() {
+    editor.on('cursorActivity', function () {
       self.set('highlightedText', editor.getSelections());
     });
 
     editor.setValue(this.get('query') || '');
 
     editor.on('change', function (instance) {
-      Ember.run(function() {
+      Ember.run(function () {
         self.set('query', instance.getValue());
       });
     });
@@ -110,7 +110,7 @@ export default Ember.Component.extend({
     this.tablesChanged();
   }.on('didInsertElement'),
 
-  updateValue: function() {
+  updateValue: function () {
     var query = this.get('query');
     var editor = this.get('editor');
 

+ 10 - 9
contrib/views/hive/src/main/resources/ui/hive-web/app/components/typeahead-widget.js

@@ -20,7 +20,7 @@ import Typeahead from 'ember-cli-selectize/components/ember-selectize';
 import Ember from 'ember';
 
 export default Typeahead.extend(Ember.I18n.TranslateableProperties, {
-  didInsertElement: function() {
+  didInsertElement: function () {
     this._super();
 
     if (!this.get('selection') && this.get('content.firstObject')) {
@@ -30,8 +30,7 @@ export default Typeahead.extend(Ember.I18n.TranslateableProperties, {
     this.selectize.on('dropdown_close', Ember.$.proxy(this.onClose, this));
   },
 
-  removeExcludedObserver: function() {
-    var self    = this;
+  removeExcludedObserver: function () {
     var options = this.get('content');
 
     if (!options) {
@@ -42,8 +41,7 @@ export default Typeahead.extend(Ember.I18n.TranslateableProperties, {
     }
   }.observes('excluded.@each.key').on('init'),
 
-  removeExcluded: function(shouldReturn) {
-    var self            = this;
+  removeExcluded: function (shouldReturn) {
     var excluded        = this.get('excluded') || [];
     var options         = this.get('options');
     var selection       = this.get('selection');
@@ -59,9 +57,12 @@ export default Typeahead.extend(Ember.I18n.TranslateableProperties, {
       objectToModify = Ember.copy(options);
     }
 
+    var valuePath = this.get('optionValuePath');
+    var selectionName = selection ? selection[valuePath] : selection;
+
     if (options) {
-      options.forEach(function(option, index) {
-        if (excluded.contains(option) && option !== selection) {
+      options.forEach(function (option) {
+        if (excluded.contains(option) && option.name !== selectionName) {
           objectsToRemove.push(option);
         } else if (!objectToModify.contains(option)) {
           objectsToAdd.push(option);
@@ -75,13 +76,13 @@ export default Typeahead.extend(Ember.I18n.TranslateableProperties, {
     return objectToModify;
   },
 
-  onClose: function() {
+  onClose: function () {
     if (!this.get('selection') && this.get('prevSelection')) {
       this.set('selection', this.get('prevSelection'));
     }
   },
 
-  _onItemAdd: function(value) {
+  _onItemAdd: function (value) {
     this._super(value);
 
     this.set('prevSelection', this.get('selection'));

+ 5 - 5
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/databases.js

@@ -209,24 +209,24 @@ export default Ember.ArrayController.extend({
   }.property(),
 
   actions: {
-    refreshDatabaseExplorer: function() {
+    refreshDatabaseExplorer: function () {
       var self = this;
       var selectedDatabase = this.get('selectedDatabase');
 
       this.store.unloadAll('database');
-      this.store.fetchAll('database').then(function() {
+      this.store.fetchAll('database').then(function () {
         var database = self.get('model').findBy('id', selectedDatabase.get('id'));
         self.set('selectedDatabase', database);
-      }).catch(function(response) {
+      }).catch(function (response) {
         self.notify.error(response.responseJSON.message, response.responseJSON.trace);
       });
     },
 
-    loadSampleData: function(tableName, database) {
+    loadSampleData: function (tableName, database) {
       var self = this;
       this.send('addQuery', Ember.I18n.t('titles.tableSample', { tableName: tableName }));
 
-      Ember.run.later(function() {
+      Ember.run.later(function () {
         var query = constants.sampleDataQuery.fmt(tableName);
 
         self.set('selectedDatabase', database);

+ 25 - 0
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/history.js

@@ -142,6 +142,31 @@ export default Ember.ArrayController.extend(FilterableMixin, {
       job.destroyRecord().then(function () {
         self.store.find(constants.namingConventions.job, id);
       });
+    },
+
+    clearFilters: function () {
+      var columns = this.get('columns');
+
+      if (columns) {
+        columns.forEach(function (column) {
+          var filterValue = column.get('filterValue');
+          var rangeFilter;
+
+          if (filterValue) {
+            if (typeof filterValue === 'string') {
+              column.set('filterValue');
+            } else {
+              rangeFilter = column.get('numberRange') || column.get('dateRange');
+
+              rangeFilter.set('from', rangeFilter.get('min'));
+              rangeFilter.set('to', rangeFilter.get('max'));
+            }
+          }
+        });
+      }
+
+      //call clear filters from Filterable mixin
+      this.clearFilters();
     }
   }
 });

+ 34 - 61
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index.js

@@ -29,7 +29,8 @@ export default Ember.Controller.extend({
            constants.namingConventions.jobExplain,
            constants.namingConventions.settings,
            constants.namingConventions.visualExplain,
-           constants.namingConventions.tezUI
+           constants.namingConventions.tezUI,
+           constants.namingConventions.jobProgress,
   ],
 
   openQueries: Ember.computed.alias('controllers.' + constants.namingConventions.openQueries),
@@ -41,19 +42,7 @@ export default Ember.Controller.extend({
   settings: Ember.computed.alias('controllers.' + constants.namingConventions.settings),
   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'),
-
+  jobProgress: Ember.computed.alias('controllers.' + constants.namingConventions.jobProgress),
 
   canExecute: function () {
     var isModelRunning = this.get('model.isRunning');
@@ -199,10 +188,26 @@ export default Ember.Controller.extend({
   },
 
   prependQuerySettings: function (query) {
-    var settings = this.get('settings').getSettingsString();
+    var validSettings = this.get('settings').getCurrentValidSettings();
+    var regex = new RegExp(utils.regexes.setSetting);
+    var existingSettings = query.match(regex);
+
+    //clear previously added settings
+    if (existingSettings) {
+      existingSettings.forEach(function (setting) {
+        query = query.replace(setting, '');
+      });
+    }
+
+    query = query.trim();
 
-    if (settings.length) {
-      return settings + "\n\n" + query;
+    //update with the current settings
+    if (validSettings) {
+      query = '\n' + query;
+
+      validSettings.forEach(function (setting) {
+        query = setting + '\n' + query;
+      });
     }
 
     return query;
@@ -221,16 +226,16 @@ export default Ember.Controller.extend({
     }
 
     queries = queryComponents.queryString.split(';');
-    queries = queries.map(function(s) {
+    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;
-    }
+    // if (queries.length > 1) {
+    //   return false;
+    // }
 
     queries = queries.map(function (query) {
       if (shouldExplain) {
@@ -335,7 +340,7 @@ export default Ember.Controller.extend({
     });
   }.observes('content'),
 
-  selectedDatabaseChanged: function() {
+  selectedDatabaseChanged: function () {
     this.set('content.dataBase', this.get('databases.selectedDatabase.name'));
   }.observes('databases.selectedDatabase'),
 
@@ -447,7 +452,7 @@ export default Ember.Controller.extend({
       this.saveToHDFS();
     },
 
-    downloadAsCSV: function() {
+    downloadAsCSV: function () {
       var self = this,
           defer = Ember.RSVP.defer();
 
@@ -467,7 +472,7 @@ export default Ember.Controller.extend({
     },
 
     insertUdf: function (item) {
-      var query = this.get('openQueries').getQueryForModel(this.get('model'));
+      var query = this.get('openQueries.currentQuery');
 
       var queryString = query.get('fileContent');
 
@@ -523,7 +528,8 @@ export default Ember.Controller.extend({
       //case 4. Update an existing query tab. -> route doesn't change
 
       var self = this,
-          defer = Ember.RSVP.defer();
+          defer = Ember.RSVP.defer(),
+          currentQuery = this.get('openQueries.currentQuery');
 
       this.set('model.dataBase', this.get('databases.selectedDatabase.name'));
 
@@ -536,6 +542,8 @@ export default Ember.Controller.extend({
       });
 
       defer.promise.then(function (result) {
+        currentQuery.set('fileContent', self.prependQuerySettings(currentQuery.get('fileContent')));
+
         if (result.get('overwrite')) {
           self.get('openQueries').save(self.get('content'), null, true, result.get('text'));
         } else {
@@ -578,41 +586,6 @@ export default Ember.Controller.extend({
       }, function (err) {
         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') {
-        this.set('tezUI.showOverlay', false);
-      } else if (this.get('settings.showOverlay') && targetController !== 'settings') {
-        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'));
-      }
-
-      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');
-      }
     }
   }
-});
+});

+ 2 - 3
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/explain.js

@@ -80,7 +80,6 @@ export default Ember.ObjectController.extend({
         currentNode,
         currentNodeWhitespace,
         previousNode,
-        formattedExplain = [],
         getLeadingWhitespacesCount = function (str) {
           return str.replace(utils.regexes.whitespaces, '$1').length;
         };
@@ -96,7 +95,7 @@ export default Ember.ObjectController.extend({
                       text: str,
                       parentNode: null,
                       contents: []
-                    }
+                    };
                   });
 
     for (var i = 0; i < explainSet.length; i++) {
@@ -130,4 +129,4 @@ export default Ember.ObjectController.extend({
 
     this.set('formattedExplain', formatted);
   }
-});
+});

+ 9 - 4
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/logs.js

@@ -21,8 +21,10 @@ import constants from 'hive/utils/constants';
 import utils from 'hive/utils/functions';
 
 export default Ember.ObjectController.extend({
-  needs: [ constants.namingConventions.loadedFiles ],
+  needs: [ constants.namingConventions.queryTabs,
+           constants.namingConventions.loadedFiles ],
 
+  queryTabs: Ember.computed.alias('controllers.' + constants.namingConventions.queryTabs),
   files: Ember.computed.alias('controllers.' + constants.namingConventions.loadedFiles),
 
   reloadJobLogs: function (job) {
@@ -68,19 +70,22 @@ export default Ember.ObjectController.extend({
 
       this.reloadJobLogs(job).then(function () {
         var stillRunning = self.isJobRunning(job);
+        var currentContentId = self.get('content.id');
+        var currentActiveTab = self.get('queryTabs.activeTab.name');
 
         //if the current model is the same with the one displayed, continue reloading job
-        if (stillRunning && job.get('id') === self.get('content.id')) {
+        if (stillRunning && job.get('id') === currentContentId) {
           self.listenForUpdates(job);
         } else if (!stillRunning) {
           job.set('isRunning', undefined);
 
-          if (job.get('id') === self.get('content.id')) {
+          if (job.get('id') === currentContentId &&
+              currentActiveTab === constants.namingConventions.index) {
             self.transitionToRoute(constants.namingConventions.subroutes.jobResults);
           }
         }
       });
-    }, 2000);
+    }, 10000);
   },
 
   isJobRunning: function (job) {

+ 3 - 3
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/index/history-query/results.js

@@ -24,19 +24,19 @@ export default Ember.ObjectController.extend({
   cachedResults: [],
   formattedResults: [],
 
-  processResults: function() {
+  processResults: function () {
     var results = this.get('results');
 
     if (!results || !results.schema || !results.rows) {
       return;
     }
 
-    var schema = results.schema.map(function(column) {
+    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 });

+ 92 - 0
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/job-progress.js

@@ -0,0 +1,92 @@
+/**
+ * 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.Controller.extend({
+  needs: [ constants.namingConventions.index ],
+
+  index: Ember.computed.alias('controllers.' + constants.namingConventions.index),
+
+  listenForProgress: function () {
+    var self = this;
+    var url = this.container.lookup('adapter:application').buildURL();
+    var stages = [];
+    var job = this.get('index.model');
+
+    var reloadProgress = function () {
+      Ember.run.later(function () {
+        Ember.$.getJSON(url).then(function (data) {
+          var total = 0;
+          var length = Object.keys(data.vertexProgresses).length;
+
+          if (!self.get('stages.length')) {
+            data.vertexProgresses.forEach(function (vertexProgress) {
+              var progress = vertexProgress.progress * 100;
+
+              stages.pushObject(Ember.Object.create({
+                name: vertexProgress.name,
+                value: progress
+              }));
+
+              total += progress;
+            });
+
+            self.set('stages', stages);
+          } else {
+            data.vertexProgresses.forEach(function (vertexProgress) {
+              var progress = vertexProgress.progress * 100;
+
+              self.get('stages').findBy('name', vertexProgress.name).set('value', progress);
+
+              total += progress;
+            });
+          }
+
+          total /= length;
+
+          self.set('totalProgress', total);
+
+          if (job.get('isRunning')) {
+            reloadProgress();
+          }
+
+        }, function (err) {
+          reloadProgress();
+        });
+      }, 1000);
+    };
+
+    //reset stages
+    this.set('stages', []);
+    this.set('totalProgress', 0);
+
+    if (!job.get('applicationId')) {
+      return;
+    }
+
+    url += '/' + constants.namingConventions.jobs + '/' + job.get('id') + '/progress';
+
+    reloadProgress();
+  }.observes('index.model', 'index.model.applicationId'),
+
+  displayProgress: function () {
+    return this.get('index.model.constructor.typeKey') === constants.namingConventions.job;
+  }.property('index.model')
+});

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/job.js

@@ -26,7 +26,7 @@ export default Ember.ObjectController.extend({
   files: Ember.computed.alias('controllers.' + constants.namingConventions.loadedFiles),
 
   canStop: function () {
-    return utils.insensitiveCompare(this.get('status'), constants.statuses.running, constants.statuses.initialized, constants.statuses.pending)
+    return utils.insensitiveCompare(this.get('status'), constants.statuses.running, constants.statuses.initialized, constants.statuses.pending);
   }.property('status'),
 
   actions: {

+ 7 - 3
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/messages.js

@@ -19,15 +19,19 @@ import Ember from 'ember';
 
 export default Ember.Controller.extend({
   messages: Ember.computed.alias('notify.messages'),
-  count: Ember.computed.alias('messages.length'),
+  count: Ember.computed.alias('notify.unseenMessages.length'),
 
   actions: {
-    removeMessage: function(message) {
+    removeMessage: function (message) {
       this.notify.removeMessage(message);
     },
 
-    removeAllMessages: function() {
+    removeAllMessages: function () {
       this.notify.removeAllMessages();
+    },
+
+    markMessagesAsSeen: function () {
+      this.notify.markMessagesAsSeen();
     }
   }
 });

+ 2 - 4
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/open-queries.js

@@ -200,7 +200,7 @@ export default Ember.ArrayController.extend({
 
       tab.set('isDirty', false);
 
-      var content = self.get('index').prependQuerySettings(query.get('fileContent'));
+      var content = query.get('fileContent');
       //update query tab path with saved model id if its a new record
       if (wasNew) {
         self.get('settings').updateSettingsId(originalId, updatedModel.get('id'));
@@ -274,7 +274,7 @@ export default Ember.ArrayController.extend({
          hasSettings;
   },
 
-  isDirty: function(model) {
+  isDirty: function (model) {
     var query = this.getQueryForModel(model);
 
     if (model.get('isNew') && !query.get('fileContent')) {
@@ -286,8 +286,6 @@ export default Ember.ArrayController.extend({
     }
 
     return !!(!model.get('queryId') && model.get('isDirty'));
-
-
   },
 
   updatedDeletedQueryTab: function (model) {

+ 17 - 3
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/queries.js

@@ -31,9 +31,6 @@ export default Ember.ArrayController.extend(FilterableMixin, {
   sortProperties: [],
 
   init: function () {
-    var oneYearAgo = new Date();
-    oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
-
     this._super();
 
     this.set('columns', Ember.ArrayProxy.create({ content: Ember.A([
@@ -106,6 +103,23 @@ export default Ember.ArrayController.extend(FilterableMixin, {
         this.set('sortAscending', true);
         this.set('sortProperties', [ property ]);
       }
+    },
+
+    clearFilters: function () {
+      var columns = this.get('columns');
+
+      if (columns) {
+        columns.forEach(function (column) {
+          var filterValue = column.get('filterValue');
+
+          if (filterValue && typeof filterValue === 'string') {
+            column.set('filterValue');
+          }
+        });
+      }
+
+      //call clear filters from Filterable mixin
+      this.clearFilters();
     }
   }
 });

+ 80 - 37
contrib/views/hive/src/main/resources/ui/hive-web/app/components/query-tabs.js → contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/query-tabs.js

@@ -15,98 +15,141 @@
  * 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.Controller.extend({
+  needs: [ constants.namingConventions.index],
+
+  index: Ember.computed.alias('controllers' + constants.namingConventions.index),
 
-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'
+      id: 'query-icon',
+      action: 'setDefaultActive',
+      name: constants.namingConventions.index,
+      tooltip: Ember.I18n.t('tooltips.query')
     }),
     Ember.Object.create({
       iconClass: 'fa-gear',
+      id: 'settings-icon',
       action: 'toggleOverlay',
       template: 'settings',
       outlet: 'overlay',
-      into: 'open-queries'
+      into: 'open-queries',
+      tooltip: Ember.I18n.t('tooltips.settings')
     }),
     Ember.Object.create({
-      iconClass: 'fa-bar-chart',
+      iconClass: 'fa-link',
+      id: 'visual-explain-icon',
       action: 'toggleOverlay',
       template: 'visual-explain',
       outlet: 'overlay',
-      into: 'index'
+      into: 'index',
+      onTabOpen: 'onTabOpen',
+      tooltip: Ember.I18n.t('tooltips.visualExplain')
     }),
     Ember.Object.create({
       iconClass: 'text-icon',
+      id: 'tez-icon',
       text: 'TEZ',
       action: 'toggleOverlay',
       template: 'tez-ui',
       outlet: 'overlay',
-      into: 'index'
+      into: 'index',
+      tooltip: Ember.I18n.t('tooltips.tez')
     }),
     Ember.Object.create({
       iconClass: 'fa-envelope',
+      id: 'notifications-icon',
       action: 'toggleOverlay',
       template: 'messages',
       outlet: 'overlay',
-      into: 'open-queries',
-      badgeProperty: 'count'
+      into: 'index',
+      badgeProperty: 'count',
+      onTabOpen: 'markMessagesAsSeen',
+      tooltip: Ember.I18n.t('tooltips.notifications')
     })
   ],
 
-  setDefaultTab: function() {
-    var defaultTab = this.get('tabs.firstObject');
-
-    defaultTab.set('active', true);
-    this.set('default', defaultTab);
-    this.set('active', defaultTab);
-  }.on('init'),
+  init: function() {
+    this.setupControllers();
+    this.setDefaultTab();
+    this.setupTabsBadges();
+  },
 
-  setupTabsBadges: function() {
+  setupControllers: 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);
+    tabs.map(function (tab) {
+      var controller;
 
-        Ember.oneWay(tab, 'badge', 'controller.count');
+      if (tab.get('template')) {
+        controller = self.container.lookup('controller:' + tab.get('template'));
+        tab.set('controller', controller);
       }
     });
-  }.on('init'),
+  },
+
+  setDefaultTab: function () {
+    var defaultTab = this.get('tabs.firstObject');
 
-  closeActiveOverlay: function() {
-    this.sendAction('closeOverlayAction', this.get('active'));
+    defaultTab.set('active', true);
+
+    this.set('default', defaultTab);
+    this.set('activeTab', defaultTab);
+  },
+
+  setupTabsBadges: function () {
+    var tabs = this.get('tabs').filterProperty('badgeProperty');
+
+    tabs.map(function (tab) {
+        Ember.oneWay(tab, 'badge', 'controller.' + tab.badgeProperty);
+    });
   },
 
-  openOverlay: function(tab) {
+  closeActiveOverlay: function () {
+    this.send('closeOverlay', this.get('activeTab'));
+  },
+
+  onTabOpen: function (tab) {
+    if (!tab.onTabOpen) {
+      return;
+    }
+
+    var controller = this.container.lookup('controller:' + tab.template);
+    controller.send(tab.onTabOpen, controller);
+  },
+
+  openOverlay: function (tab) {
     this.closeActiveOverlay();
-    this.set('active.active', false);
+    this.set('activeTab.active', false);
     tab.set('active', true);
-    this.set('active', tab);
-    this.sendAction('openOverlayAction', tab);
+    this.set('activeTab', tab);
+
+    this.onTabOpen(tab);
+    this.send('openOverlay', tab);
   },
 
-  setDefaultActive: function() {
-    var active     = this.get('active');
+  setDefaultActive: function () {
+    var activeTab = this.get('activeTab');
     var defaultTab = this.get('default');
 
-    if (active !== defaultTab) {
+    if (activeTab !== defaultTab) {
       this.closeActiveOverlay();
       defaultTab.set('active', true);
-      active.set('active', false);
-      this.set('active', defaultTab);
+      activeTab.set('active', false);
+      this.set('activeTab', defaultTab);
     }
   },
 
   actions: {
-    toggleOverlay: function(tab) {
+    toggleOverlay: function (tab) {
       if (tab !== this.get('default') && tab.get('active')) {
         this.setDefaultActive();
       } else {
@@ -114,7 +157,7 @@ export default Ember.Component.extend({
       }
     },
 
-    setDefaultActive: function() {
+    setDefaultActive: function () {
       this.setDefaultActive();
     }
   }

+ 69 - 75
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/settings.js

@@ -18,6 +18,7 @@
 
 import Ember from 'ember';
 import constants from 'hive/utils/constants';
+import utils from 'hive/utils/functions';
 
 export default Ember.ArrayController.extend({
   needs: [
@@ -34,11 +35,11 @@ export default Ember.ArrayController.extend({
 
   predefinedSettings: constants.hiveParameters,
 
-  selectedSettings: function() {
+  selectedSettings: function () {
     var predefined = this.get('predefinedSettings');
     var current = this.get('currentSettings.settings');
 
-    return predefined.filter(function(setting) {
+    return predefined.filter(function (setting) {
       return current.findBy('key.name', setting.name);
     });
   }.property('currentSettings.settings.@each.key'),
@@ -47,7 +48,7 @@ export default Ember.ArrayController.extend({
     var currentId = this.get('index.model.id');
     var targetSettings = this.findBy('id', currentId);
 
-   if (!targetSettings) {
+   if (!targetSettings && currentId) {
       targetSettings = this.pushObject(Ember.Object.create({
         id: currentId,
         settings: []
@@ -55,64 +56,54 @@ export default Ember.ArrayController.extend({
     }
 
     return targetSettings;
-  }.property('index.model.id'),
+  }.property('openQueries.currentQuery'),
 
   updateSettingsId: function (oldId, newId) {
     this.filterBy('id', oldId).setEach('id', newId);
   },
 
-  getSettingsString: function () {
-    var currentId = this.get('index.model.id');
-
-    var querySettings = this.findBy('id', currentId);
+  getCurrentValidSettings: function () {
+    var currentSettings = this.get('currentSettings');
+    var validSettings = [];
 
-    if (!querySettings) {
-      return "";
+    if (!currentSettings) {
+      return '';
     }
 
-    var settings = querySettings.get('settings').map(function (setting) {
-      return 'set %@ = %@;'.fmt(setting.get('key.name'), setting.get('value'));
+    currentSettings.get('settings').map(function (setting) {
+      if (setting.get('valid')) {
+        validSettings.pushObject('set %@ = %@;'.fmt(setting.get('key.name'), setting.get('value')));
+      }
     });
 
-    return settings.join("\n");
+    return validSettings;
   },
 
   hasSettings: function (id) {
-    id = id ? id : this.get('index.model.id');
-    var settings = this.findBy('id', id);
+    var settings;
+    var settingId = id ? id : this.get('index.model.id');
+
+    settings = this.findBy('id', settingId);
 
     return settings && settings.get('settings.length');
   },
 
   parseQuerySettings: function () {
-    var id = this.get('index.model.id');
     var query = this.get('openQueries.currentQuery');
     var content = query.get('fileContent');
     var self = this;
+    var regex = new RegExp(utils.regexes.setSetting);
+    var settings = content.match(regex) || [];
+    var targetSettings = this.findBy('id', this.get('index.model.id'));
 
-    var regex = new RegExp(/^set\s+[\w-.]+(\s+|\s?)=(\s+|\s?)[\w-.]+(\s+|\s?);/gim);
-    var settings = content.match(regex);
-
-    if (!settings) {
+    if (!query || !targetSettings) {
       return;
     }
 
-    var Setting = Ember.Object.extend({
-      key: Ember.Object.create(),
-      valid: true,
-      selection: Ember.Object.create(),
-      value: Ember.computed.alias('selection.value')
-    });
-
-    query.set('fileContent', content.replace(regex, '').trim());
     settings = settings.map(function (setting) {
-      var KV = setting.split('=');
-      var name = KV[0].replace('set', '').trim();
-      var value = KV[1].replace(';', '').trim();
-
-      var newSetting = Setting.create({});
-      newSetting.set('key.name', name);
-      newSetting.set('selection.value', value);
+      var KeyValue = setting.split('=');
+      var name     = KeyValue[0].replace('set', '').trim();
+      var value    = KeyValue[1].replace(';', '').trim();
 
       if (!self.get('predefinedSettings').findBy('name', name)) {
         self.get('predefinedSettings').pushObject({
@@ -120,37 +111,32 @@ export default Ember.ArrayController.extend({
         });
       }
 
-      return newSetting;
-    });
+      var settingObj = Ember.Object.createWithMixins({
+        key: Ember.Object.create({ name: 'nam' }),
+        selection : Ember.Object.create({ value: 'val'}),
 
-    this.setSettingForQuery(id, settings);
-  }.observes('openQueries.currentQuery', 'openQueries.tabUpdated'),
+        value: Ember.computed.alias('selection.value'),
+        valid: true
+      });
 
-  setSettingForQuery: function (id, settings) {
-    var querySettings = this.findBy('id', id);
+      settingObj.set('key.name', name);
+      settingObj.set('selection.value', value);
 
-    if (!querySettings) {
-      this.pushObject(Ember.Object.create({
-        id: id,
-        settings: settings
-      }));
-    } else {
-      querySettings.setProperties({
-        'settings': settings
-      });
-    }
-  },
+      return settingObj;
+    });
+
+    targetSettings.set('settings', settings);
+  }.observes('openQueries.currentQuery', 'openQueries.currentQuery.fileContent', 'openQueries.tabUpdated'),
 
-  validate: function() {
+  validate: function () {
     var settings = this.get('currentSettings.settings') || [];
     var predefinedSettings = this.get('predefinedSettings');
 
-    settings.forEach(function(setting) {
-      var predefined = predefinedSettings.filterProperty('name', setting.get('key.name'));
-      if (!predefined.length) {
+    settings.forEach(function (setting) {
+      var predefined = predefinedSettings.findBy('name', setting.get('key.name'));
+
+      if (!predefined) {
         return;
-      } else {
-        predefined = predefined[0];
       }
 
       if (predefined.values && predefined.values.contains(setting.get('value'))) {
@@ -172,14 +158,14 @@ export default Ember.ArrayController.extend({
     });
   }.observes('currentSettings.[]', 'currentSettings.settings.[]', 'currentSettings.settings.@each.value', 'currentSettings.settings.@each.key'),
 
-  currentSettingsAreValid: function() {
+  currentSettingsAreValid: function () {
     var currentSettings = this.get('currentSettings.settings');
     var invalid = currentSettings.filterProperty('valid', false);
 
     return invalid.length ? false : true;
   }.property('currentSettings.settings.@each.value', 'currentSettings.settings.@each.key'),
 
-  loadSessionStatus: function() {
+  loadSessionStatus: function () {
     var model         = this.get('index.model');
     var sessionActive = this.get('sessionActive');
     var sessionTag    = this.get('sessionTag');
@@ -188,10 +174,10 @@ export default Ember.ArrayController.extend({
 
     if (sessionTag && sessionActive === undefined) {
       adapter.ajax(url, 'GET')
-        .then(function(response) {
+        .then(function (response) {
           model.set('sessionActive', response.session.actual);
         })
-        .catch(function() {
+        .catch(function () {
           model.set('sessionActive', false);
         });
     }
@@ -199,20 +185,25 @@ export default Ember.ArrayController.extend({
 
   actions: {
     add: function () {
-      var currentId = this.get('index.model.id'),
-          querySettings = this.findBy('id', currentId);
-
-      var Setting = Ember.Object.extend({
+      var setting = Ember.Object.createWithMixins({
         valid: true,
         selection: Ember.Object.create(),
         value: Ember.computed.alias('selection.value')
       });
 
-      querySettings.get('settings').pushObject(Setting.create({}));
+      this.get('currentSettings.settings').pushObject(setting);
     },
 
     remove: function (setting) {
-      this.findBy('id', this.get('index.model.id')).settings.removeObject(setting);
+      var currentQuery = this.get('openQueries.currentQuery');
+      var currentQueryContent = currentQuery.get('fileContent');
+      var keyValue = 'set %@ = %@;\n'.fmt(setting.get('key.name'), setting.get('value'));
+
+      this.get('currentSettings.settings').removeObject(setting);
+
+      if (currentQueryContent.indexOf(keyValue) > -1) {
+        currentQuery.set('fileContent', currentQueryContent.replace(keyValue, ''));
+      }
     },
 
     addKey: function (param) {
@@ -223,14 +214,17 @@ 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);
+    removeAll: function () {
+      var currentQuery = this.get('openQueries.currentQuery'),
+          currentQueryContent = currentQuery.get('fileContent'),
+          regex = new RegExp(utils.regexes.setSetting),
+          settings = currentQueryContent.match(regex);
 
-      querySettings.set('settings', []);
+      currentQuery.set('fileContent', currentQueryContent.replace(settings, ''));
+      this.get('currentSettings').set('settings', []);
     },
 
-    invalidateSession: function() {
+    invalidateSession: function () {
       var self       = this;
       var sessionTag = this.get('sessionTag');
       var adapter    = this.container.lookup('adapter:application');
@@ -238,7 +232,7 @@ export default Ember.ArrayController.extend({
       var model = this.get('index.model');
 
       // @TODO: Split this into then/catch once the BE is fixed
-      adapter.ajax(url, 'DELETE').catch(function(response) {
+      adapter.ajax(url, 'DELETE').catch(function (response) {
         if ([200, 404].contains(response.status)) {
           model.set('sessionActive', false);
           self.notify.success('alerts.success.sessions.deleted');
@@ -248,4 +242,4 @@ export default Ember.ArrayController.extend({
       });
     }
   }
-});
+});

+ 9 - 10
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/tez-ui.js

@@ -31,7 +31,7 @@ export default Ember.Controller.extend({
 
   isTezViewAvailable: Ember.computed.bool('tezViewURL'),
 
-  dagId: function() {
+  dagId: function () {
     if (this.get('isTezViewAvailable')) {
       return this.get('index.model.dagId');
     }
@@ -39,7 +39,7 @@ export default Ember.Controller.extend({
     return false;
   }.property('index.model.dagId', 'isTezViewAvailable'),
 
-  dagURL: function() {
+  dagURL: function () {
     if (this.get('dagId')) {
       return "%@%@%@".fmt(this.get('tezViewURL'), this.get('tezDagPath'), this.get('dagId'));
     }
@@ -47,27 +47,27 @@ export default Ember.Controller.extend({
     return false;
   }.property('dagId'),
 
-  getTezView: function() {
+  getTezView: function () {
     if (this.get('isTezViewAvailable')) {
       return;
     }
 
     var self = this;
     Ember.$.getJSON(this.get('tezApiURL'))
-      .then(function(response) {
+      .then(function (response) {
         self.getTezViewInstance(response);
       })
-      .fail(function(response) {
+      .fail(function (response) {
         self.setTezViewError(response);
       });
   }.on('init'),
 
-  getTezViewInstance: function(data) {
+  getTezViewInstance: function (data) {
     var self = this;
     var url = data.versions[0].href;
 
     Ember.$.getJSON(url)
-      .then(function(response) {
+      .then(function (response) {
         if (!response.instances.length) {
           self.setTezViewError(response);
           return;
@@ -80,7 +80,7 @@ export default Ember.Controller.extend({
       });
   },
 
-  setTezViewURL: function(instance) {
+  setTezViewURL: function (instance) {
     var url = "%@/%@/%@".fmt(
       this.get('tezURLPrefix'),
       instance.version,
@@ -90,7 +90,7 @@ export default Ember.Controller.extend({
     this.set('tezViewURL', url);
   },
 
-  setTezViewError: function(data) {
+  setTezViewError: function (data) {
     // status: 404 => Tev View isn't deployed
     if (data.status && data.status === 404) {
       this.set('error', 'tez.errors.not.deployed');
@@ -100,7 +100,6 @@ export default Ember.Controller.extend({
     // no instance created
     if (data.instances && !data.instances.length) {
       this.set('error', 'tez.errors.no.instance');
-      return;
     }
   }
 });

+ 17 - 0
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/udfs.js

@@ -64,6 +64,23 @@ export default Ember.ArrayController.extend(FilterableMixin, {
 
     add: function () {
       this.store.createRecord(constants.namingConventions.udf);
+    },
+
+    clearFilters: function () {
+      var columns = this.get('columns');
+
+      if (columns) {
+        columns.forEach(function (column) {
+          var filterValue = column.get('filterValue');
+
+          if (filterValue && typeof filterValue === 'string') {
+            column.set('filterValue');
+          }
+        });
+      }
+
+      //call clear filters from Filterable mixin
+      this.clearFilters();
     }
   }
 });

+ 26 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/controllers/visual-explain.js

@@ -17,6 +17,31 @@
  */
 
 import Ember from 'ember';
+import constants from 'hive/utils/constants';
 
-export default Ember.ObjectController.extend({
+export default Ember.Controller.extend({
+  needs: [ constants.namingConventions.index,
+           constants.namingConventions.jobProgress ],
+
+  index: Ember.computed.alias('controllers.' + constants.namingConventions.index),
+  jobProgress: Ember.computed.alias('controllers.' + constants.namingConventions.jobProgress),
+
+  updateProgress: function () {
+    this.set('verticesProgress', this.get('jobProgress.stages'));
+  }.observes('jobProgress.stages', 'jobProgress.stages.@each.value'),
+
+  actions: {
+    onTabOpen: function () {
+      var self = this;
+
+      this.get('index')._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('json', json);
+        }
+      }, function (err) {
+        self.notify.error(err.responseJSON.message, err.responseJSON.trace);
+      });
+    }
+  }
 });

+ 2 - 2
contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/all-uppercase.js

@@ -18,8 +18,8 @@
 
 import Ember from 'ember';
 
-export function allUppercase(input) {
+export function allUppercase (input) {
   return input ? input.toUpperCase() : input;
-};
+}
 
 export default Ember.Handlebars.makeBoundHelper(allUppercase);

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/code-helper.js

@@ -18,7 +18,7 @@
 
 import Ember from 'ember';
 
-export function code(text) {
+export function code (text) {
   text = Ember.Handlebars.Utils.escapeExpression(text);
   text = text.replace(/(\r\n|\n|\r)/gm, '<br>');
 

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/date-binding.js

@@ -20,7 +20,7 @@
 
 import Ember from 'ember';
 
-export function pathBinding(data, key) {
+export function pathBinding (data, key) {
   return moment(data.get(key)).fromNow();
 }
 

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/log-helper.js

@@ -18,7 +18,7 @@
 
 import Ember from 'ember';
 
-export function log(text) {
+export function log (text) {
   text = Ember.Handlebars.Utils.escapeExpression(text);
   text = text.replace(/(\r\n|\n|\r)/gm, '<br>');
 

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/path-binding.js

@@ -18,7 +18,7 @@
 
 import Ember from 'ember';
 
-export function pathBinding(data, key) {
+export function pathBinding (data, key) {
   if (!data || !key) {
     return;
   }

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/preformatted-string.js

@@ -17,7 +17,7 @@
 */
 import Ember from 'ember';
 
-export function preformattedString(string) {
+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

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/helpers/tb-helper.js

@@ -18,7 +18,7 @@
 
 import Ember from 'ember';
 
-export function tb(key, data) {
+export function tb (key, data) {
   var path = data.get ? data.get(key) : data[key];
 
   if (!path && key) {

+ 19 - 3
contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/i18n.js

@@ -22,7 +22,7 @@ var TRANSLATIONS;
 
 export default {
   name: 'i18n',
-  initialize: function() {
+  initialize: function () {
     Ember.ENV.I18N_COMPILE_WITHOUT_HANDLEBARS = true;
     Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN = false;
     Ember.I18n.translations = TRANSLATIONS;
@@ -33,8 +33,14 @@ export default {
 TRANSLATIONS = {
   tooltips: {
     refresh: 'Refresh database',
-    loadSample: 'Load sample data'
+    loadSample: 'Load sample data',
+    query: 'Query',
+    settings: 'Settings',
+    visualExplain: 'Visual Explain',
+    tez: 'Tez',
+    notifications: 'Notifications'
   },
+
   alerts: {
     errors: {
       save: {
@@ -58,6 +64,7 @@ TRANSLATIONS = {
       }
     }
   },
+
   modals: {
     delete: {
       heading: 'Confirm deletion',
@@ -76,6 +83,7 @@ TRANSLATIONS = {
       csv: 'Download results as CSV'
     }
   },
+
   titles: {
     database: 'Database Explorer',
     explorer: 'Databases',
@@ -87,11 +95,13 @@ TRANSLATIONS = {
       process: 'Query Process Results',
       parameters: 'Parameters',
       visualExplain: 'Visual Explain',
-      tez: 'TEZ'
+      tez: 'TEZ',
+      messages: 'Messages'
     },
     download: 'Save results...',
     tableSample: '{{tableName}} sample'
   },
+
   placeholders: {
     search: {
       tables: 'Search tables...',
@@ -119,6 +129,7 @@ TRANSLATIONS = {
       value: '1'
     }
   },
+
   menus: {
     query: 'Query',
     savedQueries: 'Saved Queries',
@@ -128,6 +139,7 @@ TRANSLATIONS = {
     results: 'Results',
     explain: 'Explain'
   },
+
   columns: {
     id: 'id',
     shortQuery: 'preview',
@@ -142,6 +154,7 @@ TRANSLATIONS = {
     expand: '',
     actions: ''
   },
+
   buttons: {
     addItem: 'Add new item...',
     insert: 'Insert',
@@ -168,10 +181,12 @@ TRANSLATIONS = {
     saveCsv: 'Download as CSV',
     runOnTez: 'Run on Tez'
   },
+
   labels: {
     noTablesMatch: 'No tables match',
     table: 'Table '
   },
+
   popover: {
     visualExplain: {
       statistics: "Statistics"
@@ -186,6 +201,7 @@ TRANSLATIONS = {
     },
     add: 'Add'
   },
+
   tez: {
     errors: {
       'not.deployed': "Tez View isn't deployed.",

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/initializers/notify.js

@@ -17,7 +17,7 @@
 */
 export default {
   name: 'notify',
-  initialize: function(container, app) {
+  initialize: function (container, app) {
     app.inject('route', 'notify', 'service:notify');
     app.inject('controller', 'notify', 'service:notify');
     app.inject('component', 'notify', 'service:notify');

+ 3 - 5
contrib/views/hive/src/main/resources/ui/hive-web/app/mixins/filterable.js

@@ -87,7 +87,9 @@ export default Ember.Mixin.create({
   },
 
   clearFilters: function () {
-    if (!this.get('filters') || this.get('filters.length')) {
+    var filters = this.get('filters');
+
+    if (!filters || filters.get('length')) {
       this.set('filters', Ember.A());
     }
   },
@@ -95,10 +97,6 @@ export default Ember.Mixin.create({
   actions: {
     filter: function (property, filterValue) {
       this.updateFilters(property, filterValue);
-    },
-
-    clearFilters: function () {
-      this.clearFilters();
     }
   }
 });

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/router.js

@@ -24,7 +24,7 @@ var Router = Ember.Router.extend({
   location: config.locationType
 });
 
-Router.map(function() {
+Router.map(function () {
   var savedQueryPath = constants.namingConventions.routes.queries + '/:' + constants.namingConventions.savedQuery + '_id';
   var historyQueryPath = constants.namingConventions.routes.history + '/:' + constants.namingConventions.job + '_id';
 

+ 13 - 2
contrib/views/hive/src/main/resources/ui/hive-web/app/routes/application.js

@@ -53,17 +53,28 @@ export default Ember.Route.extend({
       });
     },
 
-    openOverlay: function(overlay) {
+    openOverlay: function (overlay) {
       return this.render(overlay.template, {
         outlet: overlay.outlet,
         into: overlay.into
       });
     },
-    closeOverlay: function(overlay) {
+    closeOverlay: function (overlay) {
       return this.disconnectOutlet({
         outlet: overlay.outlet,
         parentView: overlay.into
       });
+    },
+
+    removeNotification: function (notification) {
+      this.notify.removeNotification(notification);
+    },
+
+    willTransition: function(transition) {
+      // close active overlay if we transition
+      this.controllerFor('queryTabs').setDefaultActive();
+
+      return transition;
     }
   }
 });

+ 11 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/routes/index/saved-query.js

@@ -21,11 +21,21 @@ import constants from 'hive/utils/constants';
 
 export default Ember.Route.extend({
   setupController: function (controller, model) {
+    // settings modify fileContent to extract the settings
+    // when you load a saved query use the original fileContent
+    // this.store.find('file', model.get('queryFile'))
+    //   .then(function(queryFile) {
+    //     var changes = queryFile.changedAttributes();
+    //     if (changes.fileContent && changes.fileContent[0]) {
+    //       queryFile.set('fileContent', changes.fileContent[0]);
+    //     }
+    //   });
+
     this.controllerFor(constants.namingConventions.routes.index).set('model', model);
   },
 
   actions: {
-    error: function() {
+    error: function () {
       this.store.unloadAll(constants.namingConventions.savedQuery);
       this.transitionTo(constants.namingConventions.routes.index);
     }

+ 2 - 2
contrib/views/hive/src/main/resources/ui/hive-web/app/serializers/database.js

@@ -31,8 +31,8 @@ export default DS.JSONSerializer.extend({
     return this._super(store, primaryType, payload);
   },
 
-  normalizePayload: function(payload) {
-    var normalized = payload.databases.map(function(database) {
+  normalizePayload: function (payload) {
+    var normalized = payload.databases.map(function (database) {
       return database;
     });
 

+ 19 - 12
contrib/views/hive/src/main/resources/ui/hive-web/app/services/notify.js

@@ -21,10 +21,11 @@ import constants from 'hive/utils/constants';
 export default Ember.Service.extend({
   types: constants.notify,
 
-  messages      : Ember.ArrayProxy.create({ content : [] }),
-  notifications : Ember.ArrayProxy.create({ content : [] }),
+  messages       : Ember.ArrayProxy.create({ content : [] }),
+  notifications  : Ember.ArrayProxy.create({ content : [] }),
+  unseenMessages : Ember.ArrayProxy.create({ content : [] }),
 
-  add: function(type, message, body) {
+  add: function (type, message, body) {
     var formattedBody = this.formatMessageBody(body);
 
     var notification = Ember.Object.create({
@@ -35,25 +36,26 @@ export default Ember.Service.extend({
 
     this.messages.pushObject(notification);
     this.notifications.pushObject(notification);
+    this.unseenMessages.pushObject(notification);
   },
 
-  info: function(message, body) {
+  info: function (message, body) {
     this.add(this.types.INFO, message, body);
   },
 
-  warn: function(message, body) {
+  warn: function (message, body) {
     this.add(this.types.WARN, message, body);
   },
 
-  error: function(message, body) {
+  error: function (message, body) {
     this.add(this.types.ERROR, message, body);
   },
 
-  success: function(message, body) {
+  success: function (message, body) {
     this.add(this.types.SUCCESS, message, body);
   },
 
-  formatMessageBody: function(body) {
+  formatMessageBody: function (body) {
     if (!body) {
       return;
     }
@@ -72,17 +74,22 @@ export default Ember.Service.extend({
     }
   },
 
-  removeMessage: function(message) {
+  removeMessage: function (message) {
     this.messages.removeObject(message);
     this.notifications.removeObject(message);
   },
 
-  removeNotification: function(notification) {
+  removeNotification: function (notification) {
     this.notifications.removeObject(notification);
   },
 
-  removeAllMessages: function() {
+  removeAllMessages: function () {
     this.messages.removeAt(0, this.messages.get('length'));
-  }
+  },
 
+  markMessagesAsSeen: function () {
+    if (this.unseenMessages.get('length')) {
+      this.unseenMessages.removeAt(0, this.unseenMessages.get('length'));
+    }
+  }
 });

+ 43 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/styles/app.scss

@@ -66,9 +66,13 @@ a {
 
 #visual-explain {
   white-space: nowrap;
+
+  .panel-body {
+    overflow: hidden;
+  }
 }
 
-#visual-explain, #tez-ui {
+.index-overlay {
   position: absolute;
   left: 0;
   width: 0;
@@ -152,6 +156,30 @@ aside  hr {
   background-color: white;
 }
 
+.gray {
+  background-color: gray;
+}
+
+.green {
+  background-color: #99CC00;
+}
+
+.red {
+  background-color: #ff3300;
+}
+
+.orange {
+  background-color: #FF9933;
+}
+
+.yellow {
+  background-color: #CCCC00;
+}
+
+.blue {
+  background-color: blue;
+}
+
 .UNKNOWN {
   color: gray;
 }
@@ -372,6 +400,7 @@ tree-view ul li {
   font-weight: 800;
 
   .edge-path {
+    margin-top: -55px;
     height: 2px;
     background-color: #dedede;
     position: absolute;
@@ -436,6 +465,19 @@ tree-view ul li {
           color: green;
         }
       }
+
+      .progress {
+        border-radius: 0;
+        margin: 0 10px 10px 10px;
+      }
+
+      .progress-bar {
+        min-width: 2em;
+      }
     }
   }
 }
+
+.messages-controls {
+  margin: 0 0 10px;
+}

+ 0 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/styles/dropdown-submenu.scss

@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-
 .dropdown-submenu {
     position:relative;
 }

+ 16 - 16
contrib/views/hive/src/main/resources/ui/hive-web/app/styles/mixins.scss

@@ -1,20 +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.
- */
+* 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;

+ 1 - 0
contrib/views/hive/src/main/resources/ui/hive-web/app/styles/notifications.scss

@@ -15,6 +15,7 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
+
 .notifications-container {
   position: absolute;
   top: 4px;

+ 2 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/styles/query-tabs.scss

@@ -15,6 +15,7 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
+
 .query-menu {
   margin-top: 57px;
 
@@ -59,7 +60,7 @@
 .query-menu-tab .badge {
   position: absolute;
   top: -4px;
-  left: -4px;
+  right: 0;
   background-color: red;
   color: #fff;
   padding: 2px 4px;

+ 1 - 0
contrib/views/hive/src/main/resources/ui/hive-web/app/styles/vars.scss

@@ -15,6 +15,7 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
+
 $panel-background: #f5f5f5;
 $placeholder-color: #aaa;
 $border-color: #ddd;

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/templates/application.hbs

@@ -16,7 +16,7 @@
 * limitations under the License.
 }}
 
-{{notify-widget}}
+{{notify-widget notifications=notify.notifications}}
 {{render 'navbar'}}
 
 <div id="content">

+ 4 - 6
contrib/views/hive/src/main/resources/ui/hive-web/app/templates/components/progress-widget.hbs

@@ -17,9 +17,7 @@
 }}
 
 <div class="progress">
-  {{#each stage in formattedStages}}
-  <div {{bind-attr class="stage.className :progress-bar"}} {{bind-attr style=stage.progress}}>
-      <span class="sr-only">35% Complete (success)</span>
-    </div>
-  {{/each}}
-</div>
+  <div {{bind-attr class=":progress-bar :progress-bar-success" style=style}}>
+    {{percentage}}
+  </div>
+</div>

+ 6 - 2
contrib/views/hive/src/main/resources/ui/hive-web/app/templates/index.hbs

@@ -23,7 +23,6 @@
     </aside>
 
     <div class="col-md-9 col-xs-12 query-container">
-
       {{#panel-widget headingTranslation="titles.query.editor" classNames="query-editor-panel"}}
         {{render 'open-queries'}}
 
@@ -47,6 +46,11 @@
         </div>
       {{/panel-widget}}
 
+      {{#if jobProgress.stages}}
+        {{#progress-widget value=jobProgress.totalProgress}}
+        {{/progress-widget}}
+      {{/if}}
+
       {{#if queryParams}}
         {{#panel-widget headingTranslation="titles.query.parameters"}}
           <div class="form-horizontal">
@@ -87,6 +91,6 @@
       </ul>
     {{/popover-widget}}
 
-    {{query-tabs}}
+    {{render 'query-tabs'}}
   </div>
 </div>

+ 11 - 9
contrib/views/hive/src/main/resources/ui/hive-web/app/templates/messages.hbs

@@ -16,15 +16,17 @@
 * limitations under the License.
 }}
 
-<div class="editor-overlay messages-container">
-  <h3>Messages
-    {{#if messages.length}}
-      <button class="btn btn-danger btn-xs" {{action 'removeAllMessages'}}><i class="fa fa-minus"></i> Clear All</button>
-    {{/if}}
-  </h3>
+<div id="messages" class="index-overlay">
+  {{#panel-widget headingTranslation="titles.query.messages"}}
+    <div class="messages-controls">
+      {{#if messages.length}}
+        <button class="btn btn-danger btn-xs" {{action 'removeAllMessages'}}><i class="fa fa-minus"></i> Clear All</button>
+      {{/if}}
+    </div>
 
 
-  {{#each message in messages}}
-    {{view 'message' notification=message}}
-  {{/each}}
+    {{#each message in messages}}
+      {{view 'message' notification=message}}
+    {{/each}}
+  {{/panel-widget}}
 </div>

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/templates/components/query-tabs.hbs → contrib/views/hive/src/main/resources/ui/hive-web/app/templates/query-tabs.hbs

@@ -17,7 +17,7 @@
 }}
 
 {{#each tab in tabs}}
-    <span {{action tab.action tab}} {{bind-attr class=":query-menu-tab tabClassNames tab.iconClass tab.active:active"}}>
+    <span {{action tab.action tab}} {{bind-attr class=":query-menu-tab tabClassNames tab.iconClass tab.active:active" title="tab.tooltip" id="tab.id"}}>
       {{#if tab.badge}}
         <span class="badge">{{tab.badge}}</span>
       {{/if}}

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/templates/tez-ui.hbs

@@ -16,7 +16,7 @@
 * limitations under the License.
 }}
 
-<div id="tez-ui">
+<div id="tez-ui" class="index-overlay">
   {{#panel-widget headingTranslation="titles.query.tez"}}
     {{#if dagURL}}
       <iframe {{bind-attr src=dagURL}}></iframe>

+ 56 - 49
contrib/views/hive/src/main/resources/ui/hive-web/app/templates/visual-explain.hbs

@@ -16,65 +16,72 @@
 * limitations under the License.
 }}
 
-<div id="visual-explain">
+<div id="visual-explain" class="index-overlay">
   {{#panel-widget headingTranslation="titles.query.visualExplain"}}
 
-  {{#each edge in view.edges}}
-    <div class="edge">
-      <div class="edge-path" {{bind-attr style="edge.style"}}>
-        {{edge.type}}
+  {{#unless json}}
+    <div class="spinner"></div>
+  {{/unless}}
+
+  <div id="visual-explain-graph">
+    {{#each edge in view.edges}}
+      <div class="edge">
+        <div class="edge-path" {{bind-attr style="edge.style"}}>
+          {{edge.type}}
+        </div>
+       {{!--  <div class="edge-arrow" ></div> --}}
       </div>
-     {{!--  <div class="edge-arrow" ></div> --}}
-    </div>
-  {{/each}}
+    {{/each}}
 
-  <div class="nodes">
-    {{#each group in view.verticesGroups}}
-      <div class="node-container">
-        {{#if group.contents}}
-          {{#each node in group.contents}}
-            <div {{bind-attr class="node.isTableNode:table-node node.isOutputNode:output-node :node" title="node.id"}}>
-              {{#if node.isTableNode}}
-                <p><strong>{{t 'labels.table'}}</strong></p>
-                {{node.label}}
-              {{else}}
-                {{#if node.isOutputNode}}
+    <div class="nodes">
+      {{#each group in view.verticesGroups}}
+        <div class="node-container">
+          {{#if group.contents}}
+            {{#each node in group.contents}}
+              <div {{bind-attr class="node.isTableNode:table-node node.isOutputNode:output-node :node" title="node.id"}}>
+                {{#if node.isTableNode}}
+                  <p><strong>{{t 'labels.table'}}</strong></p>
                   {{node.label}}
                 {{else}}
-                  <div class="node-heading">
-                    <strong>{{node.label}}</strong>
-                  </div>
-                  <div class="node-content">
-                    {{#each section in node.contents}}
-                      <p>
-                        {{#popover-widget classNames="fa fa-info-circle" titleTranslation="popover.visualExplain.statistics" }}
-                          {{section.statistics}}
-                        {{/popover-widget}}
-                        <strong>
-                          {{section.index}}. {{section.title}}
-                        </strong>
-                        {{section.value}}
-                      </p>
+                  {{#if node.isOutputNode}}
+                    {{node.label}}
+                  {{else}}
+                    <div class="node-heading">
+                      <strong>{{node.label}}</strong>
+                    </div>
+                    <div class="node-content">
+                      {{#each section in node.contents}}
+                        <p>
+                          {{#popover-widget classNames="fa fa-info-circle" titleTranslation="popover.visualExplain.statistics" }}
+                            {{section.statistics}}
+                          {{/popover-widget}}
+                          <strong>
+                            {{section.index}}. {{section.title}}
+                          </strong>
+                          {{section.value}}
+                        </p>
 
-                      {{#each field in section.fields}}
-                        {{#if field.value}}
-                          <p>{{field.label}} {{field.value}}</p>
-                        {{/if}}
+                        {{#each field in section.fields}}
+                          {{#if field.value}}
+                            <p>{{field.label}} {{field.value}}</p>
+                          {{/if}}
+                        {{/each}}
                       {{/each}}
-                    {{/each}}
-                  </div>
+                    </div>
+                    {{progress-widget value=node.progress}}
+                  {{/if}}
                 {{/if}}
-              {{/if}}
+              </div>
+            {{/each}}
+          {{else}}
+            <div class="node" {{bind-attr title="group.label"}}>
+              {{group.label}}
             </div>
-          {{/each}}
-        {{else}}
-          <div class="node" {{bind-attr title="group.label"}}>
-            {{group.label}}
-          </div>
-        {{/if}}
-      </div>
-    {{/each}}
+          {{/if}}
+        </div>
+      {{/each}}
+    </div>
   </div>
 
   {{/panel-widget}}
-</div>
+</div>

+ 31 - 30
contrib/views/hive/src/main/resources/ui/hive-web/app/utils/constants.js

@@ -80,74 +80,75 @@ export default Ember.Object.create({
     databaseSearch: 'databases-search-results',
     tables: 'tables',
     columns: 'columns',
-    settings: 'settings'
+    settings: 'settings',
+    jobProgress: 'job-progress',
+    queryTabs: 'query-tabs'
   },
 
   hiveParameters: [
     {
       name: 'hive.tez.container.size',
-      values: [
-        Ember.Object.create({ value: 'true' }),
-        Ember.Object.create({ value: 'false' })
-      ]
+      validate: helpers.regexes.digits
     },
     {
       name: 'hive.prewarm.enabled',
-      validate: helpers.regexes.digits
+      values: helpers.validationValues.bool
     },
     {
       name: 'hive.prewarm.numcontainers',
-      values: [
-        Ember.Object.create({ value: 'one' }),
-        Ember.Object.create({ value: 'two' }),
-        Ember.Object.create({ value: 'three' })
-      ]
+      validate: helpers.regexes.digits
     },
     {
       name: 'hive.tez.auto.reducer.parallelism',
-      value: 'test'
-    },
-    {
-      name: 'hive.execution.engine'
-    },
-    {
-      name: 'hive.vectorized.execution.enabled'
+      values: helpers.validationValues.bool
     },
     {
-      name: 'tez.am.resource.memory.mb'
+      name: 'hive.execution.engine',
+      values: helpers.validationValues.execEngine
     },
     {
-      name: 'tez.am.container.idle.release-timeout-min.millis'
+      name: 'hive.vectorized.execution.enabled',
+      values: helpers.validationValues.bool
     },
     {
-      name: 'tez.am.container.idle.release-timeout-max.millis'
+      name: 'tez.am.resource.memory.mb',
+      validate: helpers.regexes.digits
     },
     {
-      name: 'tez.queue.name'
+      name: 'tez.am.container.idle.release-timeout-min.millis',
+      validate: helpers.regexes.digits
     },
     {
-      name: 'tez.runtime.io.sort.mb'
+      name: 'tez.am.container.idle.release-timeout-max.millis',
+      validate: helpers.regexes.digits
     },
     {
-      name: 'tez.runtime.sort.threads'
+      name: 'tez.queue.name',
+      validate: helpers.regexes.name
     },
     {
-      name: 'tez.runtime.optimize.shared.fetch'
+      name: 'tez.runtime.io.sort.mb',
+      validate: helpers.regexes.digits
     },
     {
-      name: 'tez.runtime.compress.codec'
+      name: 'tez.runtime.sort.threads',
+      validate: helpers.regexes.digits
     },
     {
-      name: 'tez.runtime.shuffle.keep-alive.enabled'
+      name: 'tez.runtime.compress.codec',
+      validate: helpers.regexes.dotPath
     },
     {
-      name: 'tez.grouping.min-size'
+      name: 'tez.grouping.min-size',
+      validate: helpers.regexes.digits
     },
     {
-      name: 'tez.grouping.max-size'
+      name: 'tez.grouping.max-size',
+      validate: helpers.regexes.digits
     },
     {
-      name: 'tez.generate.debug.artifacts'
+      name: 'tez.generate.debug.artifacts',
+      values: helpers.validationValues.bool
     }
   ],
 

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/app/utils/dag-rules.js

@@ -138,4 +138,4 @@ export default Ember.ArrayProxy.create({
       }
     ]
   )
-});
+});

+ 18 - 3
contrib/views/hive/src/main/resources/ui/hive-web/app/utils/functions.js

@@ -25,14 +25,29 @@ export default Ember.Object.create({
     return !isNaN(x);
   },
 
-  isDate: function(date) {
+  isDate: function (date) {
     return moment(date).isValid();
   },
 
   regexes: {
     allUppercase: /^[^a-z]*$/,
     whitespaces: /^(\s*).*$/,
-    digits: /^\d+$/
+    digits: /^\d+$/,
+    name: /\w+/ig,
+    dotPath: /[a-z.]+/i,
+    setSetting: /^set\s+[\w-.]+(\s+|\s?)=(\s+|\s?)[\w-.]+(\s+|\s?);/gim
+  },
+
+  validationValues: {
+    bool: [
+      Ember.Object.create({ value: 'true' }),
+      Ember.Object.create({ value: 'false' })
+    ],
+
+    execEngine: [
+      Ember.Object.create({ value: 'tez' }),
+      Ember.Object.create({ value: 'mr' })
+    ]
   },
 
   insensitiveCompare: function (sourceString) {
@@ -46,4 +61,4 @@ export default Ember.Object.create({
       return sourceString.match(new RegExp('^' + arg + '$', 'i'));
     });
   }
-});
+});

+ 2 - 2
contrib/views/hive/src/main/resources/ui/hive-web/app/views/message.js

@@ -25,11 +25,11 @@ export default NotificationView.extend({
   removeMessage: 'removeMessage',
 
   actions: {
-    expand: function() {
+    expand: function () {
       this.toggleProperty('isExpanded');
     },
 
-    close: function() {
+    close: function () {
       this.get('controller').send('removeMessage', this.get('notification'));
     }
   }

+ 35 - 0
contrib/views/hive/src/main/resources/ui/hive-web/app/views/messages.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';
+
+export default Ember.View.extend({
+  didInsertElement: function () {
+    var target = this.$('#messages');
+
+    target.css('min-height', $('.main-content').height());
+    target.animate({ width: $('.main-content').width() }, 'fast');
+  },
+
+  willDestroyElement: function () {
+    var target = this.$('#messages');
+
+    target.css('min-height', 0);
+    target.css('width', 0);
+  }
+});

+ 7 - 7
contrib/views/hive/src/main/resources/ui/hive-web/app/views/notification.js

@@ -19,18 +19,18 @@
 import Ember from 'ember';
 
 export default Ember.View.extend({
-  closeAfter         : 500000,
+  closeAfter         : 5000,
   isHovering         : false,
   templateName       : 'notification',
   removeNotification : 'removeNotification',
 
-  setup: function() {
+  setup: function () {
     this.set('typeClass', this.get('notification.type.typeClass'));
     this.set('typeIcon', this.get('notification.type.typeIcon'));
   }.on('init'),
 
-  removeLater: function() {
-    Ember.run.later(this, function() {
+  removeLater: function () {
+    Ember.run.later(this, function () {
       if (this.get('isHovering')) {
         this.removeLater();
       } else if (this.element) {
@@ -39,11 +39,11 @@ export default Ember.View.extend({
     }, this.get('closeAfter'));
   }.on('didInsertElement'),
 
-  mouseEnter: function() { this.set('isHovering', true);  },
-  mouseLeave: function() { this.set('isHovering', false); },
+  mouseEnter: function () { this.set('isHovering', true);  },
+  mouseLeave: function () { this.set('isHovering', false); },
 
   actions: {
-    close: function() {
+    close: function () {
       this.remove();
       this.get('parentView').send('removeNotification', this.get('notification'));
     }

+ 65 - 43
contrib/views/hive/src/main/resources/ui/hive-web/app/views/visual-explain.js

@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+/* globals dagre */
+
 import Ember from 'ember';
 import dagRules from '../utils/dag-rules';
 
@@ -24,10 +26,6 @@ export default Ember.View.extend({
     this.set('verticesGroups', []);
     this.set('edges', []);
     this.set('graph', new dagre.graphlib.Graph());
-
-    if (this.get('controller.json')) {
-      this.renderDag();
-    }
   },
 
   didInsertElement: function () {
@@ -38,7 +36,7 @@ export default Ember.View.extend({
     target.css('min-height', $('.main-content').height());
     target.animate({ width: $('.main-content').width() }, 'fast');
 
-    Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
+    this.$('#visual-explain-graph').draggable();
   },
 
   willDestroyElement: function () {
@@ -48,6 +46,31 @@ export default Ember.View.extend({
     target.css('width', 0);
   },
 
+  updateProgress: function () {
+    var verticesProgress = this.get('controller.verticesProgress');
+    var verticesGroups = this.get('verticesGroups');
+
+    if (!verticesGroups || !verticesProgress || !verticesProgress.length) {
+      return;
+    }
+
+    verticesGroups.forEach(function (verticesGroup) {
+      verticesGroup.contents.forEach(function (node) {
+        var progress = verticesProgress.findBy('name', node.get('label'));
+
+        if (progress) {
+          node.set('progress', progress.get('value'));
+        }
+      });
+    });
+  }.observes('controller.verticesProgress', 'verticesGroups'),
+
+  jsonChanged: function () {
+    if (this.get('controller.json')) {
+      this.renderDag();
+    }
+  }.observes('controller.json'),
+
   getOffset: function (el) {
     var _x = 0;
     var _y = 0;
@@ -106,24 +129,6 @@ export default Ember.View.extend({
     });
   },
 
-  afterRenderEvent : function () {
-    var g = this.get('graph');
-    var self = this;
-
-    //draw edges after the slide aniamtion for the visual explain container is done
-    Ember.run.later(function () {
-      g.edges().forEach(function (value) {
-        var edge = g.edge(value);
-        var v = value.v;
-
-        var firstNode = self.$("[title='" + value.v + "']")[0];
-        var secondNode = self.$("[title='" + value.w + "']")[0];
-
-        self.addEdge(firstNode, secondNode, 2, g.edge(value).type);
-      });
-    }, 300);
-  },
-
   getNodeContents: function (operator, contents, table, vertex) {
     var currentTable = table,
       contents = contents || [],
@@ -132,6 +137,8 @@ export default Ember.View.extend({
       ruleNode,
       nodeLabelValue,
       self = this;
+    
+    contents = contents || [];  
 
     if (operator.constructor === Array) {
       operator.forEach(function (childOperator) {
@@ -165,7 +172,7 @@ export default Ember.View.extend({
             return {
               label: field.label,
               value: value
-            }
+            };
           })
         });
 
@@ -222,19 +229,17 @@ export default Ember.View.extend({
       var currentTable;
 
       if (vertex.name.indexOf('Map') > -1) {
-        operator = vertex.value['Map Operator Tree:'][0];
-        currentTable = operator["TableScan"]["alias:"];
+        if (vertex.value && vertex.value['Map Operator Tree:']) {
+          operator = vertex.value['Map Operator Tree:'][0];
+          currentTable = operator["TableScan"]["alias:"];
+        } else {
+          //https://hortonworks.jira.com/browse/BUG-36168
+          operator = "None";
+        }
       } else if (vertex.name.indexOf('Reducer') > -1) {
         operator = vertex.value['Reduce Operator Tree:'];
       }
 
-      // else if (vertex.name.indexOf('Union') > -1) {
-      //   g.setNode(vertex, {
-      //     id: vertex.name,
-      //     label: vertex.name
-      //   });
-      // }
-
       if (operator) {
         contents = self.getNodeContents(operator, null, currentTable, vertex.name);
 
@@ -251,7 +256,6 @@ export default Ember.View.extend({
 
   //sets edges between operator nodes
   setEdges: function (edges) {
-    var i;
     var g = this.get('graph');
     var invalidEdges = [];
     var edgesToBeRemoved = [];
@@ -296,6 +300,7 @@ export default Ember.View.extend({
     });
 
     invalidEdges.forEach(function (invalidEdge) {
+      var parent;
       var targetEdge = g.edges().find(function (graphEdge) {
         return graphEdge.v === invalidEdge.edge.parent ||
                graphEdge.w === invalidEdge.edge.parent;
@@ -335,7 +340,7 @@ export default Ember.View.extend({
       var table;
       var id;
 
-      if (vertex.name.indexOf('Map') > -1) {
+      if (vertex.name.indexOf('Map') > -1 && vertex.value && vertex.value['Map Operator Tree:']) {
         operator = vertex.value['Map Operator Tree:'][0];
         for (var node in operator) {
           table = operator[node]['alias:'];
@@ -350,6 +355,8 @@ export default Ember.View.extend({
       }
     });
 
+    dagre.layout(g);
+
     return this;
   },
 
@@ -368,10 +375,10 @@ export default Ember.View.extend({
         if (!existentRow) {
            groupedNodes.pushObject({
               topOffset: node.y,
-              contents: [ node ]
+              contents: [ Ember.Object.create(node) ]
            });
         } else {
-          existentRow.contents.pushObject(node);
+          existentRow.contents.pushObject(Ember.Object.create(node));
         }
       }
     });
@@ -388,12 +395,28 @@ export default Ember.View.extend({
     g.setEdge(fileOutputOperator.title, lastRowNode.id);
 
     groupedNodes.pushObject({
-      contents: [ g.node(fileOutputOperator.title) ]
+      contents: [ Ember.Object.create(g.node(fileOutputOperator.title)) ]
     });
 
     lastRowNode.contents.removeObject(fileOutputOperator);
 
     this.set('verticesGroups', groupedNodes);
+
+    return this;
+  },
+
+  renderEdges: function () {
+    var self = this;
+    var g = this.get('graph');
+
+    Ember.run.later(function () {
+      g.edges().forEach(function (value) {
+        var firstNode = self.$("[title='" + value.v + "']")[0];
+        var secondNode = self.$("[title='" + value.w + "']")[0];
+
+        self.addEdge(firstNode, secondNode, 2, g.edge(value).type);
+      });
+    }, 200);
   },
 
   renderDag: function () {
@@ -427,10 +450,9 @@ export default Ember.View.extend({
 
     this.setNodes(vertices)
         .setEdges(edges)
-        .setTableNodesAndEdges(vertices);
-
-    dagre.layout(g);
-
-    this.createNodeGroups();
+        .setTableNodesAndEdges(vertices)
+        .createNodeGroups()
+        .renderEdges();
   }
 });
+

+ 1 - 1
contrib/views/hive/src/main/resources/ui/hive-web/package.json

@@ -25,7 +25,7 @@
     "broccoli-asset-rev": "^2.0.0",
     "broccoli-sass": "0.6.3",
     "ember-cli": "0.2.2",
-    "ember-cli-blanket": "^0.4.0",
+    "ember-cli-blanket": "^0.5.0",
     "ember-cli-content-security-policy": "0.3.0",
     "ember-cli-font-awesome": "0.0.4",
     "ember-cli-htmlbars": "0.7.4",

部分文件因为文件数量过多而无法显示