Просмотр исходного кода

AMBARI-8402. View: Pig ui updates + flow changes (alexantonenko)

Alex Antonenko 10 лет назад
Родитель
Сommit
82e72e517f
100 измененных файлов с 3402 добавлено и 1363 удалено
  1. 0 1
      contrib/views/pig/pom.xml
  2. 174 0
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/DataStoreStorage.java
  3. 2 2
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/InstanceKeyValueStorage.java
  4. 1 0
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/LocalKeyValueStorage.java
  5. 1 0
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/PersistentConfiguration.java
  6. 1 1
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/Storage.java
  7. 4 1
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/utils/ContextConfigurationAdapter.java
  8. 30 5
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/utils/StorageUtil.java
  9. 1 1
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/CRUDResourceManager.java
  10. 12 4
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/PersonalCRUDResourceManager.java
  11. 1 1
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/files/FileService.java
  12. 68 45
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/jobs/JobResourceManager.java
  13. 23 3
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/jobs/JobService.java
  14. 27 21
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/jobs/models/PigJob.java
  15. 50 49
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/jobs/utils/JobPolling.java
  16. 6 6
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/scripts/ScriptResourceManager.java
  17. 1 1
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/scripts/ScriptService.java
  18. 7 50
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/services/BaseService.java
  19. 4 3
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/services/HelpService.java
  20. 21 3
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/templeton/client/Request.java
  21. 1 1
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/templeton/client/TempletonApi.java
  22. 1 1
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/utils/FilePaginator.java
  23. 60 0
      contrib/views/pig/src/main/java/org/apache/ambari/view/pig/utils/HdfsApi.java
  24. 128 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/assets/data/pigHelpers.json
  25. BIN
      contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/FontAwesome.otf
  26. BIN
      contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/fontawesome-webfont.eot
  27. 196 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/fontawesome-webfont.svg
  28. BIN
      contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/fontawesome-webfont.ttf
  29. BIN
      contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/fontawesome-webfont.woff
  30. 83 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/components/codeMirror.js
  31. 146 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/components/helpers-data.js
  32. 10 14
      contrib/views/pig/src/main/resources/ui/pig-web/app/components/jobProgress.js
  33. 54 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/components/pigHelper.js
  34. 39 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/components/scriptListRow.js
  35. 44 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/components/tabControl.js
  36. 0 195
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/edit.js
  37. 1 1
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/errorLog.js
  38. 0 58
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/jobResults.js
  39. 47 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/confirmAway.js
  40. 5 6
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/confirmDelete.js
  41. 52 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/createScript.js
  42. 10 11
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/createUdf.js
  43. 7 2
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/deleteJob.js
  44. 6 5
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/gotoCopy.js
  45. 18 15
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/logDownload.js
  46. 40 1
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/pigModal.js
  47. 19 15
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/resultsDownload.js
  48. 5 1
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/page.js
  49. 80 3
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pig.js
  50. 5 5
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigAlert.js
  51. 19 7
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigHistory.js
  52. 0 68
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigScriptEdit.js
  53. 84 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigScripts.js
  54. 46 2
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigUdfs.js
  55. 0 59
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/poll.js
  56. 117 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/script.js
  57. 208 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/scriptEdit.js
  58. 11 7
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/scriptHistory.js
  59. 65 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/scriptJob.js
  60. 112 105
      contrib/views/pig/src/main/resources/ui/pig-web/app/initialize.js
  61. 54 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/mixins/fileHandler.js
  62. 99 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/mixins/pagination.js
  63. 29 2
      contrib/views/pig/src/main/resources/ui/pig-web/app/models/pig_job.js
  64. 17 12
      contrib/views/pig/src/main/resources/ui/pig-web/app/models/pig_script.js
  65. 5 8
      contrib/views/pig/src/main/resources/ui/pig-web/app/router.js
  66. 27 28
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pig.js
  67. 14 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigHistory.js
  68. 0 74
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigScriptEdit.js
  69. 0 93
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigScriptList.js
  70. 6 3
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigScripts.js
  71. 0 33
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigUdfs.js
  72. 59 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/script.js
  73. 47 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/scriptEdit.js
  74. 14 7
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/scriptHistory.js
  75. 21 20
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/scriptJob.js
  76. 10 8
      contrib/views/pig/src/main/resources/ui/pig-web/app/routes/splash.js
  77. 420 45
      contrib/views/pig/src/main/resources/ui/pig-web/app/styles/style.less
  78. 2 2
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/components/pigHelper.hbs
  79. 26 21
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/components/scriptListRow.hbs
  80. 35 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/error.hbs
  81. 0 18
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/index.hbs
  82. 32 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/confirmAway.hbs
  83. 15 2
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/confirmDelete.hbs
  84. 42 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/createScript.hbs
  85. 38 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/createUdf.hbs
  86. 15 3
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/deleteJob.hbs
  87. 15 13
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/gotoCopy.hbs
  88. 42 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/logDownload.hbs
  89. 3 3
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/modalLayout.hbs
  90. 42 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/resultsDownload.hbs
  91. 9 9
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/partials/alert-content.hbs
  92. 42 0
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/partials/paginationControls.hbs
  93. 29 3
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig.hbs
  94. 3 3
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/alert.hbs
  95. 37 21
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/history.hbs
  96. 0 19
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/jobResults.hbs
  97. 0 40
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/jobResultsOutput.hbs
  98. 0 18
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/jobStatus.hbs
  99. 0 40
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/modal/createScript.hbs
  100. 0 36
      contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/modal/createUdf.hbs

+ 0 - 1
contrib/views/pig/pom.xml

@@ -169,7 +169,6 @@
               <arguments>
                 <argument>node_modules/.bin/brunch</argument>
                 <argument>build</argument>
-                <argument>--production</argument>
               </arguments>
             </configuration>
           </execution>

+ 174 - 0
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/DataStoreStorage.java

@@ -0,0 +1,174 @@
+/**
+ * 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.pig.persistence;
+
+import com.google.gson.Gson;
+import org.apache.ambari.view.PersistenceException;
+import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.pig.persistence.utils.*;
+import org.apache.ambari.view.pig.utils.ServiceFormattedException;
+import org.apache.commons.configuration.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.WebApplicationException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Engine for storing objects to context DataStore storage
+ */
+public class DataStoreStorage implements Storage {
+  private final static Logger LOG =
+      LoggerFactory.getLogger(DataStoreStorage.class);
+  protected final Gson gson = new Gson();
+  protected ViewContext context;
+
+  /**
+   * Constructor
+   * @param context View Context instance
+   */
+  public DataStoreStorage(ViewContext context) {
+    this.context = context;
+  }
+
+  @Override
+  public synchronized void store(Indexed obj) {
+    try {
+      if (obj.getId() == null) {
+        int id = nextIdForEntity(context, obj.getClass());
+        obj.setId(String.valueOf(id));
+      }
+      context.getDataStore().store(obj);
+    } catch (PersistenceException e) {
+      throw new ServiceFormattedException("Error while saving object to DataStorage", e);
+    }
+  }
+
+  private static synchronized int nextIdForEntity(ViewContext context, Class aClass) {
+    // auto increment id implementation
+    String lastId = context.getInstanceData(aClass.getName());
+    int newId;
+    if (lastId == null) {
+      newId = 1;
+    } else {
+      newId = Integer.parseInt(lastId) + 1;
+    }
+    context.putInstanceData(aClass.getName(), String.valueOf(newId));
+    return newId;
+  }
+
+  @Override
+  public synchronized <T extends Indexed> T load(Class<T> model, int id) throws ItemNotFound {
+    LOG.debug(String.format("Loading %s #%d", model.getName(), id));
+    try {
+      T obj = context.getDataStore().find(model, String.valueOf(id));
+      if (obj != null) {
+        return obj;
+      } else {
+        throw new ItemNotFound();
+      }
+    } catch (PersistenceException e) {
+      throw new ServiceFormattedException("Error while finding object in DataStorage", e);
+    }
+  }
+
+  @Override
+  public synchronized <T extends Indexed> List<T> loadAll(Class<T> model, FilteringStrategy filter) {
+    LinkedList<T> list = new LinkedList<T>();
+    LOG.debug(String.format("Loading all %s-s", model.getName()));
+    try {
+      for(T item: context.getDataStore().findAll(model, null)) {
+        if ((filter == null) || filter.isConform(item)) {
+          list.add(item);
+        }
+      }
+    } catch (PersistenceException e) {
+      throw new ServiceFormattedException("Error while finding all objects in DataStorage", e);
+    }
+    return list;
+  }
+
+  @Override
+  public synchronized <T extends Indexed> List<T> loadAll(Class<T> model) {
+    return loadAll(model, new OnlyOwnersFilteringStrategy(this.context.getUsername()));
+  }
+
+  @Override
+  public synchronized void delete(Class model, int id) throws ItemNotFound {
+    LOG.debug(String.format("Deleting %s:%d", model.getName(), id));
+    Object obj = load(model, id);
+    try {
+      context.getDataStore().remove(obj);
+    } catch (PersistenceException e) {
+      throw new ServiceFormattedException("Error while removing object from DataStorage", e);
+    }
+  }
+
+  @Override
+  public boolean exists(Class model, int id) {
+    try {
+      return context.getDataStore().find(model, String.valueOf(id)) != null;
+    } catch (PersistenceException e) {
+      throw new ServiceFormattedException("Error while finding object in DataStorage", e);
+    }
+  }
+
+  public static void storageSmokeTest(ViewContext context) {
+    try {
+      SmokeTestEntity entity = new SmokeTestEntity();
+      entity.setData("42");
+      DataStoreStorage storage = new DataStoreStorage(context);
+      storage.store(entity);
+
+      if (entity.getId() == null) throw new ServiceFormattedException("Ambari Views instance data DB doesn't work properly (auto increment id doesn't work)", null);
+      int id = Integer.parseInt(entity.getId());
+      SmokeTestEntity entity2 = storage.load(SmokeTestEntity.class, id);
+      boolean status = entity2.getData().compareTo("42") == 0;
+      storage.delete(SmokeTestEntity.class, id);
+      if (!status) throw new ServiceFormattedException("Ambari Views instance data DB doesn't work properly", null);
+    } catch (WebApplicationException ex) {
+      throw ex;
+    } catch (Exception ex) {
+      throw new ServiceFormattedException(ex.getMessage(), ex);
+    }
+  }
+
+  public static class SmokeTestEntity implements Indexed {
+    private String id = null;
+    private String data = null;
+
+    public String getId() {
+      return id;
+    }
+
+    public void setId(String id) {
+      this.id = id;
+    }
+
+    public String getData() {
+      return data;
+    }
+
+    public void setData(String data) {
+      this.data = data;
+    }
+  }
+}

+ 2 - 2
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/InstanceKeyValueStorage.java

@@ -30,9 +30,9 @@ import javax.ws.rs.WebApplicationException;
 
 /**
  * Persistent storage engine for storing java beans to
- * properties file
- * Path to file should be in 'dataworker.storagePath' parameter
+ * instance data
  */
+@Deprecated
 public class InstanceKeyValueStorage extends KeyValueStorage {
   private final static Logger LOG =
       LoggerFactory.getLogger(InstanceKeyValueStorage.class);

+ 1 - 0
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/LocalKeyValueStorage.java

@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
  * properties file
  * Path to file should be in 'dataworker.storagePath' parameter
  */
+@Deprecated
 public class LocalKeyValueStorage extends KeyValueStorage {
   private final static Logger LOG =
       LoggerFactory.getLogger(LocalKeyValueStorage.class);

+ 1 - 0
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/PersistentConfiguration.java

@@ -28,6 +28,7 @@ import java.io.File;
  * Configuration enables all necessary options for PropertiesConfiguration:
  * auto-save, auto-reloading, no delimiter parsing and other
  */
+@Deprecated
 public class PersistentConfiguration extends PropertiesConfiguration {
   /**
    * Constructor

+ 1 - 1
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/Storage.java

@@ -66,7 +66,7 @@ public interface Storage {
    * @param model bean class
    * @param id identifier
    */
-  void delete(Class model, int id);
+  void delete(Class model, int id) throws ItemNotFound;
 
   /**
    * Check is object exists

+ 4 - 1
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/utils/ContextConfigurationAdapter.java

@@ -25,11 +25,13 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 
 /**
  * Persistence API to Apache Configuration adapter
  */
+@Deprecated
 public class ContextConfigurationAdapter implements Configuration {
   private ViewContext context;
 
@@ -53,7 +55,8 @@ public class ContextConfigurationAdapter implements Configuration {
 
   @Override
   public boolean containsKey(String s) {
-    return context.getInstanceData().containsKey(s);
+    Map<String, String> data = context.getInstanceData();
+    return data.containsKey(s);
   }
 
   @Override

+ 30 - 5
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/persistence/utils/StorageUtil.java

@@ -19,12 +19,16 @@
 package org.apache.ambari.view.pig.persistence.utils;
 
 import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.pig.persistence.DataStoreStorage;
 import org.apache.ambari.view.pig.persistence.InstanceKeyValueStorage;
 import org.apache.ambari.view.pig.persistence.LocalKeyValueStorage;
 import org.apache.ambari.view.pig.persistence.Storage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Storage factory, creates storage of Local or Persistence API type.
  * Type depends on context configuration: if "dataworker.storagePath" is set,
@@ -33,17 +37,38 @@ import org.slf4j.LoggerFactory;
  * Storage is singleton.
  */
 public class StorageUtil {
-  private static Storage storageInstance = null;
+  private Storage storageInstance = null;
 
   protected final static Logger LOG =
       LoggerFactory.getLogger(StorageUtil.class);
 
+
+  private static Map<String, StorageUtil> viewSingletonObjects = new HashMap<String, StorageUtil>();
+  public static StorageUtil getInstance(ViewContext context) {
+    if (!viewSingletonObjects.containsKey(context.getInstanceName()))
+      viewSingletonObjects.put(context.getInstanceName(), new StorageUtil(context));
+    return viewSingletonObjects.get(context.getInstanceName());
+  }
+
+  public static void dropAllConnections() {
+    viewSingletonObjects.clear();
+  }
+
+  private ViewContext context;
+
   /**
-   * Get storage instance. If one is not created, creates instance.
+   * Constructor of storage util
    * @param context View Context instance
+   */
+  public StorageUtil(ViewContext context) {
+    this.context = context;
+  }
+
+  /**
+   * Get storage instance. If one is not created, creates instance.
    * @return storage instance
    */
-  public synchronized static Storage getStorage(ViewContext context) {
+  public synchronized Storage getStorage() {
     if (storageInstance == null) {
       String fileName = context.getProperties().get("dataworker.storagePath");
       if (fileName != null) {
@@ -53,7 +78,7 @@ public class StorageUtil {
       } else {
         LOG.debug("Using Persistence API to store data");
         // If not specifed, use ambari-views Persistence API
-        storageInstance = new InstanceKeyValueStorage(context);
+        storageInstance = new DataStoreStorage(context);
       }
     }
     return storageInstance;
@@ -64,7 +89,7 @@ public class StorageUtil {
    * Used in unit tests.
    * @param storage storage instance
    */
-  public static void setStorage(Storage storage) {
+  public void setStorage(Storage storage) {
     storageInstance = storage;
   }
 }

+ 1 - 1
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/CRUDResourceManager.java

@@ -113,7 +113,7 @@ abstract public class CRUDResourceManager<T extends Indexed> {
 
   protected Storage getPigStorage() {
     if (storage == null) {
-      storage = StorageUtil.getStorage(getContext());
+      storage = StorageUtil.getInstance(getContext()).getStorage();
     }
     return storage;
   }

+ 12 - 4
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/PersonalCRUDResourceManager.java

@@ -55,7 +55,11 @@ public class PersonalCRUDResourceManager<T extends PersonalResource> extends CRU
 
   @Override
   public T save(T object) {
-    object.setOwner(this.context.getUsername());
+    if (!ignorePermissions) {
+      // in threads permissions should be ignored,
+      // because context.getUsername doesn't work. See BUG-27093.
+      object.setOwner(this.context.getUsername());
+    }
     return super.save(object);
   }
 
@@ -67,7 +71,7 @@ public class PersonalCRUDResourceManager<T extends PersonalResource> extends CRU
   }
 
   @Override
-  protected ViewContext getContext() {
+  public ViewContext getContext() {
     return context;
   }
 
@@ -88,10 +92,14 @@ public class PersonalCRUDResourceManager<T extends PersonalResource> extends CRU
     return result;
   }
 
-  protected String getUsername() {
+  protected static String getUsername(ViewContext context) {
     String userName = context.getProperties().get("dataworker.username");
-    if (userName == null)
+    if (userName == null || userName.compareTo("null") == 0 || userName.compareTo("") == 0)
       userName = context.getUsername();
     return userName;
   }
+
+  protected String getUsername() {
+    return getUsername(context);
+  }
 }

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

@@ -164,7 +164,7 @@ public class FileService extends BaseService {
    */
   public static void hdfsSmokeTest(ViewContext context) {
     try {
-      HdfsApi api = connectToHDFSApi(context);
+      HdfsApi api = HdfsApi.connectToHDFSApi(context);
       api.getStatus();
     } catch (WebApplicationException ex) {
       throw ex;

+ 68 - 45
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/jobs/JobResourceManager.java

@@ -26,6 +26,7 @@ import org.apache.ambari.view.pig.resources.jobs.models.PigJob;
 import org.apache.ambari.view.pig.resources.jobs.utils.JobPolling;
 import org.apache.ambari.view.pig.services.BaseService;
 import org.apache.ambari.view.pig.templeton.client.TempletonApi;
+import org.apache.ambari.view.pig.utils.HdfsApi;
 import org.apache.ambari.view.pig.utils.MisconfigurationFormattedException;
 import org.apache.ambari.view.pig.utils.ServiceFormattedException;
 import org.apache.hadoop.fs.FSDataOutputStream;
@@ -89,26 +90,26 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
     });
 
     for(PigJob job : notCompleted) {
-      JobPolling.pollJob(context, job);
+      JobPolling.pollJob(this, job);
     }
   }
 
   @Override
   public PigJob create(PigJob object) {
-    object.setStatus(PigJob.Status.SUBMITTING);
+    object.setStatus(PigJob.PIG_JOB_STATE_SUBMITTING);
     PigJob job = super.create(object);
     LOG.debug("Submitting job...");
 
     try {
       submitJob(object);
     } catch (RuntimeException e) {
-      object.setStatus(PigJob.Status.SUBMIT_FAILED);
+      object.setStatus(PigJob.PIG_JOB_STATE_SUBMIT_FAILED);
       save(object);
       LOG.debug("Job submit FAILED");
       throw e;
     }
     LOG.debug("Job submit OK");
-    object.setStatus(PigJob.Status.SUBMITTED);
+    object.setStatus(PigJob.PIG_JOB_STATE_SUBMITTED);
     save(object);
     return job;
   }
@@ -121,13 +122,17 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
   public void killJob(PigJob object) throws IOException {
     LOG.debug("Killing job...");
 
-    try {
-      getTempletonApi().killJob(object.getJobId());
-    } catch (IOException e) {
-      LOG.debug("Job kill FAILED");
-      throw e;
+    if (object.getJobId() != null) {
+      try {
+        getTempletonApi().killJob(object.getJobId());
+      } catch (IOException e) {
+        LOG.debug("Job kill FAILED");
+        throw e;
+      }
+      LOG.debug("Job kill OK");
+    } else {
+      LOG.debug("Job was not submitted, ignoring kill request...");
     }
-    LOG.debug("Job kill OK");
   }
 
   /**
@@ -136,15 +141,18 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
    */
   private void submitJob(PigJob job) {
     String date = new SimpleDateFormat("dd-MM-yyyy-HH-mm-ss").format(new Date());
-    String statusdir = String.format(context.getProperties().get("dataworker.jobs.path") +
-            "/%s/%s_%s", getUsername(),
-        job.getTitle().toLowerCase().replaceAll("[^a-zA-Z0-9 ]+", "").replace(" ", "_"),
-        date);
+    String jobsBaseDir = context.getProperties().get("jobs.dir");
+    String storeBaseDir = context.getProperties().get("store.dir");
+    if (storeBaseDir == null || storeBaseDir.compareTo("null") == 0 || storeBaseDir.compareTo("") == 0)
+      storeBaseDir = context.getProperties().get("jobs.dir");
+    String jobNameCleaned = job.getTitle().toLowerCase().replaceAll("[^a-zA-Z0-9 ]+", "").replace(" ", "_");
+    String storedir = String.format(storeBaseDir + "/%s_%s", jobNameCleaned, date);
+    String statusdir = String.format(jobsBaseDir + "/%s_%s", jobNameCleaned, date);
 
-    String newPigScriptPath = statusdir + "/script.pig";
-    String newSourceFilePath = statusdir + "/source.pig";
-    String newPythonScriptPath = statusdir + "/udf.py";
-    String templetonParamsFilePath = statusdir + "/params";
+    String newPigScriptPath = storedir + "/script.pig";
+    String newSourceFilePath = storedir + "/source.pig";
+    String newPythonScriptPath = storedir + "/udf.py";
+    String templetonParamsFilePath = storedir + "/params";
     try {
       // additional file can be passed to copy into work directory
       if (job.getSourceFileContent() != null && !job.getSourceFileContent().isEmpty()) {
@@ -152,13 +160,13 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
         job.setSourceFileContent(null); // we should not store content in DB
         save(job);
 
-        FSDataOutputStream stream = BaseService.getHdfsApi(context).create(newSourceFilePath, true);
+        FSDataOutputStream stream = HdfsApi.getInstance(context).create(newSourceFilePath, true);
         stream.writeBytes(sourceFileContent);
         stream.close();
       } else {
         if (job.getSourceFile() != null && !job.getSourceFile().isEmpty()) {
           // otherwise, just copy original file
-          if (!BaseService.getHdfsApi(context).copy(job.getSourceFile(), newSourceFilePath)) {
+          if (!HdfsApi.getInstance(context).copy(job.getSourceFile(), newSourceFilePath)) {
             throw new ServiceFormattedException("Can't copy source file from " + job.getSourceFile() +
                 " to " + newPigScriptPath);
           }
@@ -176,16 +184,16 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
         String forcedContent = job.getForcedContent();
         // variable for sourceFile can be passed from front-ent
         forcedContent = forcedContent.replace("${sourceFile}",
-            context.getProperties().get("dataworker.defaultFs") + newSourceFilePath);
+            context.getProperties().get("webhdfs.url") + newSourceFilePath);
         job.setForcedContent(null); // we should not store content in DB
         save(job);
 
-        FSDataOutputStream stream = BaseService.getHdfsApi(context).create(newPigScriptPath, true);
+        FSDataOutputStream stream = HdfsApi.getInstance(context).create(newPigScriptPath, true);
         stream.writeBytes(forcedContent);
         stream.close();
       } else {
         // otherwise, just copy original file
-        if (!BaseService.getHdfsApi(context).copy(job.getPigScript(), newPigScriptPath)) {
+        if (!HdfsApi.getInstance(context).copy(job.getPigScript(), newPigScriptPath)) {
           throw new ServiceFormattedException("Can't copy pig script file from " + job.getPigScript() +
               " to " + newPigScriptPath);
         }
@@ -198,7 +206,7 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
 
     if (job.getPythonScript() != null && !job.getPythonScript().isEmpty()) {
       try {
-        if (!BaseService.getHdfsApi(context).copy(job.getPythonScript(), newPythonScriptPath)) {
+        if (!HdfsApi.getInstance(context).copy(job.getPythonScript(), newPythonScriptPath)) {
           throw new ServiceFormattedException("Can't copy python udf script file from " + job.getPythonScript() +
               " to " + newPythonScriptPath);
         }
@@ -210,7 +218,7 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
     }
 
     try {
-      FSDataOutputStream stream = BaseService.getHdfsApi(context).create(templetonParamsFilePath, true);
+      FSDataOutputStream stream = HdfsApi.getInstance(context).create(templetonParamsFilePath, true);
       if (job.getTempletonArguments() != null) {
         stream.writeBytes(job.getTempletonArguments());
       }
@@ -235,7 +243,7 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
     }
     job.setJobId(data.id);
 
-    JobPolling.pollJob(context, job);
+    JobPolling.pollJob(this, job);
   }
 
   /**
@@ -253,29 +261,40 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
 
     if (info.status != null && (info.status.containsKey("runState"))) {
       //TODO: retrieve from RM
+      Long time = System.currentTimeMillis() / 1000L;
+      Long currentDuration = time - job.getDateStarted();
       int runState = ((Double) info.status.get("runState")).intValue();
+      boolean isStatusChanged = false;
       switch (runState) {
-        case PigJob.RUN_STATE_KILLED:
+        case RUN_STATE_KILLED:
           LOG.debug(String.format("Job KILLED: %s", job.getJobId()));
-          job.setStatus(PigJob.Status.KILLED);
+          isStatusChanged = job.getStatus() != PigJob.PIG_JOB_STATE_KILLED;
+          job.setStatus(PigJob.PIG_JOB_STATE_KILLED);
           break;
-        case PigJob.RUN_STATE_FAILED:
+        case RUN_STATE_FAILED:
           LOG.debug(String.format("Job FAILED: %s", job.getJobId()));
-          job.setStatus(PigJob.Status.FAILED);
+          isStatusChanged = job.getStatus() != PigJob.PIG_JOB_STATE_FAILED;
+          job.setStatus(PigJob.PIG_JOB_STATE_FAILED);
           break;
-        case PigJob.RUN_STATE_PREP:
-        case PigJob.RUN_STATE_RUNNING:
-          job.setStatus(PigJob.Status.RUNNING);
+        case RUN_STATE_PREP:
+        case RUN_STATE_RUNNING:
+          isStatusChanged = job.getStatus() != PigJob.PIG_JOB_STATE_RUNNING;
+          job.setStatus(PigJob.PIG_JOB_STATE_RUNNING);
           break;
-        case PigJob.RUN_STATE_SUCCEEDED:
+        case RUN_STATE_SUCCEEDED:
           LOG.debug(String.format("Job COMPLETED: %s", job.getJobId()));
-          job.setStatus(PigJob.Status.COMPLETED);
+          isStatusChanged = job.getStatus() != PigJob.PIG_JOB_STATE_COMPLETED;
+          job.setStatus(PigJob.PIG_JOB_STATE_COMPLETED);
           break;
         default:
           LOG.debug(String.format("Job in unknown state: %s", job.getJobId()));
-          job.setStatus(PigJob.Status.UNKNOWN);
+          isStatusChanged = job.getStatus() != PigJob.PIG_JOB_STATE_UNKNOWN;
+          job.setStatus(PigJob.PIG_JOB_STATE_UNKNOWN);
           break;
       }
+      if (isStatusChanged) {
+        job.setDuration(currentDuration);
+      }
     }
     Pattern pattern = Pattern.compile("\\d+");
     Matcher matcher = null;
@@ -306,13 +325,13 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
   }
 
   private static TempletonApi connectToTempletonApi(ViewContext context) {
-    String webhcatUrl = context.getProperties().get("dataworker.webhcat.url");
+    String webhcatUrl = context.getProperties().get("webhcat.url");
     if (webhcatUrl == null) {
-      String message = "dataworker.webhcat.url is not configured!";
+      String message = "webhcat.url is not configured!";
       LOG.error(message);
-      throw new MisconfigurationFormattedException("dataworker.webhcat.url");
+      throw new MisconfigurationFormattedException("webhcat.url");
     }
-    return new TempletonApi(context.getProperties().get("dataworker.webhcat.url"),
+    return new TempletonApi(context.getProperties().get("webhcat.url"),
         getTempletonUser(context), getTempletonUser(context), context);
   }
 
@@ -322,12 +341,16 @@ public class JobResourceManager extends PersonalCRUDResourceManager<PigJob> {
    * @return username in templeton
    */
   private static String getTempletonUser(ViewContext context) {
-    String username = context.getProperties().get("dataworker.webhcat.user");
-    if (username == null) {
-      String message = "dataworker.webhcat.user is not configured!";
-      LOG.error(message);
-      throw new MisconfigurationFormattedException("dataworker.webhcat.user");
+    String username = context.getProperties().get("webhcat.username");
+    if (username == null || username.compareTo("null") == 0 || username.compareTo("") == 0) {
+      username = getUsername(context);
     }
     return username;
   }
+
+  public static final int RUN_STATE_RUNNING = 1;
+  public static final int RUN_STATE_SUCCEEDED = 2;
+  public static final int RUN_STATE_FAILED = 3;
+  public static final int RUN_STATE_PREP = 4;
+  public static final int RUN_STATE_KILLED = 5;
 }

+ 23 - 3
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/jobs/JobService.java

@@ -21,6 +21,7 @@ package org.apache.ambari.view.pig.resources.jobs;
 import com.google.inject.Inject;
 import org.apache.ambari.view.ViewContext;
 import org.apache.ambari.view.ViewResourceHandler;
+import org.apache.ambari.view.pig.persistence.utils.Indexed;
 import org.apache.ambari.view.pig.persistence.utils.ItemNotFound;
 import org.apache.ambari.view.pig.persistence.utils.OnlyOwnersFilteringStrategy;
 import org.apache.ambari.view.pig.resources.files.FileResource;
@@ -31,6 +32,8 @@ import org.apache.ambari.view.pig.utils.FilePaginator;
 import org.apache.ambari.view.pig.utils.NotFoundFormattedException;
 import org.apache.ambari.view.pig.utils.ServiceFormattedException;
 import org.json.simple.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.*;
@@ -57,6 +60,9 @@ public class JobService extends BaseService {
   @Inject
   ViewResourceHandler handler;
 
+  protected final static Logger LOG =
+      LoggerFactory.getLogger(JobService.class);
+
   protected JobResourceManager resourceManager = null;
 
   /**
@@ -108,7 +114,8 @@ public class JobService extends BaseService {
    */
   @DELETE
   @Path("{jobId}")
-  public Response killJob(@PathParam("jobId") String jobId) throws IOException {
+  public Response killJob(@PathParam("jobId") String jobId,
+                          @QueryParam("remove") final String remove) throws IOException {
     try {
       PigJob job = null;
       try {
@@ -117,6 +124,9 @@ public class JobService extends BaseService {
         throw new NotFoundFormattedException(itemNotFound.getMessage(), itemNotFound);
       }
       getResourceManager().killJob(job);
+      if (remove != null && remove.compareTo("true") == 0) {
+        getResourceManager().delete(jobId);
+      }
       return Response.status(204).build();
     } catch (WebApplicationException ex) {
       throw ex;
@@ -206,10 +216,20 @@ public class JobService extends BaseService {
    */
   @GET
   @Produces(MediaType.APPLICATION_JSON)
-  public Response getJobList(@Context HttpHeaders headers, @Context UriInfo ui) {
+  public Response getJobList(@QueryParam("scriptId") final String scriptId) {
     try {
       List allJobs = getResourceManager().readAll(
-          new OnlyOwnersFilteringStrategy(this.context.getUsername()));
+          new OnlyOwnersFilteringStrategy(this.context.getUsername()) {
+            @Override
+            public boolean isConform(Indexed item) {
+              if (scriptId == null)
+                return super.isConform(item);
+              else {
+                PigJob job = (PigJob) item;
+                return (job.getScriptId() != null && scriptId.compareTo(job.getScriptId()) == 0 && super.isConform(item));
+              }
+            }
+          });  //TODO: move strategy to PersonalCRUDRM
 
       JSONObject object = new JSONObject();
       object.put("jobs", allJobs);

+ 27 - 21
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/jobs/models/PigJob.java

@@ -20,7 +20,6 @@ package org.apache.ambari.view.pig.resources.jobs.models;
 
 import org.apache.ambari.view.pig.persistence.utils.PersonalResource;
 import org.apache.commons.beanutils.BeanUtils;
-import org.codehaus.jackson.annotate.JsonIgnore;
 
 import java.io.Serializable;
 import java.lang.reflect.InvocationTargetException;
@@ -42,23 +41,16 @@ import java.util.Map;
  * COMPLETED      KILLED        FAILED
  */
 public class PigJob implements Serializable, PersonalResource {
-
-  public enum Status {
-    UNKNOWN,
-    SUBMITTING, SUBMITTED, RUNNING,  // in progress
-    SUBMIT_FAILED, COMPLETED, FAILED, KILLED  // finished
-  }
-
-  public boolean isInProgress() {
-    return status == Status.SUBMITTED || status == Status.SUBMITTING ||
-        status == Status.RUNNING;
-  }
-
-  public static final int RUN_STATE_RUNNING = 1;
-  public static final int RUN_STATE_SUCCEEDED = 2;
-  public static final int RUN_STATE_FAILED = 3;
-  public static final int RUN_STATE_PREP = 4;
-  public static final int RUN_STATE_KILLED = 5;
+  public static final String PIG_JOB_STATE_UNKNOWN = "UNKNOWN";
+  // in progress
+  public static final String PIG_JOB_STATE_SUBMITTING = "SUBMITTING";
+  public static final String PIG_JOB_STATE_SUBMITTED = "SUBMITTED";
+  public static final String PIG_JOB_STATE_RUNNING = "RUNNING";
+  // finished
+  public static final String PIG_JOB_STATE_SUBMIT_FAILED = "SUBMIT_FAILED";
+  public static final String PIG_JOB_STATE_COMPLETED = "COMPLETED";
+  public static final String PIG_JOB_STATE_FAILED = "FAILED";
+  public static final String PIG_JOB_STATE_KILLED = "KILLED";
 
   public PigJob() {
   }
@@ -96,10 +88,11 @@ public class PigJob implements Serializable, PersonalResource {
 
   private String statusDir;
   private Long dateStarted = 0L;
+  private Long duration = 0L;
   private String jobId = null;
 
   // status fields (not reliable)
-  private Status status = Status.UNKNOWN;
+  private String status = PIG_JOB_STATE_UNKNOWN;
   private Integer percentComplete = null;
 
   @Override
@@ -119,6 +112,11 @@ public class PigJob implements Serializable, PersonalResource {
     return id.hashCode();
   }
 
+  public boolean isInProgress() {
+    return status.equals(PIG_JOB_STATE_SUBMITTED) || status.equals(PIG_JOB_STATE_SUBMITTING) ||
+        status.equals(PIG_JOB_STATE_RUNNING);
+  }
+
   @Override
   public String getId() {
     return id;
@@ -139,11 +137,11 @@ public class PigJob implements Serializable, PersonalResource {
     this.owner = owner;
   }
 
-  public Status getStatus() {
+  public String getStatus() {
     return status;
   }
 
-  public void setStatus(Status status) {
+  public void setStatus(String status) {
     this.status = status;
   }
 
@@ -203,6 +201,14 @@ public class PigJob implements Serializable, PersonalResource {
     this.dateStarted = dateStarted;
   }
 
+  public Long getDuration() {
+    return duration;
+  }
+
+  public void setDuration(Long duration) {
+    this.duration = duration;
+  }
+
   public Integer getPercentComplete() {
     return percentComplete;
   }

+ 50 - 49
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/jobs/utils/JobPolling.java

@@ -18,7 +18,8 @@
 
 package org.apache.ambari.view.pig.resources.jobs.utils;
 
-import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.pig.persistence.utils.FilteringStrategy;
+import org.apache.ambari.view.pig.persistence.utils.Indexed;
 import org.apache.ambari.view.pig.persistence.utils.ItemNotFound;
 import org.apache.ambari.view.pig.resources.jobs.JobResourceManager;
 import org.apache.ambari.view.pig.resources.jobs.models.PigJob;
@@ -26,12 +27,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
-import java.util.Observable;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 
 /**
  * Polling manager
@@ -49,67 +47,71 @@ public class JobPolling implements Runnable {
    */
   private static final int WORKER_COUNT = 2;
 
-  private static final int POLLING_DELAY = 10*60;  // 10 minutes
+  private static final int POLLING_DELAY = 60;  // 1 minutes
 
   /**
    * In LONG_JOB_THRESHOLD seconds job reschedules polling from POLLING_DELAY to LONG_POLLING_DELAY
    */
-  private static final int LONG_POLLING_DELAY = 60*60; // 1 hour
-  private static final int LONG_JOB_THRESHOLD = 60*60; // 1 hour
+  private static final int LONG_POLLING_DELAY = 10*60; // 10 minutes
+  private static final int LONG_JOB_THRESHOLD = 10*60; // 10 minutes
 
   private static final ScheduledExecutorService pollWorkersPool = Executors.newScheduledThreadPool(WORKER_COUNT);
 
   private static final Map<String, JobPolling> jobPollers = new HashMap<String, JobPolling>();
 
   private JobResourceManager resourceManager = null;
-  private final ViewContext context;
   private PigJob job;
   private volatile ScheduledFuture<?> thisFuture;
 
-  private JobPolling(ViewContext context, PigJob job) {
-    this.context = context;
+  private JobPolling(JobResourceManager resourceManager, PigJob job) {
+    this.resourceManager = resourceManager;
     this.job = job;
   }
 
-  protected synchronized JobResourceManager getResourceManager() {
-    if (resourceManager == null) {
-      resourceManager = new JobResourceManager(context);
-    }
-    return resourceManager;
-  }
-
   /**
    * Do polling
    */
   public void run() {
-    LOG.debug("Polling job status " + job.getJobId() + " #" + job.getId());
     try {
-      job = getResourceManager().read(job.getId());
-    } catch (ItemNotFound itemNotFound) {
-      LOG.error("Job " + job.getJobId() + " does not exist! Polling canceled");
-      thisFuture.cancel(false);
-      return;
-    }
-    getResourceManager().retrieveJobStatus(job);
-
-    Long time = System.currentTimeMillis() / 1000L;
-    if (time - job.getDateStarted() > LONG_JOB_THRESHOLD) {
-      LOG.debug("Job becomes long.. Rescheduling polling to longer period");
-      // If job running longer than LONG_JOB_THRESHOLD, reschedule
-      // it to poll every LONG_POLLING_DELAY instead of POLLING_DELAY
-      thisFuture.cancel(false);
-      scheduleJobPolling(true);
-    }
-
-    switch (job.getStatus()) {
-      case SUBMIT_FAILED:
-      case COMPLETED:
-      case FAILED:
-      case KILLED:
-        LOG.debug("Job finished. Polling canceled");
-        thisFuture.cancel(false);
-        break;
-      default:
+      // Hack to make permission check work. It is based on
+      // context.getUsername(), but it doesn't work in another thread. See BUG-27093.
+      resourceManager.ignorePermissions(new Callable<Void>() {
+        @Override
+        public Void call() throws Exception {
+          LOG.debug("Polling job status " + job.getJobId() + " #" + job.getId());
+          try {
+            job = resourceManager.read(job.getId());
+          } catch (ItemNotFound itemNotFound) {
+            LOG.error("Job " + job.getId() + " does not exist! Polling canceled");
+            thisFuture.cancel(false);
+            return null;
+          }
+          resourceManager.retrieveJobStatus(job);
+
+          Long time = System.currentTimeMillis() / 1000L;
+          if (time - job.getDateStarted() > LONG_JOB_THRESHOLD) {
+            LOG.debug("Job becomes long.. Rescheduling polling to longer period");
+            // If job running longer than LONG_JOB_THRESHOLD, reschedule
+            // it to poll every LONG_POLLING_DELAY instead of POLLING_DELAY
+            thisFuture.cancel(false);
+            scheduleJobPolling(true);
+          }
+
+          if (job.getStatus().equals(PigJob.PIG_JOB_STATE_SUBMIT_FAILED) ||
+              job.getStatus().equals(PigJob.PIG_JOB_STATE_COMPLETED) ||
+              job.getStatus().equals(PigJob.PIG_JOB_STATE_FAILED) ||
+              job.getStatus().equals(PigJob.PIG_JOB_STATE_KILLED)) {
+            LOG.debug("Job finished. Polling canceled");
+            thisFuture.cancel(false);
+
+          } else {
+          }
+          return null;
+        }
+      });
+    } catch (Exception e) {
+      LOG.error("Exception during handling job polling: " + e.getMessage());
+      e.printStackTrace();
     }
   }
 
@@ -129,14 +131,13 @@ public class JobPolling implements Runnable {
 
   /**
    * Schedule job polling
-   * @param context ViewContext of web app
    * @param job job instance
    * @return returns false if already scheduled
    */
-  public static boolean pollJob(ViewContext context, PigJob job) {
+  public static boolean pollJob(JobResourceManager resourceManager, PigJob job) {
     if (jobPollers.get(job.getJobId()) == null) {
       LOG.debug("Setting up polling for " + job.getJobId());
-      JobPolling polling = new JobPolling(context, job);
+      JobPolling polling = new JobPolling(resourceManager, job);
       polling.scheduleJobPolling();
       jobPollers.put(job.getJobId(), polling);
       return true;

+ 6 - 6
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/scripts/ScriptResourceManager.java

@@ -23,6 +23,7 @@ import org.apache.ambari.view.pig.persistence.utils.ItemNotFound;
 import org.apache.ambari.view.pig.resources.PersonalCRUDResourceManager;
 import org.apache.ambari.view.pig.resources.scripts.models.PigScript;
 import org.apache.ambari.view.pig.services.BaseService;
+import org.apache.ambari.view.pig.utils.HdfsApi;
 import org.apache.ambari.view.pig.utils.MisconfigurationFormattedException;
 import org.apache.ambari.view.pig.utils.ServiceFormattedException;
 import org.apache.hadoop.fs.FSDataOutputStream;
@@ -59,11 +60,11 @@ public class ScriptResourceManager extends PersonalCRUDResourceManager<PigScript
   }
 
   private void createDefaultScriptFile(PigScript object) {
-    String userScriptsPath = context.getProperties().get("dataworker.scripts.path");
+    String userScriptsPath = context.getProperties().get("scripts.dir");
     if (userScriptsPath == null) {
-      String msg = "dataworker.scripts.path is not configured!";
+      String msg = "scripts.dir is not configured!";
       LOG.error(msg);
-      throw new MisconfigurationFormattedException("dataworker.scripts.path");
+      throw new MisconfigurationFormattedException("scripts.dir");
     }
     int checkId = 0;
 
@@ -73,12 +74,11 @@ public class ScriptResourceManager extends PersonalCRUDResourceManager<PigScript
       String normalizedName = object.getTitle().replaceAll("[^a-zA-Z0-9 ]+", "").replaceAll(" ", "_").toLowerCase();
       String timestamp = new SimpleDateFormat("yyyy-MM-dd_hh-mm").format(new Date());
       newFilePath = String.format(userScriptsPath +
-              "/%s/%s-%s%s.pig", getUsername(),
-          normalizedName, timestamp, (checkId == 0)?"":"_"+checkId);
+              "/%s-%s%s.pig", normalizedName, timestamp, (checkId == 0)?"":"_"+checkId);
       LOG.debug("Trying to create new file " + newFilePath);
 
       try {
-        FSDataOutputStream stream = BaseService.getHdfsApi(context).create(newFilePath, false);
+        FSDataOutputStream stream = HdfsApi.getInstance(context).create(newFilePath, false);
         stream.close();
         fileCreated = true;
         LOG.debug("File created successfully!");

+ 1 - 1
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/resources/scripts/ScriptService.java

@@ -111,7 +111,7 @@ public class ScriptService extends BaseService {
     try {
       LOG.debug("Getting all scripts");
       List allScripts = getResourceManager().readAll(
-          new OnlyOwnersFilteringStrategy(this.context.getUsername()));
+          new OnlyOwnersFilteringStrategy(this.context.getUsername()));  //TODO: move strategy to PersonalCRUDRM
 
       JSONObject object = new JSONObject();
       object.put("scripts", allScripts);

+ 7 - 50
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/services/BaseService.java

@@ -42,14 +42,14 @@ public class BaseService {
   @Inject
   protected ViewContext context;
 
-  protected final static Logger LOG =
+  private final static Logger LOG =
       LoggerFactory.getLogger(BaseService.class);
 
   private Storage storage;
 
   protected Storage getStorage() {
     if (this.storage == null) {
-      storage = StorageUtil.getStorage(context);
+      storage = StorageUtil.getInstance(context).getStorage();
     }
     return storage;
   }
@@ -58,62 +58,19 @@ public class BaseService {
     this.storage = storage;
   }
 
-  private static HdfsApi hdfsApi = null;
-
-  /**
-   * Returns HdfsApi object
-   * @param context View Context instance
-   * @return Hdfs business delegate object
-   */
-  public static HdfsApi getHdfsApi(ViewContext context) {
-    if (hdfsApi == null) {
-      hdfsApi = connectToHDFSApi(context);
-    }
-    return hdfsApi;
-  }
-
-  protected static HdfsApi connectToHDFSApi(ViewContext context) {
-    HdfsApi api = null;
-    Thread.currentThread().setContextClassLoader(null);
-
-    String defaultFS = context.getProperties().get("dataworker.defaultFs");
-    if (defaultFS == null) {
-      String message = "dataworker.defaultFs is not configured!";
-      LOG.error(message);
-      throw new MisconfigurationFormattedException("dataworker.defaultFs");
-    }
-
-    try {
-      api = new HdfsApi(defaultFS, getHdfsUsername(context));
-      LOG.info("HdfsApi connected OK");
-    } catch (IOException e) {
-      String message = "HdfsApi IO error: " + e.getMessage();
-      LOG.error(message);
-      throw new ServiceFormattedException(message, e);
-    } catch (InterruptedException e) {
-      String message = "HdfsApi Interrupted error: " + e.getMessage();
-      LOG.error(message);
-      throw new ServiceFormattedException(message, e);
-    }
-    return api;
-  }
-
-  public static String getHdfsUsername(ViewContext context) {
-    String userName = context.getProperties().get("dataworker.hdfs.username");
-    if (userName == null)
-      userName = context.getUsername();
-    return userName;
-  }
+  private HdfsApi hdfsApi = null;
 
   protected HdfsApi getHdfsApi()  {
-    return getHdfsApi(context);
+    if (hdfsApi == null)
+      hdfsApi = HdfsApi.getInstance(context);
+    return hdfsApi;
   }
 
   /**
    * Set HdfsApi delegate
    * @param api HdfsApi instance
    */
-  public static void setHdfsApi(HdfsApi api)  {
+  public void setHdfsApi(HdfsApi api)  {
     hdfsApi = api;
   }
 }

+ 4 - 3
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/services/HelpService.java

@@ -20,6 +20,7 @@ package org.apache.ambari.view.pig.services;
 
 import org.apache.ambari.view.ViewContext;
 import org.apache.ambari.view.ViewResourceHandler;
+import org.apache.ambari.view.pig.persistence.DataStoreStorage;
 import org.apache.ambari.view.pig.persistence.InstanceKeyValueStorage;
 import org.apache.ambari.view.pig.resources.files.FileService;
 import org.apache.ambari.view.pig.resources.jobs.JobResourceManager;
@@ -59,8 +60,8 @@ public class HelpService extends BaseService {
   @Produces(MediaType.APPLICATION_JSON)
   public Response config(){
     JSONObject object = new JSONObject();
-    String fs = context.getProperties().get("dataworker.defaultFs");
-    object.put("dataworker.defaultFs", fs);
+    String fs = context.getProperties().get("webhdfs.url");
+    object.put("webhdfs.url", fs);
     return Response.ok(object).build();
   }
 
@@ -111,7 +112,7 @@ public class HelpService extends BaseService {
   @Path("/storageStatus")
   @Produces(MediaType.APPLICATION_JSON)
   public Response storageStatus(){
-    InstanceKeyValueStorage.storageSmokeTest(context);
+    DataStoreStorage.storageSmokeTest(context);
     return getOKResponse();
   }
 

+ 21 - 3
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/templeton/client/Request.java

@@ -69,6 +69,7 @@ public class Request<RESPONSE> {
     InputStream inputStream = context.getURLStreamProvider().readFrom(resource.toString(), "GET",
         null, new HashMap<String, String>());
 
+    LOG.info(String.format("curl \"" + resource.toString() + "\""));
     String responseJson = IOUtils.toString(inputStream);
     LOG.debug(String.format("RESPONSE => %s", responseJson));
     return gson.fromJson(responseJson, responseClass);
@@ -100,10 +101,14 @@ public class Request<RESPONSE> {
     LOG.debug("POST " + resource.toString());
     LOG.debug("data: " + data.toString());
 
+    StringBuilder curlBuilder = new StringBuilder();
+
     UriBuilder builder = UriBuilder.fromPath("host/");
     for(String key : data.keySet()) {
-      for(String value : data.get(key))
+      for(String value : data.get(key)) {
         builder.queryParam(key, value);
+        curlBuilder.append(String.format("-d %s=\"%s\"", key, value.replace("\"", "\\\"")));
+      }
     }
 
     if (data != null)
@@ -112,6 +117,7 @@ public class Request<RESPONSE> {
     Map<String, String> headers = new HashMap<String, String>();
     headers.put("Content-Type", "application/x-www-form-urlencoded");
 
+    LOG.info(String.format("curl " + curlBuilder.toString() + " \"" + resource.toString() + "\""));
     InputStream inputStream = context.getURLStreamProvider().readFrom(resource.toString(),
         "POST", builder.build().getRawQuery(), headers);
     String responseJson = IOUtils.toString(inputStream);
@@ -150,10 +156,14 @@ public class Request<RESPONSE> {
   public RESPONSE put(WebResource resource, MultivaluedMapImpl data) throws IOException {
     LOG.debug("PUT " + resource.toString());
 
+    StringBuilder curlBuilder = new StringBuilder();
+
     UriBuilder builder = UriBuilder.fromPath("host/");
     for(String key : data.keySet()) {
-      for(String value : data.get(key))
+      for(String value : data.get(key)) {
         builder.queryParam(key, value);
+        curlBuilder.append(String.format("-d %s=\"%s\"", key, value.replace("\"", "\\\"")));
+      }
     }
 
     if (data != null)
@@ -162,6 +172,8 @@ public class Request<RESPONSE> {
     Map<String, String> headers = new HashMap<String, String>();
     headers.put("Content-Type", "application/x-www-form-urlencoded");
 
+    LOG.info(String.format("curl -X PUT " + curlBuilder.toString() + " \"" + resource.toString() + "\""));
+
     InputStream inputStream = context.getURLStreamProvider().readFrom(resource.toString(),
         "PUT", builder.build().getRawQuery(), headers);
     String responseJson = IOUtils.toString(inputStream);
@@ -200,10 +212,14 @@ public class Request<RESPONSE> {
   public RESPONSE delete(WebResource resource, MultivaluedMapImpl data) throws IOException {
     LOG.debug("DELETE " + resource.toString());
 
+    StringBuilder curlBuilder = new StringBuilder();
+
     UriBuilder builder = UriBuilder.fromPath("host/");
     for(String key : data.keySet()) {
-      for(String value : data.get(key))
+      for(String value : data.get(key)) {
         builder.queryParam(key, value);
+        curlBuilder.append(String.format("-d %s=\"%s\"", key, value.replace("\"", "\\\"")));
+      }
     }
 
     if (data != null)
@@ -212,6 +228,8 @@ public class Request<RESPONSE> {
     Map<String, String> headers = new HashMap<String, String>();
     headers.put("Content-Type", "application/x-www-form-urlencoded");
 
+    LOG.info(String.format("curl -X DELETE " + curlBuilder.toString() + " \"" + resource.toString() + "\""));
+
     InputStream inputStream = context.getURLStreamProvider().readFrom(resource.toString(),
         "DELETE", builder.build().getRawQuery(), headers);
     String responseJson = IOUtils.toString(inputStream);

+ 1 - 1
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/templeton/client/TempletonApi.java

@@ -52,7 +52,7 @@ public class TempletonApi {
 
   /**
    * TempletonApi constructor
-   * @param api dataworker.webhcat.url
+   * @param api webhcat.url
    * @param username templeton username
    * @param doAs doAs argument
    * @param context context with URLStreamProvider

+ 1 - 1
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/utils/FilePaginator.java

@@ -44,7 +44,7 @@ public class FilePaginator {
    */
   public FilePaginator(String filePath, ViewContext context) {
     this.filePath = filePath;
-    hdfsApi = BaseService.getHdfsApi(context);
+    hdfsApi = HdfsApi.getInstance(context);
   }
 
   /**

+ 60 - 0
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/utils/HdfsApi.java

@@ -18,6 +18,7 @@
 
 package org.apache.ambari.view.pig.utils;
 
+import org.apache.ambari.view.ViewContext;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.*;
 import org.apache.hadoop.fs.permission.FsPermission;
@@ -29,10 +30,13 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.URI;
 import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.hadoop.security.UserGroupInformation;
 import org.json.simple.JSONArray;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.LinkedHashMap;
 
@@ -45,6 +49,9 @@ public class HdfsApi {
   private FileSystem fs;
   private UserGroupInformation ugi;
 
+  private final static Logger LOG =
+      LoggerFactory.getLogger(HdfsApi.class);
+
   /**
    * Constructor
    * @param defaultFs hdfs uri
@@ -284,4 +291,57 @@ public class HdfsApi {
     return json;
   }
 
+
+  private static Map<String, HdfsApi> viewSingletonObjects = new HashMap<String, HdfsApi>();
+  /**
+   * Returns HdfsApi object specific to instance
+   * @param context View Context instance
+   * @return Hdfs business delegate object
+   */
+  public static HdfsApi getInstance(ViewContext context) {
+    if (!viewSingletonObjects.containsKey(context.getInstanceName()))
+      viewSingletonObjects.put(context.getInstanceName(), connectToHDFSApi(context));
+    return viewSingletonObjects.get(context.getInstanceName());
+  }
+
+  public static void setInstance(ViewContext context, HdfsApi api) {
+    viewSingletonObjects.put(context.getInstanceName(), api);
+  }
+
+  public static HdfsApi connectToHDFSApi(ViewContext context) {
+    HdfsApi api = null;
+    Thread.currentThread().setContextClassLoader(null);
+
+    String defaultFS = context.getProperties().get("webhdfs.url");
+    if (defaultFS == null) {
+      String message = "webhdfs.url is not configured!";
+      LOG.error(message);
+      throw new MisconfigurationFormattedException("webhdfs.url");
+    }
+
+    try {
+      api = new HdfsApi(defaultFS, getHdfsUsername(context));
+      LOG.info("HdfsApi connected OK");
+    } catch (IOException e) {
+      String message = "HdfsApi IO error: " + e.getMessage();
+      LOG.error(message);
+      throw new ServiceFormattedException(message, e);
+    } catch (InterruptedException e) {
+      String message = "HdfsApi Interrupted error: " + e.getMessage();
+      LOG.error(message);
+      throw new ServiceFormattedException(message, e);
+    }
+    return api;
+  }
+
+  public static String getHdfsUsername(ViewContext context) {
+    String userName = context.getProperties().get("webhdfs.username");
+    if (userName == null || userName.compareTo("null") == 0 || userName.compareTo("") == 0)
+      userName = context.getUsername();
+    return userName;
+  }
+
+  public static void dropAllConnections() {
+    viewSingletonObjects.clear();
+  }
 }

+ 128 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/assets/data/pigHelpers.json

@@ -0,0 +1,128 @@
+[
+      {
+        "title":"Eval Functions",
+        "helpers":[
+          "AVG(%VAR%)",
+          "CONCAT(%VAR1%, %VAR2%)",
+          "COUNT(%VAR%)",
+          "COUNT_START(%VAR%)",
+          "IsEmpty(%VAR%)",
+          "DIFF(%VAR1%, %VAR2%)",
+          "MAX(%VAR%)",
+          "MIN(%VAR%)",
+          "SIZE(%VAR%)",
+          "SUM(%VAR%)",
+          "TOKENIZE(%VAR%, %DELIM%)"
+        ]
+      },
+      {
+        "title":"Relational Operators",
+        "helpers":[
+          "COGROUP %VAR% BY %VAR%",
+          "CROSS %VAR1%, %VAR2%;",
+          "DISTINCT %VAR%;",
+          "FILTER %VAR% BY %COND%",
+          "FLATTEN(%VAR%)",
+          "FOREACH %DATA% GENERATE %NEW_DATA%",
+          "FOREACH %DATA% {%NESTED_BLOCK%}",
+          "GROUP %VAR% BY %VAR%",
+          "GROUP %VAR% ALL",
+          "JOIN %VAR% BY ",
+          "LIMIT %VAR% %N%",
+          "ORDER %VAR% BY %FIELD%",
+          "SAMPLE %VAR% %SIZE%",
+          "SPLIT %VAR1% INTO %VAR2% IF %EXPRESSIONS%",
+          "UNION %VAR1%, %VAR2%"
+        ]
+      },
+      {
+        "title":"I/0",
+        "helpers":[
+          "LOAD \"%FILE%\";",
+          "DUMP %VAR%;",
+          "STORE %VAR% INTO %PATH%;"
+        ]
+      },
+      {
+        "title":"Debug",
+        "helpers":[
+          "EXPLAIN %VAR%;",
+          "ILLUSTRATE %VAR%;",
+          "DESCRIBE %VAR%;"
+        ]
+      },
+      {
+        "title":"HCatalog",
+        "helpers":[
+          "LOAD \"%TABLE%\" USING org.apache.hcatalog.pig.HCatLoader();"
+        ]
+      },
+      {
+        "title":"Math",
+        "helpers":[
+          "ABS(%VAR%)",
+          "ACOS(%VAR%)",
+          "ASIN(%VAR%)",
+          "ATAN(%VAR%)",
+          "CBRT(%VAR%)",
+          "CEIL(%VAR%)",
+          "COS(%VAR%)",
+          "COSH(%VAR%)",
+          "EXP(%VAR%)",
+          "FLOOR(%VAR%)",
+          "LOG(%VAR%)",
+          "LOG10(%VAR%)",
+          "RANDOM(%VAR%)",
+          "ROUND(%VAR%)",
+          "SIN(%VAR%)",
+          "SINH(%VAR%)",
+          "SQRT(%VAR%)",
+          "TAN(%VAR%)",
+          "TANH(%VAR%)"
+        ]
+      },
+      {
+        "title":"Tuple, Bag, Map Functions",
+        "helpers":[
+          "TOTUPLE(%VAR%)",
+          "TOBAG(%VAR%)",
+          "TOMAP(%KEY%, %VALUE%)",
+          "TOP(%topN%, %COLUMN%, %RELATION%)"
+        ]
+      },
+      {
+        "title":"String Functions",
+        "helpers":[
+          "INDEXOF(%STRING%, \"%CHARACTER%\", %STARTINDEX%)",
+          "LAST_INDEX_OF(%STRING%, \"%CHARACTER%\", %STARTINDEX%)",
+          "LOWER(%STRING%)",
+          "REGEX_EXTRACT(%STRING%, %REGEX%, %INDEX%)",
+          "REGEX_EXTRACT_ALL(%STRING%, %REGEX%)",
+          "REPLACE(%STRING%, \"%oldChar%\", \"%newChar%\")",
+          "STRSPLIT(%STRING%, %REGEX%, %LIMIT%)",
+          "SUBSTRING(%STRING%, %STARTINDEX%, %STOPINDEX%)",
+          "TRIM(%STRING%)",
+          "UCFIRST(%STRING%)",
+          "UPPER(%STRING%)"
+        ]
+      },
+      {
+        "title":"Macros",
+        "helpers":[
+          "IMPORT \"%PATH_TO_MACRO%\";"
+        ]
+      },
+      {
+        "title":"HBase",
+        "helpers":[
+          "LOAD \"hbase://%TABLE%\" USING org.apache.pig.backend.hadoop.hbase.HBaseStorage(\"%columnList%\")",
+          "STORE %VAR% INTO \"hbase://%TABLE%\" USING org.apache.pig.backend.hadoop.hbase.HBaseStorage(\"%columnList%\")"
+        ]
+      },
+      {
+        "title":"Python UDF",
+        "helpers":[
+          "REGISTER \"python_udf.py\" USING jython AS myfuncs;"
+        ]
+      }
+    ]

BIN
contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/FontAwesome.otf


BIN
contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/fontawesome-webfont.eot


Разница между файлами не показана из-за своего большого размера
+ 196 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/fontawesome-webfont.svg


BIN
contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/fontawesome-webfont.ttf


BIN
contrib/views/pig/src/main/resources/ui/pig-web/app/assets/static/fonts/fontawesome-webfont.woff


+ 83 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/components/codeMirror.js

@@ -0,0 +1,83 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.CodeMirrorComponent = Ember.Component.extend({
+  tagName: "textarea",
+  readOnly:false,
+  codeMirror:null,
+  updateCM:function () {
+    var cm = this.get('codeMirror');
+    if (this.get('readOnly')) {
+      cm.setValue((this.get('content.fileContent')||''));
+      return cm.setOption('readOnly',true);
+    }
+    var cmElement = $(cm.display.wrapper);
+    if (this.get('content.isLoaded')) {
+      cm.setOption('readOnly',false);
+      cmElement.removeClass('inactive');
+      cm.setValue((this.get('content.fileContent')||''));
+      cm.markClean();
+    } else {
+      cm.setOption('readOnly',true);
+      cmElement.addClass('inactive');
+    }
+  }.observes('codeMirror', 'content.didLoad'),
+  didInsertElement: function() {
+    var cm = CodeMirror.fromTextArea(this.get('element'),{
+      lineNumbers: true,
+      matchBrackets: true,
+      indentUnit: 4,
+      keyMap: "emacs"
+    });
+
+    $('.editor-container').resizable({
+      stop:function () {
+        cm.setSize(null, this.style.height);
+      },
+      resize: function() {
+        this.getElementsByClassName('CodeMirror')[0].style.height = this.style.height;
+        this.getElementsByClassName('CodeMirror-gutters')[0].style.height = this.style.height;
+      },
+      minHeight:215,
+      handles: {'s': '#sgrip' }
+    });
+
+    this.set('codeMirror',cm);
+    if (!this.get('readOnly')) {
+      cm.focus();
+      cm.on('change', Em.run.bind(this,this.editorDidChange));
+    }
+  },
+  editorDidChange:function () {
+    var pig_script = this.get('content');
+    if (pig_script.get('isLoaded')){
+      pig_script.set('fileContent',this.get('codeMirror').getValue());
+      this.get('codeMirror').markClean();
+    }
+  },
+  scriptDidChange:function () {
+    if (this.get('codeMirror').isClean() && this.get('content.isDirty')) {
+      this.get('codeMirror').setValue(this.get(('content.fileContent')||''));
+    }
+  }.observes('content.fileContent'),
+  willClearRender:function () {
+    this.get('codeMirror').toTextArea();
+  }
+});

+ 146 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/components/helpers-data.js

@@ -0,0 +1,146 @@
+/**
+ * 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.
+ */
+
+module.exports = [
+  {
+    'title':'Eval Functions',
+    'helpers':[
+      'AVG(%VAR%)',
+      'CONCAT(%VAR1%, %VAR2%)',
+      'COUNT(%VAR%)',
+      'COUNT_START(%VAR%)',
+      'IsEmpty(%VAR%)',
+      'DIFF(%VAR1%, %VAR2%)',
+      'MAX(%VAR%)',
+      'MIN(%VAR%)',
+      'SIZE(%VAR%)',
+      'SUM(%VAR%)',
+      'TOKENIZE(%VAR%, %DELIM%)'
+    ]
+  },
+  {
+    'title':'Relational Operators',
+    'helpers':[
+      'COGROUP %VAR% BY %VAR%',
+      'CROSS %VAR1%, %VAR2%;',
+      'DISTINCT %VAR%;',
+      'FILTER %VAR% BY %COND%',
+      'FLATTEN(%VAR%)',
+      'FOREACH %DATA% GENERATE %NEW_DATA%',
+      'FOREACH %DATA% {%NESTED_BLOCK%}',
+      'GROUP %VAR% BY %VAR%',
+      'GROUP %VAR% ALL',
+      'JOIN %VAR% BY ',
+      'LIMIT %VAR% %N%',
+      'ORDER %VAR% BY %FIELD%',
+      'SAMPLE %VAR% %SIZE%',
+      'SPLIT %VAR1% INTO %VAR2% IF %EXPRESSIONS%',
+      'UNION %VAR1%, %VAR2%'
+    ]
+  },
+  {
+    'title':'I/0',
+    'helpers':[
+      "LOAD '%FILE%';",
+      'DUMP %VAR%;',
+      'STORE %VAR% INTO %PATH%;'
+    ]
+  },
+  {
+    'title':'Debug',
+    'helpers':[
+      'EXPLAIN %VAR%;',
+      'ILLUSTRATE %VAR%;',
+      'DESCRIBE %VAR%;'
+    ]
+  },
+  {
+    'title':'HCatalog',
+    'helpers':[
+      "LOAD '%TABLE%' USING org.apache.hcatalog.pig.HCatLoader();"
+    ]
+  },
+  {
+    'title':'Math',
+    'helpers':[
+      'ABS(%VAR%)',
+      'ACOS(%VAR%)',
+      'ASIN(%VAR%)',
+      'ATAN(%VAR%)',
+      'CBRT(%VAR%)',
+      'CEIL(%VAR%)',
+      'COS(%VAR%)',
+      'COSH(%VAR%)',
+      'EXP(%VAR%)',
+      'FLOOR(%VAR%)',
+      'LOG(%VAR%)',
+      'LOG10(%VAR%)',
+      'RANDOM(%VAR%)',
+      'ROUND(%VAR%)',
+      'SIN(%VAR%)',
+      'SINH(%VAR%)',
+      'SQRT(%VAR%)',
+      'TAN(%VAR%)',
+      'TANH(%VAR%)'
+    ]
+  },
+  {
+    'title':'Tuple, Bag, Map Functions',
+    'helpers':[
+      'TOTUPLE(%VAR%)',
+      'TOBAG(%VAR%)',
+      'TOMAP(%KEY%, %VALUE%)',
+      'TOP(%topN%, %COLUMN%, %RELATION%)'
+    ]
+  },
+  {
+    'title':'String Functions',
+    'helpers':[
+      "INDEXOF(%STRING%, '%CHARACTER%', %STARTINDEX%)",
+      "LAST_INDEX_OF(%STRING%, '%CHARACTER%', %STARTINDEX%)",
+      "LOWER(%STRING%)",
+      "REGEX_EXTRACT(%STRING%, %REGEX%, %INDEX%)",
+      "REGEX_EXTRACT_ALL(%STRING%, %REGEX%)",
+      "REPLACE(%STRING%, '%oldChar%', '%newChar%')",
+      "STRSPLIT(%STRING%, %REGEX%, %LIMIT%)",
+      "SUBSTRING(%STRING%, %STARTINDEX%, %STOPINDEX%)",
+      "TRIM(%STRING%)",
+      "UCFIRST(%STRING%)",
+      "UPPER(%STRING%)"
+    ]
+  },
+  {
+    'title':'Macros',
+    'helpers':[
+      "IMPORT '%PATH_TO_MACRO%';"
+    ]
+  },
+  {
+    'title':'HBase',
+    'helpers':[
+      "LOAD 'hbase://%TABLE%' USING org.apache.pig.backend.hadoop.hbase.HBaseStorage('%columnList%')",
+      "STORE %VAR% INTO 'hbase://%TABLE%' USING org.apache.pig.backend.hadoop.hbase.HBaseStorage('%columnList%')"
+    ]
+  },
+  {
+    'title':'Python UDF',
+    'helpers':[
+      "REGISTER 'python_udf.py' USING jython AS myfuncs;"
+    ]
+  }
+]

+ 10 - 14
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigScriptEditResults.js → contrib/views/pig/src/main/resources/ui/pig-web/app/components/jobProgress.js

@@ -18,19 +18,15 @@
 
 var App = require('app');
 
-App.PigScriptEditResultsRoute = Em.Route.extend({
-  model: function(params) {
-    return this.store.find('job',params.job_id);
+App.JobProgressComponent = Em.Component.extend({
+  job:null,
+  classNames:['progress'],
+  didInsertElement:function () {
+    this.update();
   },
-  afterModel:function (model) {
-    //this.controllerFor('pigScriptEdit').set('activeJob',model);
-  },
-  renderTemplate: function() {
-    this.render('pig/scriptResultsNav',{
-      outlet: 'nav',
-    });
-    this.render('pig/scriptResults',{
-      outlet: 'main',
-    });
-  }
+  update:function () {
+    var progress = (!this.get('job.isTerminated'))?this.get('job.percentStatus'):100;
+    $(this.get('element')).find('.progress-bar').css('width',progress+'%');
+  }.observes('job.percentStatus'),
+  layout:Em.Handlebars.compile('<div {{bind-attr class=":progress-bar job.isTerminated:progress-bar-danger:progress-bar-success" }}role="progressbar"></div>')
 });

+ 54 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/components/pigHelper.js

@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var pigHelpers = require('./helpers-data');
+
+App.PigHelperComponent = Em.Component.extend({
+  layoutName: 'components/pigHelper',
+  helpers: pigHelpers,
+  editor: null,
+  actions:{
+    putToEditor:function (helper) {
+      var editor = this.get('editor');
+      var cursor = editor.getCursor();
+      var pos = this.findPosition(helper);
+
+      editor.replaceRange(helper, cursor, cursor);
+      editor.focus();
+
+      if (pos.length>1) {
+        editor.setSelection(
+          {line:cursor.line, ch:cursor.ch + pos[0]},
+          {line:cursor.line, ch:cursor.ch + pos[1]+1}
+        );
+      }
+
+      return false;
+    }
+  },
+  findPosition: function (curLine){
+    var pos= curLine.indexOf("%");
+    var posArr=[];
+    while(pos > -1) {
+      posArr.push(pos);
+      pos = curLine.indexOf("%", pos+1);
+    }
+    return posArr;
+  }
+});

+ 39 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/components/scriptListRow.js

@@ -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.
+ */
+
+var App = require('app');
+
+App.ScriptListRowComponent = Em.Component.extend({
+  tagName:'tr',
+  scriptJobs:function () {
+    var scriptId = this.get('script.id');
+    var scriptJobs = this.get('jobs').filter(function (job) {
+      return job.get('scriptId') == scriptId;
+    });
+    return scriptJobs;
+  }.property('jobs.content'),
+  scriptJobsIds: Ember.computed.mapBy('scriptJobs', 'id'),
+  currentJobId:Em.computed.max('scriptJobsIds'),
+  currentJob:function () {
+    var jobId = this.get('currentJobId');
+    var scriptJob = this.get('scriptJobs').filter(function (job) {
+      return job.get('id') == jobId;
+    });
+    return scriptJob.get('firstObject');
+  }.property('currentJobId','scriptJobs')
+});

+ 44 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/components/tabControl.js

@@ -0,0 +1,44 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.TabControlComponent = Em.Component.extend({
+  actions:{
+    popTab:function (name){
+      this.sendAction('popTab',name);
+    }
+  },
+  tagName:'li',
+  classNameBindings:['isActive:active','isJob:job','disabled:disabled'],
+  disabled:function () {
+    return !this.get('target');
+  }.property('target'),
+  isActive:function () {
+    return this.get('tab') == this.get('current');
+  }.property('tab','current'),
+  isJob:function () {
+    return !(this.get('tab') == 'history' || this.get('tab') == 'script');
+  }.property('tab'),
+  layout:Em.Handlebars.compile('{{yield}}{{#if isJob}}<button {{action \'popTab\' tab }} class="btn" ><i class="fa fa-close"></i></button>{{/if}}'),
+  tooltip:function () {
+    if (this.get('disabled')){
+      this.$().tooltip({title:'The script has been deleted and is no longer available.',placement:'bottom'});
+    }
+  }.on('didInsertElement')
+});

+ 0 - 195
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/edit.js

@@ -1,195 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.EditController = Em.ObjectController.extend({
-  pigArgumentsPull: Em.A(),
-  isExec:false,
-  isRenaming:false,
-  actions:{
-    rename:function (opt) {
-      var changedAttributes = this.content.changedAttributes(),
-          controller = this;
-      if (opt==='ask') {
-        this.set('isRenaming',true);
-      };
-
-      if (opt==='cancel') {
-        this.content.rollback();
-        if (Em.isArray(changedAttributes.templetonArguments)) {
-          this.content.set('templetonArguments',changedAttributes.templetonArguments[1]);
-        }
-        this.set('isRenaming',false);
-      };
-
-      if (opt==='confirm') {
-        if (Em.isArray(changedAttributes.title)) {
-          this.content.save().then(function () {
-            controller.send('showAlert', {message:Em.I18n.t('editor.title_updated'),status:'success'});
-          });
-        }
-        this.set('isRenaming',false);
-      };
-
-    },
-    addArgument:function (arg) {
-      if (!arg) {
-        return false;
-      };
-      var pull = this.get('pigArgumentsPull');
-      if (Em.$.inArray(arg,pull)<0) {
-        pull.pushObject(arg);
-      } else {
-        this.send('showAlert', {'message': Em.I18n.t('scripts.alert.arg_present'), status:'info'});
-      }
-    },
-    removeArgument:function (arg) {
-      var index = this.pigArgumentsPull.indexOf(arg.toString());
-      this.pigArgumentsPull.removeAt(index);
-    },
-    execute: function (object, operation) {
-      var controller = this,
-          job,
-          sendAlert = function (status) {
-            var alerts = {success:Em.I18n.t('job.alert.job_started'), error:Em.I18n.t('job.alert.start_filed')};
-            return function (argument) {
-              controller.set('isExec',false);
-              var trace = null;
-              if (status=='error') {
-                trace = argument.responseJSON.trace;
-              }
-              controller.send('showAlert', {message:alerts[status],status:status,trace:trace});
-              if (status=='success') {
-                controller.transitionToRoute('job',job);
-              };
-            };
-          };
-
-      controller.set('isExec',true);
-
-      return Ember.RSVP.resolve(object.get('pigScript')).then(function (file) {
-        var savePromises = [file.save()];
-
-        job = controller.prepareJob(file, object, operation);
-
-        if (object.get('constructor.typeKey') === "script") {
-          savePromises.push(object.save());
-        };
-
-        return Ember.RSVP.all(savePromises).then(function () {
-          return job.save().then(sendAlert('success'),sendAlert('error'));
-        });
-      });
-    },
-  },
-  prepareJob:function (file, object, operation) {
-    var controller = this,
-        args = object.get('templetonArguments'),
-        parameters = this.get('pigParams.length') > 0,
-        pigParams = this.get('pigParams') || [],
-        fileContent = file.get('fileContent'),
-        config;
-
-    pigParams.forEach(function (param) {
-      var rgParam = new RegExp(param.param,'g');
-      fileContent = fileContent.replace(rgParam,param.value);
-    });
-    
-    if (operation === 'execute') {
-      config = {
-        templetonArguments: args,
-        title: object.get('title'),
-        forcedContent: (parameters)? fileContent:null,
-        pigScript: (!parameters)?file:null
-      };
-    } else if (operation === 'explain') {
-      config = {
-        templetonArguments:  '',
-        title: 'Explain: "' + object.get('title') + '"',
-        jobType: 'explain',
-        sourceFileContent: (parameters)? fileContent:null,
-        sourceFile: (!parameters)?file.get('id'):null,
-        forcedContent: 'explain -script ${sourceFile}'
-      }
-    } else {
-      config = {
-        templetonArguments: (!args.match(/-check/g))?args+(args?"\t":"")+'-check':args,
-        title: 'Syntax check: "' + object.get('title') + '"',
-        jobType:  'syntax_check',
-        forcedContent: (parameters)? fileContent:null,
-        pigScript: (!parameters)?file:null
-      }
-    };
-    
-    if (object.get('constructor.typeKey') === 'script'){
-      config.scriptId = object.get('id');
-    } else {
-      config.scriptId = (operation != 'explain')?object.get('scriptId'):null;
-    };
-
-    return this.store.createRecord('job',config);
-  },
-  /*
-   *Is script or scritp file is dirty.
-   * @return {boolean}
-   */
-  scriptDirty:function () {
-    return this.get('content.isDirty') || this.get('content.pigScript.isDirty');
-  }.property('content.pigScript.isDirty','content.isDirty'),
-
-  /**
-   * Is script is in error state.
-   * @return {boolean}
-   */
-  scriptError:function () {
-    return this.get('content.isError');
-  }.property('content.isError'),
-
-  /*
-    Gets script arguments array from pigArgumentsPull
-    and write to model as string
-  */
-  pigArgToString:function (controller,observer) {
-    var args = controller.get('pigArgumentsPull');
-    var oldargs = (this.get('content.templetonArguments'))?this.get('content.templetonArguments').w():[];
-    if (args.length != oldargs.length) {
-      this.set('content.templetonArguments',args.join('\t'));
-    };
-  }.observes('pigArgumentsPull.@each'),
-
-  /*
-    get script arguments string from model
-    and write to pigArgumentsPull as array
-  */
-  pigArgToArray:function (controller,observer) {
-    var args =  controller.get(observer);
-    if (args && args.length > 0){
-      controller.set('pigArgumentsPull',args.w());
-    }
-  }.observes('content.templetonArguments'),
-
-  /**
-   * available UDFs
-   * @return {App.Udf} promise
-   */
-  ufdsList:function () {
-    return this.store.find('udf');
-  }.property('udf'),
-
-});

+ 1 - 1
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/errorLog.js

@@ -19,5 +19,5 @@
 var App = require('app');
 
 App.PigErrorLogController = Ember.ObjectController.extend({
-  errorLog: null,
+  errorLog: 'No trace'
 });

+ 0 - 58
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/jobResults.js

@@ -1,58 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.JobResultsController = Em.ObjectController.extend({
-  getOutputProxy:function (output) {
-    var self = this,
-        promise,
-        result;
-
-    var host = this.store.adapterFor('application').get('host');
-    var namespace = this.store.adapterFor('application').get('namespace');
-    var jobId = this.get('content.id');
-    var url = [host, namespace,'jobs',jobId, 'results',output].join('/');
-
-    promise = new Ember.RSVP.Promise(function(resolve,reject){
-      return Em.$.getJSON(url).then(function (stdoutFile) {
-        resolve(stdoutFile.file);
-      },function (error) {
-        var responseText = JSON.parse(error.responseText);
-        self.send('showAlert', {'message': Em.I18n.t('job.alert.'+output+'_error',
-          {status:responseText.status, message:responseText.message}), status:'error', trace: responseText.trace});
-      })
-    });
-
-    return Ember.ObjectProxy.extend(Ember.PromiseProxyMixin).create({
-      promise: promise
-    });
-  },
-  checkStatus:function (argument) {
-    return (this.get('content.status')=='COMPLETED')?true:false;
-  },
-  stdout:function() {
-    return (this.checkStatus())?this.getOutputProxy('stdout'):null;
-  }.property(),
-  stderr:function() {
-    return (this.checkStatus())?this.getOutputProxy('stderr'):null;
-  }.property(),
-  exitcode:function() {
-    return (this.checkStatus())?this.getOutputProxy('exit'):null;
-  }.property()
-});

+ 47 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/confirmAway.js

@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.ConfirmAwayController = Ember.ObjectController.extend({
+  needs:['pig'],
+  waitingTransition:null,
+  actions:{
+    confirm:function () {
+      var transition = this.get('content');
+      var script = this.get('controllers.pig.activeScript');
+      this.get('controllers.pig').send('saveScript',script,this.saveCallback.bind(this));
+      this.set('waitingTransition',transition);
+    },
+    discard:function () {
+      var script = this.get('controllers.pig.activeScript');
+      var filePromise = script.get('pigScript');
+      var transition = this.get('content');
+      this.set('waitingTransition',transition);
+      filePromise.then(function (file) {
+        script.rollback();
+        file.rollback();
+        this.get('waitingTransition').retry();
+      }.bind(this))
+    }
+  },
+  saveCallback:function(response){
+    this.get('waitingTransition').retry();
+    this.send('showAlert', {'message':Em.I18n.t('scripts.alert.script_saved',{title: response[1].get('title')}),status:'success'});
+  }
+});

+ 5 - 6
contrib/views/pig/src/main/resources/ui/pig-web/app/views/pig/modal/confirmDelete.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/confirmDelete.js

@@ -18,12 +18,11 @@
 
 var App = require('app');
 
-App.ConfirmDeleteView = App.PigModalView.extend({
-  templateName:'pig/modal/confirmdelete',
+App.ConfirmDeleteController = Ember.ObjectController.extend({
+  needs:['pig'],
   actions:{
-    confirm:function(script) {
-      $(this.get('element')).find('.modal').modal('hide');
-      return this.controller.send('confirmdelete',script);
+    confirm:function () {
+      this.get('controllers.pig').send('confirmdelete',this.get('content'));
     }
-  },
+  }
 });

+ 52 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/createScript.js

@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.CreateScriptController = Ember.ObjectController.extend({
+  needs:['pigScripts'],
+  filePath:'',
+  clearFilePath:function () {
+    this.set('didInsertTtle',false);
+    this.set('filePath','');
+  }.observes('content'),
+  actions:{
+    confirm:function () {
+      var title = this.get('content.title');
+      if (!title) {
+        this.set('error', Em.I18n.t('scripts.modal.error_empty_title'));
+        return;
+      }
+      this.get('controllers.pigScripts').send('confirmcreate',this.get('content'),this.get('filePath'));
+    },
+    cancel:function (script) {
+      this.get('content').deleteRecord();
+    }
+  },
+  didInsertTtle:false,
+  titleError:function () {
+    var title = this.get('content.title');
+
+    if (!title && this.get('didInsertTtle')) {
+      return Em.I18n.t('scripts.modal.error_empty_title');
+    } else {
+      this.set('didInsertTtle',true);
+    }
+    return '';
+  }.property('content.title')
+});

+ 10 - 11
contrib/views/pig/src/main/resources/ui/pig-web/app/views/pig/modal/pigModal.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/createUdf.js

@@ -18,18 +18,17 @@
 
 var App = require('app');
 
-App.PigModalView = Ember.View.extend({
+App.CreateUdfController = Ember.ObjectController.extend({
+  needs:['pigUdfs'],
   actions:{
-    close:function() {
-      return $(this.get('element')).find('.modal').modal('hide');
+    confirm:function () {
+      this.get('controllers.pigUdfs').send('createUdf',this.get('content'),this.get('filePath'));
+    },
+    cancel:function () {
+      this.get('content').deleteRecord();
     }
   },
-  didInsertElement: function() {
-    var view = this;
-    $(this.get('element')).find('.modal').modal('show');
-    return this.$('.modal').on("hidden.bs.modal", function(ev) {
-      view.controller.send('closeModal');
-    });
-  },
-  layoutName:'pig/modal/modalLayout'
+  udfInvalid:function (argument) {
+    return !this.get('content.name') || !this.get('content.path');
+  }.property('content.name','content.path')
 });

+ 7 - 2
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigScriptList.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/deleteJob.js

@@ -18,6 +18,11 @@
 
 var App = require('app');
 
-App.PigScriptListController = Em.ArrayController.extend({
-
+App.DeleteJobController = Ember.ObjectController.extend({
+  needs:['script'],
+  actions:{
+    confirm:function () {
+      this.get('controllers.script').send('deleteJob',this.get('content'));
+    }
+  }
 });

+ 6 - 5
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigScriptEditIndex.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/gotoCopy.js

@@ -18,10 +18,11 @@
 
 var App = require('app');
 
-App.PigScriptEditIndexRoute = Em.Route.extend({
-  renderTemplate: function() {
-    this.render('pig/scriptEditIndex',{
-      outlet: 'main',
-    });
+App.GotoCopyController = Ember.ObjectController.extend({
+  needs:['pig'],
+  actions:{
+    confirm:function () {
+      this.transitionToRoute('script.edit',this.get('content.id'));
+    }
   }
 });

+ 18 - 15
contrib/views/pig/src/main/resources/ui/pig-web/app/views/pig/modal/createScript.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/logDownload.js

@@ -18,22 +18,25 @@
 
 var App = require('app');
 
-App.CreateScriptView = App.PigModalView.extend({
-  templateName: 'pig/modal/createScript',
+App.LogDownloadController = Ember.ObjectController.extend(App.FileHandler,{
+  jobLogsLoader:function (output) {
+    var jobId = this.get('content.id');
+    var url = ['jobs',jobId, 'results','stderr'].join('/');
+
+    return this.fileProxy(url);
+  }.property('content'),
+
+  suggestedFilename: function() {
+    return this.get("content.jobId").toLowerCase().replace(/\W+/g, "_") + '_logs.txt';
+  }.property("content.jobId"),
+
+  jobLogs:function () {
+    return this.get('jobLogsLoader.content.fileContent');
+  }.property("content","jobLogsLoader.isPending"),
+
   actions:{
-    create: function(script) {
-      var title = this.controller.get('title');
-      if (!title) {
-        this.controller.set('error', Em.I18n.t('scripts.modal.error_empty_title'))
-        return;
-      }
-      var filePath = this.controller.get('filePath');
-      $(this.get('element')).find('.modal').modal('hide');
-      return this.controller.send('confirmcreate',script,filePath);
-    },
-    close:function (script) {
-      script.deleteRecord();
-      return this._super();
+    download:function () {
+      return this.downloadFile(this.get("jobLogs"), this.get("suggestedFilename"));
     }
   }
 });

+ 40 - 1
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/pigModal.js

@@ -18,5 +18,44 @@
 
 var App = require('app');
 
-App.PigModalController = Ember.ObjectController.extend({ 
+App.PigModalComponent = Ember.Component.extend({
+  didClose:'removeModal',
+  size:'',
+  large:function () {
+    return this.get('size') =='lg';
+  }.property('size'),
+  small:function () {
+    return this.get('size') =='sm';
+  }.property('size'),
+  layoutName:'modal/modalLayout',
+  actions: {
+    ok: function() {
+      this.$('.modal').modal('hide');
+      this.sendAction('ok');
+    },
+    cancel:function () {
+      this.$('.modal').modal('hide');
+      this.sendAction('close');
+    },
+    option:function () {
+      this.$('.modal').modal('hide');
+      this.sendAction('option');
+    }
+  },
+  keyUp:function (e) {
+    if (e.keyCode == 27) {
+      this.sendAction('close');
+    }
+  },
+  show: function() {
+    var modal = this.$('.modal').modal();
+    modal.off('hidden.bs.modal');
+    modal.off('shown.bs.modal');
+    modal.on('shown.bs.modal',function () {
+      this.find('input').first().focus();
+    }.bind(modal));
+    modal.on('hidden.bs.modal', function() {
+      this.sendAction('didClose');
+    }.bind(this));
+  }.on('didInsertElement')
 });

+ 19 - 15
contrib/views/pig/src/main/resources/ui/pig-web/app/views/pig/modal/createUdf.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/modal/resultsDownload.js

@@ -18,21 +18,25 @@
 
 var App = require('app');
 
-App.CreateUdfView = App.PigModalView.extend({
-  templateName: 'pig/modal/createUdf',
+App.ResultsDownloadController = Ember.ObjectController.extend(App.FileHandler,{
+  jobResultsLoader:function (output) {
+    var jobId = this.get('content.id');
+    var url = ['jobs',jobId, 'results','stdout'].join('/');
+
+    return this.fileProxy(url);
+  }.property('content'),
+
+  suggestedFilename: function() {
+    return this.get("content.jobId").toLowerCase().replace(/\W+/g, "_") + '_results.txt';
+  }.property("content.jobId"),
+
+  jobResults:function () {
+    return this.get('jobResultsLoader.content.fileContent');
+  }.property("content","jobResultsLoader.isPending"),
+
   actions:{
-    createUdf: function(udf) {
-      $(this.get('element')).find('.modal').modal('hide');
-      return this.controller.send('createUdf',udf);
-    },
-    close:function (udf) {
-      udf.deleteRecord();
-      return this._super();
+    download:function () {
+      return this.downloadFile(this.get("jobResults"), this.get("suggestedFilename"));
     }
-  },
-  udfInvalid:function (argument) {
-    var udf = this.get('controller.content');
-    var invalid = !udf.get('name') || !udf.get('path');
-    return invalid;
-  }.property('controller.content.name','controller.content.path'),
+  }
 });

+ 5 - 1
contrib/views/pig/src/main/resources/ui/pig-web/app/views/pig/scriptResults.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/page.js

@@ -18,6 +18,10 @@
 
 var App = require('app');
 
-App.PigScriptResultsView = Em.View.extend({
+App.PageController = Ember.ObjectController.extend({
+  currentPage: Ember.computed.alias('parentController.page'),
 
+  isActive:function() {
+    return this.get('number') === this.get('currentPage');
+  }.property('number', 'currentPage')
 });

+ 80 - 3
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pig.js

@@ -19,8 +19,85 @@
 var App = require('app');
 
 App.PigController = Em.ArrayController.extend({
+  needs:['scriptEdit'],
   category: 'scripts',
-  openScripts: function() {
-    return this.get('content').filterBy('opened',true);
-  }.property('content.@each.opened')
+  actions:{
+    closeScript:function () {
+      this.transitionToRoute('pig.scripts');
+    },
+    saveScript: function (script,onSuccessCallback) {
+      var onSuccess = onSuccessCallback || function(model){
+          //this.set('disableScriptControls',false);
+          this.send('showAlert', {'message':Em.I18n.t('scripts.alert.script_saved',{title: script.get('title')}),status:'success'});
+        }.bind(this),
+        onFail = function(error){
+          var trace = null;
+          if (error && error.responseJSON.trace)
+            trace = error.responseJSON.trace;
+          this.send('showAlert', {'message':Em.I18n.t('scripts.alert.save_error'),status:'error',trace:trace});
+        }.bind(this);
+
+      return script.get('pigScript').then(function(file){
+        return Ember.RSVP.all([file.save(),script.save()]).then(onSuccess,onFail);
+      },onFail);
+    },
+    deletescript:function (script) {
+      return this.send('openModal','confirmDelete',script);
+    },
+    confirmdelete:function (script) {
+      var onSuccess = function(model){
+            this.transitionToRoute('pig.scripts');
+            this.send('showAlert', {'message':Em.I18n.t('scripts.alert.script_deleted',{title : model.get('title')}),status:'success'});
+          }.bind(this);
+      var onFail = function(error){
+            var trace = null;
+            if (error && error.responseJSON.trace)
+              trace = error.responseJSON.trace;
+            this.send('showAlert', {'message':Em.I18n.t('scripts.alert.delete_failed'),status:'error',trace:trace});
+          }.bind(this);
+      script.deleteRecord();
+      return script.save().then(onSuccess,onFail);
+    },
+    copyScript:function (script) {
+      script.get('pigScript').then(function (file) {
+
+        var newScript = this.store.createRecord('script',{
+          title:script.get('title')+' (copy)',
+          templetonArguments:script.get('templetonArguments')
+        });
+
+        newScript.save().then(function (savedScript) {
+          savedScript.get('pigScript').then(function (newFile) {
+            newFile.set('fileContent',file.get('fileContent'));
+            newFile.save().then(function () {
+              this.send('showAlert', {'message':script.get('title') + ' is copied.',status:'success'});
+              if (this.get('activeScript')) {
+                this.send('openModal','gotoCopy',savedScript);
+              }
+            }.bind(this));
+          }.bind(this));
+        }.bind(this));
+      }.bind(this));
+    }
+  },
+
+  activeScriptId:null,
+
+  disableScriptControls:Em.computed.alias('controllers.scriptEdit.isRenaming'),
+
+  activeScript:function () {
+    return (this.get('activeScriptId'))?this.get('content').findBy('id',this.get('activeScriptId').toString()):null;
+  }.property('activeScriptId',"content.[]"),
+
+  /*
+   *Is script or script file is dirty.
+   * @return {boolean}
+   */
+  scriptDirty:function () {
+    return this.get('activeScript.isDirty') || this.get('activeScript.pigScript.isDirty');
+  }.property('activeScript.pigScript.isDirty','activeScript.isDirty'),
+
+  saveEnabled:function () {
+    return this.get('scriptDirty') && !this.get('disableScriptControls');
+  }.property('scriptDirty','disableScriptControls')
 });

+ 5 - 5
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/util/pigUtilAlert.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigAlert.js

@@ -18,17 +18,17 @@
 
 var App = require('app');
 
-App.PigUtilAlertController = Ember.ArrayController.extend({
+App.PigAlertController = Ember.ArrayController.extend({
   content:Ember.A(),
   needs: ['pigErrorLog'],
   actions:{
     removeAlertObject:function (alert) {
       this.content.removeObject(alert)
     },
-    showErrorLog:function (content) {
+    showErrorLog:function (context) {
       errorLogController = this.get('controllers.pigErrorLog');
-      errorLogController.set('errorLog', content[0].trace);
+      errorLogController.set('errorLog', context.trace);
       this.transitionToRoute('pig.errorLog');
-    },
-  },
+    }
+  }
 });

+ 19 - 7
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigHistory.js

@@ -18,11 +18,23 @@
 
 var App = require('app');
 
-App.PigHistoryController = Em.ArrayController.extend({
-  content: [],
-  jobs: function() {
-    return this.get('content').filter(function(job, index, enumerable){
-      return job.get('status') != 'SUBMIT_FAILED';
-    });
-  }.property('content.@each.status')
+App.PigHistoryController = Em.ArrayController.extend(App.Pagination,{
+  needs:['pig'],
+  sortProperties: ['dateStarted'],
+  sortAscending: false,
+  scriptIds:Em.computed.mapBy('controllers.pig.content','id'),
+  actions:{
+    logsPopup:function (job) {
+      this.send('openModal','logDownload',job);
+    },
+    resultsPopup:function (job) {
+      this.send('openModal','resultsDownload',job);
+    },
+    goToScript:function (id) {
+      this.transitionToRoute('script.edit',id);
+    },
+    deleteJob:function (job) {
+      this.send('openModal','deleteJob',job);
+    }
+  }
 });

+ 0 - 68
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigScriptEdit.js

@@ -1,68 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.PigScriptEditController = App.EditController.extend({
-  needs:['pig'],
-  isScript:true,
-  pigParams:Em.A(),
-
-  /*
-    Updates nav title when renaming script.
-   */
-  updateNavTitle:function (controller) {
-    var pigC = controller.get('controllers.pig')
-    if (!($.inArray(pigC.get('category'),['udfs','scripts','history'])>=0)){
-      pigC.set('category',this.get('content.name'));
-    }
-  }.observes('content.name'),
-
-  /*
-    Observes params (%VAR%) in sript file 
-    and updates pigParams object as they changes. 
-   */
-  pigParamsMatch:function (controller) {
-    var editorContent = this.get('content.pigScript').then(function(data) {
-      editorContent = data.get('fileContent');
-      if (editorContent) {
-            var match_var = editorContent.match(/\%\w+\%/g);
-            if (match_var) {
-              var oldParams = controller.pigParams;
-              controller.set('pigParams',[]);
-              match_var.forEach(function (param) {
-                var suchParams = controller.pigParams.filterProperty('param',param);
-                if (suchParams.length == 0){
-                  var oldParam = oldParams.filterProperty('param',param);
-                  var oldValue = oldParam.get('firstObject.value');
-                  controller.pigParams.pushObject(Em.Object.create({param:param,value:oldValue,title:param.replace(/%/g,'')}));
-                }
-              });
-            } else {
-              controller.set('pigParams',[]);
-            }
-          };
-    }, function(reason) {
-      var trace = null;
-      if (reason.responseJSON.trace)
-        trace = reason.responseJSON.trace;
-      controller.send('showAlert', {'message':Em.I18n.t('scripts.alert.file_not_found',{title: 'Error'}),status:'error',trace:trace});
-    });
-  }.observes('content.pigScript.fileContent'),
-
-});

+ 84 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigScripts.js

@@ -0,0 +1,84 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.PigScriptsController = Em.ArrayController.extend(App.Pagination,{
+  sortProperties: ['dateCreatedUnix'],
+  sortAscending: false,
+  needs:['pig'],
+  actions:{
+    createScript:function () {
+      var newScript = this.store.createRecord('script');
+      return this.send('openModal', 'createScript', newScript);
+    },
+    confirmcreate:function (script,filePath) {
+      var _this = this;
+      var bnd = Em.run.bind;
+
+      if (filePath) {
+        try {
+          var file = this.store.createRecord('file',{
+            id:filePath,
+            fileContent:''
+          })
+        } catch (e) {
+          return this.createScriptError(script,e);
+        }
+        return file.save()
+          .then(function(file){
+              script.set('pigScript',file);
+              script.save().then(bnd(_this,_this.createScriptSuccess),bnd(_this,_this.createScriptError,script));
+            },
+            function () {
+              file.deleteRecord();
+              _this.store.find('file', filePath).then(function(file) {
+                _this.send('showAlert', {message:Em.I18n.t('scripts.alert.file_exist_error'),status:'success'});
+                script.set('pigScript',file);
+                script.save().then(bnd(_this,_this.createScriptSuccess),bnd(_this,_this.createScriptError,script));
+              },bnd(_this,_this.createScriptError,script));
+          });
+      } else {
+          script.save().then(bnd(this,this.createScriptSuccess),bnd(this,this.createScriptError,script));
+      }
+    },
+    deletescript:function (script) {
+      this.get('controllers.pig').send('deletescript',script);
+    },
+    copyScript:function (script) {
+      this.get('controllers.pig').send('copyScript',script);
+    }
+  },
+
+  createScriptSuccess:function (script,s) {
+    this.send('showAlert', { message:Em.I18n.t('scripts.alert.script_created',{title:script.get('title')}), status:'success' });
+    this.transitionToRoute('script.edit',script);
+  },
+  createScriptError:function (script,error) {
+    var trace = null;
+    trace = (error.responseJSON)?error.responseJSON.trace:error.message;
+
+    script.deleteRecord();
+
+    this.send('showAlert', {message:Em.I18n.t('scripts.alert.create_failed'),status:'error',trace:trace});
+  },
+
+  jobs:function () {
+    return this.store.find('job');
+  }.property()
+});

+ 46 - 2
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigUdfs.js

@@ -18,6 +18,50 @@
 
 var App = require('app');
 
-App.PigUdfsController = Em.ArrayController.extend({
-  
+App.PigUdfsController = Em.ArrayController.extend(App.Pagination,{
+  actions:{
+    createUdfModal:function () {
+      return this.send('openModal','createUdf',this.store.createRecord('udf'));
+    },
+    createUdf:function (udf) {
+      return udf.save().then(this.onCreateSuccess.bind(this),this.onCreateFail.bind(this));
+    },
+    deleteUdf:function(udf){
+      udf.deleteRecord();
+      return udf.save().then(this.onDeleteSuccess.bind(this),this.onDeleteFail.bind(this));
+    }
+  },
+  onCreateSuccess:function (model) {
+    this.send('showAlert', {
+      message: Em.I18n.t('udfs.alert.udf_created',{name : model.get('name')}),
+      status:'success'
+    });
+  },
+  onCreateFail:function (error) {
+    var trace = null;
+    if (error && error.responseJSON.trace) {
+      trace = error.responseJSON.trace;
+    }
+    this.send('showAlert', {
+      message:Em.I18n.t('udfs.alert.create_failed'),
+      status:'error',trace:trace
+    });
+  },
+  onDeleteSuccess: function(model){
+    this.send('showAlert', {
+      message: Em.I18n.t('udfs.alert.udf_deleted',{name : model.get('name')}),
+      status:'success'
+    });
+  },
+  onDeleteFail:function(error){
+    var trace = null;
+    if (error && error.responseJSON.trace)
+      trace = error.responseJSON.trace;
+
+    this.send('showAlert', {
+      message: Em.I18n.t('udfs.alert.delete_failed'),
+      status:'error',
+      trace:trace
+    });
+  }
 });

+ 0 - 59
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/poll.js

@@ -1,59 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.PollController = Ember.ObjectController.extend({
-  actions:{
-    killjob:function (job) {
-      var self = this;
-      job.kill(function () {
-        job.reload();
-        self.send('showAlert', {'message': Em.I18n.t('job.alert.job_killed',{title:self.get('title')}), status:'info'});
-      },function (reason) {
-        var trace = null;
-        if (reason && reason.responseJSON.trace)
-          trace = reason.responseJSON.trace;
-        self.send('showAlert', {'message': Em.I18n.t('job.alert.job_kill_error'), status:'error', trace:trace});
-      });
-    },
-  },
-  pollster:Em.Object.create({
-    job:null,
-    start: function(job){
-      this.stop();
-      this.set('job',job);
-      this.timer = setInterval(this.onPoll.bind(this), 5000);
-      console.log('START polling. Job: ',this.get('job.id'),'. Timer: ',this.timer)
-    },
-    stop: function(){
-      this.set('job',null);
-      clearInterval(this.timer);
-      console.log('STOP polling. Job: ',this.get('job.id'),'. Timer: ',this.timer)
-    },
-    onPoll: function() {
-      if (this.job.get('needsPing')) {
-        console.log('DO polling. Job: ',this.get('job.id'),'. Timer: ', this.timer)
-        this.job.reload();
-      } else {
-        console.log('END polling. Job: ',this.get('job.id'),'. Timer: ',this.timer)
-        this.stop();
-      };
-    }
-  })
-});

+ 117 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/script.js

@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.ScriptController = Em.ObjectController.extend({
+  actions :{
+    deactivateJob:function (jobId) {
+      var rejected = this.get('activeJobs').rejectBy('id',jobId);
+      this.set('activeJobs',rejected);
+      if (!this.get('controllers.pig.activeScript')) {
+        this.transitionToRoute('pig.history');
+      }
+      else if (this.get('activeTab') == jobId) {
+        this.transitionToRoute('script.edit',this.get('controllers.pig.activeScript.id'));
+      }
+    },
+    deleteJob:function (job) {
+      job.deleteRecord();
+      job.save().then(this.deleteJobSuccess.bind(this),Em.run.bind(this,this.deleteJobFailed,job));
+      this.get('activeJobs').removeObject(job);
+    }
+  },
+
+  deleteJobSuccess:function (data) {
+    this.send('showAlert', {message:Em.I18n.t('job.alert.job_deleted'),status:'info'})
+  },
+  deleteJobFailed:function (job,error) {
+    var trace = (error.responseJSON)?error.responseJSON.trace:null;
+    job.rollback();
+    this.send('showAlert', {message:Em.I18n.t('job.alert.delete_filed'),status:'error',trace:trace});
+  },
+
+  needs:['pig'],
+
+  activeTab: 'script',
+
+  isScript:true,
+
+  activeJobs:Em.A(),
+  activeJobsIds:Em.computed.mapBy('activeJobs','id'),
+
+  staticTabs:function () {
+    return [
+      {label:'Script',name:'script',url:'script.edit',target:this.get('controllers.pig.activeScript.id')},
+      {label:'History',name:'history',url:'script.history',target:this.get('controllers.pig.activeScript.id')},
+    ];
+  }.property('controllers.pig.activeScript.id'),
+
+  jobTabs:function () {
+    var jobTabs = [];
+    this.get('activeJobs').forEach(function (job) {
+      jobTabs.push({label:job.get('title') + ' - ' + job.get('status').decamelize().capitalize(), name:job.get('id'),url:'script.job',target:job.get('id')})
+    });
+    return jobTabs;
+  }.property('activeJobs.[]','activeJobs.@each.status'),
+
+  tabs:Em.computed.union('staticTabs','jobTabs'),
+
+  pollster:Em.Object.create({
+    jobs:[],
+    timer:null,
+    start: function(jobs){
+      this.stop();
+      this.set('jobs',jobs);
+      this.onPoll();
+    },
+    stop: function(){
+      Em.run.cancel(this.get('timer'))
+    },
+    onPoll: function() {
+      this.get('jobs').forEach(function (job) {
+        if (job.get('needsPing')) {
+          job.reload();
+        } else {
+          this.jobs.removeObject(job)
+        };
+      }.bind(this));
+
+      if (this.get('jobs.length') > 0) {
+        this.set('timer', Ember.run.later(this, function() {
+          this.onPoll();
+        }, 3000));
+      }
+    }
+  }),
+
+  pollingWatcher:function () {
+    var pollster = this.get('pollster');
+    if (this.get('activeJobs.length') > 0) {
+      pollster.start(this.get('activeJobs').copy());
+    } else {
+      pollster.stop();
+    }
+  }.observes('activeJobs.@each'),
+
+  activeJobsWatcher:function () {
+    if (this.get('activeJobs.firstObject.scriptId') != this.get('controllers.pig.activeScript.id')) {
+      this.set('activeJobs',[]);
+    };
+  }.observes('controllers.pig.activeScript.id')
+});

+ 208 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/scriptEdit.js

@@ -0,0 +1,208 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.ScriptEditController = Em.ObjectController.extend({
+  needs:['script'],
+  pigParams: Em.A(),
+  isExec:false,
+  isRenaming:false,
+  titleWarn:false,
+  tmpArgument:'',
+  editor:null,
+
+  handleRenaming:function () {
+    if (this.get('content.title')) {
+      this.set('titleWarn',false);
+    }
+  }.observes('content.title','isRenaming'),
+
+  pigParamsMatch:function (controller) {
+    editorContent = this.get('content.pigScript.fileContent');
+    if (editorContent) {
+      var match_var = editorContent.match(/\%\w+\%/g);
+      if (match_var) {
+        var oldParams = controller.pigParams;
+        controller.set('pigParams',[]);
+        match_var.forEach(function (param) {
+          var suchParams = controller.pigParams.filterProperty('param',param);
+          if (suchParams.length == 0){
+            var oldParam = oldParams.filterProperty('param',param);
+            var oldValue = oldParam.get('firstObject.value');
+            controller.pigParams.pushObject(Em.Object.create({param:param,value:oldValue,title:param.replace(/%/g,'')}));
+          }
+        });
+      } else {
+        controller.set('pigParams',[]);
+      }
+    } else {
+      controller.set('pigParams',[]);
+    };
+  }.observes('content.pigScript.fileContent','content.id'),
+
+
+  oldTitle:'',
+  actions: {
+    rename:function (opt) {
+      var changedAttributes = this.get('content').changedAttributes()
+
+      if (opt==='ask') {
+        this.set('oldTitle',this.get('content.title'));
+        this.set('isRenaming',true);
+      };
+
+      if (opt==='cancel') {
+        this.set('content.title',this.get('oldTitle'));
+        this.set('oldTitle','');
+        this.set('isRenaming',false);
+      };
+
+      if (opt==='confirm') {
+        if (Em.isArray(changedAttributes.title) && this.get('content.title')) {
+          this.get('content').save().then(function () {
+            this.send('showAlert', {message:Em.I18n.t('editor.title_updated'),status:'success'});
+          }.bind(this));
+        }
+        this.set('oldTitle','');
+        this.set('isRenaming',false);
+      }
+    },
+    addArgument:function () {
+      var arg = this.get('tmpArgument');
+      if (!arg) {
+        return false;
+      }
+      var pull = this.get('content.argumentsArray');
+      if (Em.$.inArray(arg,pull)<0) {
+        pull.pushObject(arg);
+        this.set('content.argumentsArray',pull);
+        this.set('tmpArgument','');
+      } else {
+        this.send('showAlert', {'message': Em.I18n.t('scripts.alert.arg_present'), status:'info'});
+      }
+    },
+    removeArgument:function (arg) {
+      var removed = this.get('content.argumentsArray').removeObject(arg);
+      this.set('content.argumentsArray',removed);
+    },
+    execute: function (script, operation) {
+      var executeMethod = {
+        'execute':this.prepareExecute,
+        'explain':this.prepareExplain,
+        'syntax_check':this.prepareSyntaxCheck
+      };
+
+      this.set('isExec',true);
+
+      return Ember.RSVP.resolve(script.get('pigScript'))
+        .then(function (file) {
+          return Ember.RSVP.all([file.save(),script.save()]);
+        })
+        .then(executeMethod[operation].bind(this))
+        .then(this.executeSuccess.bind(this), this.executeError.bind(this))
+        .finally(Em.run.bind(this,this.set,'isExec',false));
+    }
+  },
+
+  executeSuccess:function (job) {
+    this.send('showAlert', {message:Em.I18n.t('job.alert.job_started'),status:'success'});
+    if (this.target.isActive('script.edit')) {
+      Em.run.next(this,this.transitionToRoute,'script.job',job);
+    }
+  },
+
+  executeError:function (error) {
+    var trace = (error.responseJSON)?error.responseJSON.trace:null;
+    this.send('showAlert', {message:Em.I18n.t('job.alert.start_filed'),status:'error',trace:trace});
+  },
+
+  prepareExecute:function (data) {
+    var file = data[0], script = data[1], pigParams = this.get('pigParams') || [], fileContent = file.get('fileContent');
+
+    pigParams.forEach(function (param) {
+      var rgParam = new RegExp(param.param,'g');
+      fileContent = fileContent.replace(rgParam,param.value);
+    });
+
+    var job = this.store.createRecord('job',{
+      scriptId: script.get('id'),
+      templetonArguments: script.get('templetonArguments'),
+      title: script.get('title'),
+      forcedContent: (pigParams.length > 0)? fileContent:null,
+      pigScript: (pigParams.length == 0)?file:null
+    });
+
+    return job.save();
+  },
+  prepareExplain:function (data) {
+    var file = data[0], script = data[1], pigParams = this.get('pigParams') || [], fileContent = file.get('fileContent');
+
+    pigParams.forEach(function (param) {
+      var rgParam = new RegExp(param.param,'g');
+      fileContent = fileContent.replace(rgParam,param.value);
+    });
+
+    var job = this.store.createRecord('job',{
+      scriptId: script.get('id'),
+      templetonArguments: '',
+      title: 'Explain: "' + script.get('title') + '"',
+      jobType: 'explain',
+      sourceFileContent: (pigParams.length > 0)? fileContent:null,
+      sourceFile: (pigParams.length == 0)?file.get('id'):null,
+      forcedContent: 'explain -script ${sourceFile}'
+    });
+
+    return job.save();
+  },
+  prepareSyntaxCheck:function (data) {
+    var file = data[0], script = data[1], pigParams = this.get('pigParams') || [], fileContent = file.get('fileContent'), args = script.get('templetonArguments');
+
+    pigParams.forEach(function (param) {
+      var rgParam = new RegExp(param.param,'g');
+      fileContent = fileContent.replace(rgParam,param.value);
+    });
+
+    var job = this.store.createRecord('job',{
+      scriptId: script.get('id'),
+      templetonArguments: (!args.match(/-check/g))?args+(args?"\t":"")+'-check':args,
+      title: 'Syntax check: "' + script.get('title') + '"',
+      jobType:  'syntax_check',
+      forcedContent: (pigParams.length > 0)? fileContent:null,
+      pigScript: (pigParams.length == 0)?file:null
+    });
+
+    return job.save();
+  },
+
+  /**
+   * Is script is in error state.
+   * @return {boolean}
+   */
+  scriptError:function () {
+    return this.get('content.isError');
+  }.property('content.isError'),
+
+  /**
+   * available UDFs
+   * @return {App.Udf} promise
+   */
+  ufdsList:function () {
+    return this.store.find('udf');
+  }.property('udf')
+});

+ 11 - 7
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/pigJob.js → contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/scriptHistory.js

@@ -18,14 +18,18 @@
 
 var App = require('app');
 
-
-App.JobController = App.EditController.extend({
-  needs:['poll'],
-  isJob:true,
-  category:'edit',
+App.ScriptHistoryController = Em.ArrayController.extend(App.Pagination,{
+  sortProperties: ['dateStarted'],
+  sortAscending: false,
   actions:{
-    returnToOriginal: function (job) {
-      this.transitionToRoute('pig.scriptEdit', job.get('scriptId'));
+    logsPopup:function (job) {
+      this.send('openModal','logDownload',job);
+    },
+    resultsPopup:function (job) {
+      this.send('openModal','resultsDownload',job);
     },
+    deleteJob:function (job) {
+      this.send('openModal','deleteJob',job);
+    }
   }
 });

+ 65 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/controllers/scriptJob.js

@@ -0,0 +1,65 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.ScriptJobController = Em.ObjectController.extend(App.FileHandler,{
+  scriptContents:function () {
+    var promise = new Ember.RSVP.Promise(function(resolve,reject){
+      return this.get('content.pigScript').then(function (pigScript) {
+        return resolve(pigScript);
+      },function (error) {
+        var response = (error.responseJSON)?error.responseJSON:{};
+        reject(response.message);
+        if (error.status != 404) {
+          controller.send('showAlert', {'message': Em.I18n.t('job.alert.promise_error',
+            {status:response.status, message:response.message}), status:'error', trace: response.trace});
+        }
+      }.bind(this));
+    }.bind(this));
+    return Ember.ObjectProxy.extend(Ember.PromiseProxyMixin).create({
+      promise: promise
+    });
+  }.property('content'),
+
+  jobResults:function (output) {
+    var jobId = this.get('content.id');
+    var url = ['jobs', jobId, 'results', 'stdout'].join('/');
+
+    return this.fileProxy(url);
+  }.property('content'),
+
+  jobLogs:function (output) {
+    var jobId = this.get('content.id');
+    var url = ['jobs', jobId, 'results', 'stderr'].join('/');
+
+    return this.fileProxy(url);
+  }.property('content'),
+
+  suggestedFilenamePrefix: function() {
+    return this.get("content.jobId").toLowerCase().replace(/\W+/g, "_");
+  }.property("content.jobId"),
+
+  actions:{
+    download:function (opt) {
+      var file = (opt == 'results')?'jobResults.content.fileContent':'jobLogs.content.fileContent';
+      var suffix = (opt == 'results')?'_results.txt':'_logs.txt';
+      return this.downloadFile(this.get(file), this.get("suggestedFilenamePrefix")+suffix);
+    }
+  }
+});

+ 112 - 105
contrib/views/pig/src/main/resources/ui/pig-web/app/initialize.js

@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-var App = require('app');
+window.App = require('app');
 
 App.ApplicationAdapter = DS.RESTAdapter.extend({
   namespace: App.getNamespaceUrl(),
@@ -28,11 +28,18 @@ App.ApplicationAdapter = DS.RESTAdapter.extend({
 App.FileAdapter = App.ApplicationAdapter.extend({
   pathForType: function() {
     return 'resources/file';
-  },
+  }
+});
+
+App.JobAdapter = App.ApplicationAdapter.extend({
+  deleteRecord: function (store, type, record)  {
+    var id = record.get('id');
+    return this.ajax(this.buildURL(type.typeKey, id)+ '?remove=true', "DELETE");
+  }
 });
 
 App.FileSerializer = DS.RESTSerializer.extend({
-  primaryKey:'filePath',
+  primaryKey:'filePath'
 });
 
 App.IsodateTransform = DS.Transform.extend({  
@@ -50,114 +57,114 @@ App.IsodateTransform = DS.Transform.extend({
   }
 });
 
+App.FileSaver = Ember.Object.extend({
+  save: function(fileContents, mimeType, filename) {
+    window.saveAs(new Blob([fileContents], {type: mimeType}), filename);
+  }
+});
+
+App.register('lib:fileSaver', App.FileSaver);
+
+
+
 Ember.Handlebars.registerBoundHelper('showDate', function(date,format) {
   return moment(date).format(format)
 });
 
 Em.TextField.reopen(Em.I18n.TranslateableAttributes)
 
-//////////////////////////////////
-// Templates
-//////////////////////////////////
-
 require('translations');
+require('router');
 
-require('templates/application');
-require('templates/index');
-require('templates/pig/loading');
-
-require('templates/pig');
-require('templates/pig/index');
-require('templates/pig/scriptList');
-require('templates/pig/scriptEdit');
-require('templates/pig/scriptEditIndex');
-require('templates/pig/scriptResults');
-require('templates/pig/scriptResultsNav');
-require('templates/pig/job');
-require('templates/pig/jobEdit');
-require('templates/pig/jobStatus');
-require('templates/pig/jobResults');
-require('templates/pig/jobResultsOutput');
-require('templates/pig/history');
-require('templates/pig/udfs');
-require('templates/pig/errorLog');
-
-require('templates/pig/util/script-nav');
-require('templates/pig/util/alert');
-require('templates/pig/util/alert-content');
-require('templates/pig/util/pigHelper');
-require('templates/pig/modal/confirmdelete');
-require('templates/pig/modal/createUdf');
-require('templates/pig/modal/modalLayout');
-require('templates/pig/modal/createScript');
-
-require('templates/splash');
-
-//////////////////////////////////
-// Models
-//////////////////////////////////
-
-require('models/pig_script');
-require('models/pig_job');
-require('models/file');
-require('models/udf');
-
-/////////////////////////////////
-// Controllers
-/////////////////////////////////
-
-require('controllers/pig');
-require('controllers/poll');
-require('controllers/edit');
-require('controllers/pigScriptEdit');
-require('controllers/pigScriptList');
-require('controllers/pigScriptEditResults');
-require('controllers/pigUdfs');
-require('controllers/pigHistory');
-require('controllers/pigJob');
-require('controllers/jobResults');
-require('controllers/splash');
-require('controllers/errorLog');
-require('controllers/util/pigUtilAlert');
-require('controllers/modal/pigModal');
-
-/////////////////////////////////
-// Views
-/////////////////////////////////
-
-require('views/pig');
-require('views/pig/scriptList');
-require('views/pig/scriptEdit');
-require('views/pig/scriptResults');
-require('views/pig/scriptResultsNav');
-require('views/pig/pigHistory');
-require('views/pig/pigUdfs');
-require('views/pig/pigJob');
-require('views/pig/jobResults');
-require('views/pig/modal/pigModal');
-require('views/pig/modal/confirmDelete');
-require('views/pig/modal/createUdf');
-require('views/pig/modal/createScript');
-require('views/pig/util/pigUtilAlert');
-
-/////////////////////////////////
-// Routes
-/////////////////////////////////
-
-require('routes/pig');
-require('routes/pigHistory');
-require('routes/pigIndex');
-require('routes/pigScriptEdit');
-require('routes/pigScriptEditIndex');
-require('routes/pigScriptEditResults');
-require('routes/pigScriptList');
-require('routes/pigUdfs');
-require('routes/pigJob');
-require('routes/jobResults');
-require('routes/splash');
-
-/////////////////////////////////
-// Router
-/////////////////////////////////
 
-require('router');
+// mixins
+require("mixins/fileHandler");
+require("mixins/pagination");
+
+//routes
+require("routes/pig");
+require("routes/pigHistory");
+require("routes/pigScripts");
+require("routes/pigUdfs");
+require("routes/script");
+require("routes/scriptEdit");
+require("routes/scriptHistory");
+require("routes/scriptJob");
+require("routes/splash");
+
+//models
+require("models/file");
+require("models/pig_job");
+require("models/pig_script");
+require("models/udf");
+
+//views
+require("views/pig");
+require("views/pig/alert");
+require("views/pig/history");
+require("views/pig/loading");
+require("views/pig/scripts");
+require("views/pig/udfs");
+require("views/script/edit");
+require("views/script/job");
+
+//controllers
+require("controllers/errorLog");
+require("controllers/modal/confirmAway");
+require("controllers/modal/confirmDelete");
+require("controllers/modal/deleteJob");
+require("controllers/modal/createScript");
+require("controllers/modal/createUdf");
+require("controllers/modal/gotoCopy");
+require("controllers/modal/logDownload");
+require("controllers/modal/pigModal");
+require("controllers/modal/resultsDownload");
+require("controllers/page");
+require("controllers/pig");
+require("controllers/pigAlert");
+require("controllers/pigHistory");
+require("controllers/pigScripts");
+require("controllers/pigUdfs");
+require("controllers/script");
+require("controllers/scriptEdit");
+require("controllers/scriptHistory");
+require("controllers/scriptJob");
+require("controllers/splash");
+
+//templates
+require("templates/application");
+require("templates/components/pigHelper");
+require("templates/components/scriptListRow");
+require("templates/loading");
+require("templates/modal/confirmAway");
+require("templates/modal/confirmDelete");
+require("templates/modal/createScript");
+require("templates/modal/deleteJob");
+require("templates/modal/createUdf");
+require("templates/modal/gotoCopy");
+require("templates/modal/logDownload");
+require("templates/modal/modalLayout");
+require("templates/modal/resultsDownload");
+require("templates/partials/alert-content");
+require("templates/partials/paginationControls");
+require("templates/pig");
+require("templates/pig/alert");
+require("templates/pig/errorLog");
+require("templates/pig/history");
+require("templates/pig/loading");
+require("templates/pig/scripts");
+require("templates/pig/udfs");
+require("templates/script");
+require("templates/script/edit");
+require("templates/script/history");
+require("templates/script/job");
+require("templates/splash");
+require('templates/error');
+
+//components
+require("components/codeMirror");
+require("components/helpers-data");
+require("components/jobProgress");
+require("components/pigHelper");
+require("components/scriptListRow");
+require("components/tabControl");

+ 54 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/mixins/fileHandler.js

@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+var _promise = function (controller, url, output) {
+  return new Ember.RSVP.Promise(function(resolve,reject){
+    return Em.$.getJSON(url).then(function (data) {
+      resolve(data.file);
+    },function (error) {
+      var response = (error.responseJSON)?error.responseJSON:{};
+      reject(response.message);
+      if (error.status != 404) {
+        controller.send('showAlert', {'message': Em.I18n.t('job.alert.promise_error',
+          {status:response.status, message:response.message}), status:'error', trace: response.trace});
+      }
+    })
+  });
+};
+
+App.FileHandler = Ember.Mixin.create({
+  fileProxy:function (url) {
+    var promise,
+        host = this.store.adapterFor('application').get('host');
+        namespace = this.store.adapterFor('application').get('namespace');
+
+    url = [host, namespace, url].join('/');
+    promise = _promise(this, url,'stdout');
+
+    return Ember.ObjectProxy.extend(Ember.PromiseProxyMixin).create({
+      promise: promise
+    });
+  },
+  downloadFile:function (file,saveAs) {
+    return this.fileSaver.save(file, "application/json", saveAs);
+  }
+});
+
+App.inject('controller', 'fileSaver', 'lib:fileSaver');

+ 99 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/mixins/pagination.js

@@ -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.
+ */
+
+var App = require('app');
+
+App.Pagination = Ember.Mixin.create({
+  actions:{
+    selectPage: function(number) {
+      this.set('page', number);
+    },
+
+    toggleOrder: function() {
+      this.toggleProperty('sortAscending');
+    }
+  },
+
+  page: 1,
+
+  perPage: 10,
+
+  perPageOptions:[10,25,50,100],
+
+  pageWatcher:function () {
+    if (this.get('page') > this.get('totalPages')) {
+      this.set('page',this.get('totalPages') || 1);
+    };
+  }.observes('totalPages'),
+
+  totalPages: function() {
+    return Math.ceil(this.get('length') / this.get('perPage'));
+  }.property('length', 'perPage'),
+
+  pages: function() {
+    var collection = Ember.A();
+
+    for(var i = 0; i < this.get('totalPages'); i++) {
+      collection.pushObject(Ember.Object.create({
+        number: i + 1
+      }));
+    }
+
+    return collection;
+  }.property('totalPages'),
+
+  hasPages: function() {
+    return this.get('totalPages') > 1;
+  }.property('totalPages'),
+
+  prevPage: function() {
+    var page = this.get('page');
+    var totalPages = this.get('totalPages');
+
+    if(page > 1 && totalPages > 1) {
+      return page - 1;
+    } else {
+      return null;
+    }
+  }.property('page', 'totalPages'),
+
+  nextPage: function() {
+    var page = this.get('page');
+    var totalPages = this.get('totalPages');
+
+    if(page < totalPages && totalPages > 1) {
+      return page + 1;
+    } else {
+      return null;
+    }
+  }.property('page', 'totalPages'),
+
+
+  paginatedContent: function() {
+    var start = (this.get('page') - 1) * this.get('perPage');
+    var end = start + this.get('perPage');
+
+    return this.get('arrangedContent').slice(start, end);
+  }.property('page', 'totalPages', 'arrangedContent.[]'),
+
+  paginationInfo: function () {
+    var start = (this.get('page') - 1) * this.get('perPage') + 1;
+    var end = start + this.get('paginatedContent.length') - 1;
+    return start + ' - ' + end + ' of ' + this.get('arrangedContent.length')
+  }.property('page', 'arrangedContent.length', 'perPage')
+});

+ 29 - 2
contrib/views/pig/src/main/resources/ui/pig-web/app/models/pig_job.js

@@ -26,6 +26,10 @@ App.Job = DS.Model.extend({
   templetonArguments:DS.attr('string'),
   owner: DS.attr('string'),
   forcedContent:DS.attr('string'),
+  duration: DS.attr('number'),
+  durationTime:function () {
+    return moment.duration(this.get('duration'), "seconds").format("h [hrs], m [min], s [sec]");
+  }.property('duration'),
 
   sourceFile:DS.attr('string'),
   sourceFileContent:DS.attr('string'),
@@ -39,7 +43,7 @@ App.Job = DS.Model.extend({
   percentStatus:function () {
     if (this.get('isTerminated')) {
       return 100;
-    };
+    }
     return (this.get('status')==='COMPLETED')?100:(this.get('percentComplete')||0);
   }.property('status','percentComplete'),
 
@@ -86,5 +90,28 @@ App.Job = DS.Model.extend({
   },
   needsPing:function () {
     return this.pingStatusMap[this.get('status')];
-  }.property('status')
+  }.property('status'),
+
+  jobSuccess:function () {
+    return this.get('status') == 'COMPLETED';
+  }.property('status'),
+
+  jobError:function () {
+    return this.get('status') == 'SUBMIT_FAILED' || this.get('status') == 'KILLED' || this.get('status') == 'FAILED';
+  }.property('status'),
+
+  jobInProgress:function () {
+    return this.get('status') == 'SUBMITTING' || this.get('status') == 'SUBMITTED' || this.get('status') == 'RUNNING';
+  }.property('status'),
+
+  argumentsArray:function (q,w) {
+    if (arguments.length >1) {
+      var oldargs = (this.get('templetonArguments'))?this.get('templetonArguments').w():[];
+      if (w.length != oldargs.length) {
+        this.set('templetonArguments',w.join('\t'));
+      }
+    }
+    var args = this.get('templetonArguments');
+    return (args && args.length > 0)?args.w():[];
+  }.property('templetonArguments')
 });

+ 17 - 12
contrib/views/pig/src/main/resources/ui/pig-web/app/models/pig_script.js

@@ -18,18 +18,8 @@
 
 var App = require('app');
 
-App.OpenedScript = Ember.Mixin.create({
-  opened:DS.attr('boolean'),
-  open:function (argument) {
-    return this.set('opened',true);
-  },
-  close:function (argument) {
-    return this.set('opened',false);
-  }
-});
-
-App.Script = DS.Model.extend(App.OpenedScript,{
-  title:DS.attr('string', { defaultValue: 'New script'}),
+App.Script = DS.Model.extend({
+  title:DS.attr('string'),
   pigScript:DS.belongsTo('file', { async: true }),
   dateCreated:DS.attr('isodate', { defaultValue: moment()}),
   templetonArguments:DS.attr('string', { defaultValue: '-useHCatalog'}),
@@ -40,4 +30,19 @@ App.Script = DS.Model.extend(App.OpenedScript,{
   label:function (){
     return this.get('title');
   }.property('title'),
+
+  argumentsArray:function (q,w) {
+    if (arguments.length >1) {
+      var oldargs = (this.get('templetonArguments'))?this.get('templetonArguments').w():[];
+      if (w.length != oldargs.length) {
+        this.set('templetonArguments',w.join('\t'));
+      }
+    }
+    var args = this.get('templetonArguments');
+    return (args && args.length > 0)?args.w():[];
+  }.property('templetonArguments'),
+
+  dateCreatedUnix:function () {
+    return moment(this.get('dateCreated')).unix();
+  }.property('dateCreated')
 });

+ 5 - 8
contrib/views/pig/src/main/resources/ui/pig-web/app/router.js

@@ -20,10 +20,11 @@ var App = require('app');
 
 App.Router.map(function () {
   this.resource('pig', { path: "/" }, function() {
-    this.route('scriptList',{ path: "/list" });
-    this.route('scriptEdit',{ path: "/edit/:script_id" });
-    this.resource('job', { path: "/job/:job_id" },function (argument) {
-      this.route('results');
+    this.route('scripts', { path: "/list" });
+    this.resource('script', { path: "/script" }, function () {
+      this.route('edit', { path: "/edit/:script_id" });
+      this.route('history', { path: "/history/:script_id" });
+      this.route('job', { path: "/job/:job_id" });
     });
     this.route('udfs');
     this.route('history');
@@ -31,7 +32,3 @@ App.Router.map(function () {
   });
   this.route('splash');
 });
-
-App.LoadingView = Em.View.extend({
-    templateName: 'pig/loading'
-});

+ 27 - 28
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pig.js

@@ -23,7 +23,7 @@ App.PigRoute = Em.Route.extend({
     App.set('previousTransition', transition);
   },
   redirect: function () {
-    testsConducted = App.get("smokeTests");
+    var testsConducted = App.get("smokeTests");
     if (!testsConducted) {
         App.set("smokeTests", true);
         this.transitionTo('splash');
@@ -34,38 +34,19 @@ App.PigRoute = Em.Route.extend({
       var location = (nav.hasOwnProperty('url'))?[nav.url]:['pig.scriptEdit',nav.get('id')];
       this.transitionTo.apply(this,location);
     },
-    close:function (script) {
-      var self = this;
-      script.close().save().then(function() {
-        if (self.get('controller.category') == script.get('name')) {
-          opened = self.get('controller.openScripts');
-          if (opened.length > 0 && opened.filterBy('id',script.get('id')).length == 0){
-            self.transitionTo('pig.scriptEdit',opened.get(0));
-          } else {
-            self.transitionTo('pig.scriptList');
-          }
-        }
-        self.send('showAlert', {'message':Em.I18n.t('scripts.alert.script_saved',{title: script.get('title')}), status:'success'});
-      },function (error) {
-        //script.open();
-        var trace = null;
-        if (error && error.responseJSON.trace)
-          trace = error.responseJSON.trace;
-        self.send('showAlert', {'message': Em.I18n.t('scripts.alert.save_error_reason',{message:error.statusText}) , status:'error', trace:trace});
-      });
-    },
     showAlert:function (alert) {
-      var pigUtilAlert = this.controllerFor('pigUtilAlert');
-      return pigUtilAlert.content.pushObject(Em.Object.create(alert));
+      var pigAlert = this.controllerFor('pigAlert');
+      return pigAlert.content.pushObject(Em.Object.create(alert));
     },
-    openModal: function(modalName,controller) {
-      return this.render(modalName, {
+    openModal: function(modal,content) {
+      this.controllerFor(modal).set('content', content);
+      return this.render(['modal',modal].join('/'), {
         into: 'pig',
         outlet: 'modal',
-        controller:'pigModal'
+        controller:modal
       });
     },
-    closeModal: function() {
+    removeModal: function() {
       return this.disconnectOutlet({
         outlet: 'modal',
         parentView: 'pig'
@@ -77,6 +58,24 @@ App.PigRoute = Em.Route.extend({
   },
   renderTemplate: function() {
     this.render('pig');
-    this.render('pig/util/alert', {into:'pig',outlet:'alert',controller: 'pigUtilAlert' });
+    this.render('pig/alert', {into:'pig',outlet:'alert',controller:'pigAlert'});
+  }
+});
+
+App.PigIndexRoute = Em.Route.extend({
+  redirect:function () {
+    this.transitionTo('pig.scripts');
+  }
+});
+
+App.ErrorRoute = Ember.Route.extend({
+  setupController:function (controller,error) {
+    var data;
+    if(!(error instanceof Error)) {
+      data = JSON.parse(error.responseText);
+    } else {
+      data = error;
+    }
+    controller.set('model',data);
   }
 });

+ 14 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigHistory.js

@@ -19,10 +19,24 @@
 var App = require('app');
 
 App.PigHistoryRoute = Em.Route.extend({
+  actions:{
+    error:function (error) {
+      this.controllerFor('pig').set('category',"");
+      var trace = (error.responseJSON)?error.responseJSON.trace:null;
+      this.send('showAlert', {message:Em.I18n.t('history.load_error'),status:'error',trace:trace});
+    }
+  },
   enter: function() {
     this.controllerFor('pig').set('category',"history");
   },
   model: function() {
     return this.store.find('job');
+  },
+  setupController:function (controller,model) {
+    var scripts = this.modelFor('pig');
+    var filtered = model.filter(function(job) {
+      return job.get('status') != 'SUBMIT_FAILED' && scripts.get('content').isAny('id',job.get('scriptId').toString());
+    });
+    controller.set('model',model);
   }
 });

+ 0 - 74
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigScriptEdit.js

@@ -1,74 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.PigScriptEditRoute = Em.Route.extend({
-  actions:{
-    willTransition: function(transition){
-      var model = this.controller.get('model');
-      if (model.get('isDirty') || model.get('pigScript.isDirty')) {
-        return this.send('saveScript',model);
-      };
-    },
-    toresults:function (argument) {
-      // DUMMY TRANSITION
-      this.transitionTo('pigScriptEdit.results',argument);
-    },
-    saveScript: function (script) {
-      var router = this,
-        onSuccess = function(model){
-          router.send('showAlert', {'message':Em.I18n.t('scripts.alert.script_saved',{title: script.get('title')}),status:'success'});
-        },
-        onFail = function(error){
-          var trace = null;
-          if (error && error.responseJSON.trace)
-            trace = error.responseJSON.trace;
-          router.send('showAlert', {'message':Em.I18n.t('scripts.alert.save_error'),status:'error',trace:trace});
-        };
-
-      return script.get('pigScript').then(function(file){
-        return Ember.RSVP.all([file.save(),script.save()]).then(onSuccess,onFail);
-      },onFail);
-    },
-  },
-  isExec:false,
-  model: function(params) {
-    var record;
-    var isExist = this.store.all('script').some(function(script) {
-      return script.get('id') === params.script_id;
-    });
-    if (isExist) { 
-      record = this.store.find('script',params.script_id);
-    } else {
-      record = this.store.createRecord('script');
-    }
-    return record;
-  },
-  afterModel:function  (model) {
-    if (model.get('length') == 0) {
-      this.transitionTo('pig');
-    }
-    this.controllerFor('pig').set('category', model.get('name'));
-    model.open();
-  },
-  renderTemplate: function() {
-    this.render('pig/scriptEdit');
-  }
-});
-

+ 0 - 93
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigScriptList.js

@@ -1,93 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.PigScriptListRoute = Em.Route.extend({
-  actions:{
-    createScript:function () {
-      var newScript = this.store.createRecord('script');
-      this.controllerFor('pigModal').set('content', newScript);
-      return this.send('openModal','createScript');
-      
-    },
-    deletescript:function (script) {
-      this.controllerFor('pigModal').set('content', script);
-      return this.send('openModal','confirmDelete');
-    },
-    confirmcreate:function (script,filePath) {
-      // /tmp/.pigscripts/admin/39.pig
-      var route = this;
-      var sendAlert = function (status) {
-        var alerts = {
-          success:Em.I18n.t('scripts.alert.script_created',{title:'New script'}), 
-          error: Em.I18n.t('scripts.alert.create_failed')
-        };
-        return function (data) {
-          var trace = null;
-          if (status=='error'){
-            script.deleteRecord();
-            trace = data.responseJSON.trace;
-          }
-          route.send('showAlert', {message:alerts[status],status:status,trace:trace});
-        };
-      };
-      if (filePath) {
-        var file = this.store.createRecord('file',{
-          id:filePath,
-          fileContent:''
-        });
-        return file.save().then(function(file){
-          script.set('pigScript',file);
-          script.save().then(sendAlert('success'),sendAlert('error'));
-        },function () {
-          file.deleteRecord();
-          route.store.find('file', filePath).then(function(file) {
-            route.send('showAlert', {message:Em.I18n.t('scripts.alert.file_exist_error'),status:'success'});
-            script.set('pigScript',file);
-            script.save().then(sendAlert('success'),sendAlert('error'));
-          }, sendAlert('error'));
-        });
-      } else {
-          script.save().then(sendAlert('success'),sendAlert('error'));
-      }
-
-
-    },
-    confirmdelete:function (script) {
-      var router = this;
-      var onSuccess = function(model){
-            router.send('showAlert', {'message':Em.I18n.t('scripts.alert.script_deleted',{title : model.get('title')}),status:'success'});
-          };
-      var onFail = function(error){
-            var trace = null;
-            if (error && error.responseJSON.trace)
-              trace = error.responseJSON.trace;
-            router.send('showAlert', {'message':Em.I18n.t('scripts.alert.delete_failed'),status:'error',trace:trace});
-          };
-      script.deleteRecord();
-      return script.save().then(onSuccess,onFail);
-    }
-  },
-  enter: function() {
-    this.controllerFor('pig').set('category',"scripts");
-  },
-  model: function(object,transition) {
-    return this.modelFor('pig');
-  }
-});

+ 6 - 3
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigIndex.js → contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigScripts.js

@@ -18,8 +18,11 @@
 
 var App = require('app');
 
-App.PigIndexRoute = Em.Route.extend({
-  beforeModel: function() {
-    this.transitionTo('pig.scriptList');
+App.PigScriptsRoute = Em.Route.extend({
+  enter: function() {
+    this.controllerFor('pig').set('category','scripts');
+  },
+  model: function(object,transition) {
+    return this.modelFor('pig');
   }
 });

+ 0 - 33
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigUdfs.js

@@ -19,39 +19,6 @@
 var App = require('app');
 
 App.PigUdfsRoute = Em.Route.extend({
-  actions:{
-    createUdfModal:function () {
-      this.controllerFor('pigModal').set('content', this.store.createRecord('udf'));
-      return this.send('openModal','createUdf');
-    },
-    createUdf:function (udf) {
-      var router = this;
-      var onSuccess = function(model){
-          router.send('showAlert', {'message': Em.I18n.t('udfs.alert.udf_created',{name : model.get('name')}), status:'success'});
-        };
-      var onFail = function(error){
-          var trace = null;
-          if (error && error.responseJSON.trace)
-            trace = error.responseJSON.trace;
-          router.send('showAlert', {'message':Em.I18n.t('udfs.alert.create_failed'),status:'error',trace:trace});
-        };
-      return udf.save().then(onSuccess,onFail);
-    },
-    deleteUdf:function(udf){
-      var router = this;
-      var onSuccess = function(model){
-            router.send('showAlert', {'message': Em.I18n.t('udfs.alert.udf_deleted',{name : model.get('name')}),status:'success'});
-          };
-      var onFail = function(error){
-            var trace = null;
-            if (error && error.responseJSON.trace)
-              trace = error.responseJSON.trace;
-            router.send('showAlert', {'message': Em.I18n.t('udfs.alert.delete_failed'),status:'error',trace:trace});
-          };
-      udf.deleteRecord();
-      return udf.save().then(onSuccess,onFail);
-    }
-  },
   enter: function() {
     this.controllerFor('pig').set('category',"udfs");
   },

+ 59 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/script.js

@@ -0,0 +1,59 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.ScriptRoute = Em.Route.extend({
+  actions:{
+    willTransition:function (transition) {
+      if (this.controllerFor('script.edit').get('isExec')) {
+        return transition.abort();
+      }
+      if (this.controllerFor('script.edit').get('isRenaming')) {
+        this.controllerFor('script.edit').set('titleWarn',true);
+        return transition.abort();
+      }
+      var isAway = this.isGoingAway(transition);
+      var scriptDirty = this.controllerFor('pig').get('scriptDirty');
+      if (isAway && scriptDirty) {
+        transition.abort();
+        this.send('openModal','confirmAway',transition);
+      }
+    }
+  },
+  enter:function () {
+    this.controllerFor('pig').set('category', '');
+  },
+  deactivate: function() {
+    this.controllerFor('pig').set('activeScriptId', null);
+    this.controllerFor('script').set('activeJobs',[]);
+  },
+  isGoingAway:function (transition) {
+    var isScriptAway = !transition.targetName.match(/^script./);
+    if (!isScriptAway) {
+      var targetParams = transition.params[transition.targetName];
+      if (targetParams['script_id']) {
+        return targetParams['script_id'] != this.controllerFor('pig').get('activeScriptId');
+      };
+      if (targetParams['job_id'] && this.modelFor('script.history')) {
+        return this.modelFor('script.history').get('content').filterBy('id',targetParams['job_id']).length == 0;
+      };
+    };
+    return isScriptAway;
+  }
+});

+ 47 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/scriptEdit.js

@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+App.ScriptEditRoute = Em.Route.extend({
+  enter:function () {
+    this.controllerFor('script').set('activeTab','script');
+  },
+  isExec:false,
+  model: function(params) {
+    var record;
+    var isExist = this.store.all('script').some(function(script) {
+      return script.get('id') === params.script_id;
+    });
+    if (isExist) {
+      record = this.store.find('script',params.script_id);
+    } else {
+      record = this.store.createRecord('script');
+    }
+    return record;
+  },
+  afterModel:function  (model) {
+    if (model.get('length') == 0) {
+      this.transitionTo('pig');
+    }
+    this.controllerFor('pig').set('activeScriptId', model.get('id'));
+  },
+  renderTemplate: function() {
+    this.render('script/edit');
+  }
+});

+ 14 - 7
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/jobResults.js → contrib/views/pig/src/main/resources/ui/pig-web/app/routes/scriptHistory.js

@@ -1,4 +1,4 @@
-/**
+/*
  * 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
@@ -18,12 +18,19 @@
 
 var App = require('app');
 
-App.JobResultsRoute = Em.Route.extend({
+App.ScriptHistoryRoute = Em.Route.extend({
   enter: function() {
-      this.controllerFor('pig').set('category',"");
-      this.controllerFor('job').set('category',"results");
-    },
-  model: function (controller) {
-    return this.modelFor('job');
+    this.controllerFor('script').set('activeTab','history');
+  },
+  model:function(param) {
+    this.controllerFor('pig').set('activeScriptId', param.script_id);
+    return this.store.find('job', {scriptId: param.script_id});
+  },
+  setupController:function (controller,model) {
+    var script_id = this.controllerFor('pig').get('activeScriptId');
+    model.store.recordArrayManager.registerFilteredRecordArray(model,model.type,function(job) {
+      return job.get('scriptId') == script_id;
+    });
+    controller.set('model',model);
   }
 });

+ 21 - 20
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/pigJob.js → contrib/views/pig/src/main/resources/ui/pig-web/app/routes/scriptJob.js

@@ -18,7 +18,7 @@
 
 var App = require('app');
 
-App.JobRoute = Em.Route.extend({
+App.ScriptJobRoute = Em.Route.extend({
     actions: {
       error: function(error, transition) {
         Em.warn(error.stack);
@@ -26,32 +26,33 @@ App.JobRoute = Em.Route.extend({
         if (error && error.responseJSON.trace)
           trace = error.responseJSON.trace;
         transition.send('showAlert', {'message':Em.I18n.t('job.alert.load_error',{message:error.message}), status:'error', trace:trace});
-        this.transitionTo('pig.scriptList');
+        this.transitionTo('pig.scripts');
       },
       navigate:function (argument) {
         return this.transitionTo(argument.route)
+      },
+      killjob:function (job) {
+        var self = this;
+        job.kill(function () {
+          job.reload();
+          self.send('showAlert', {'message': Em.I18n.t('job.alert.job_killed',{title:self.get('title')}), status:'info'});
+        },function (reason) {
+          var trace = null;
+          if (reason && reason.responseJSON.trace)
+            trace = reason.responseJSON.trace;
+          self.send('showAlert', {'message': Em.I18n.t('job.alert.job_kill_error'), status:'error', trace:trace});
+        });
       }
     },
+    model:function (q,w) {
+      return this.store.find('job',q.job_id);
+    },
     setupController: function(controller, model) {
       controller.set('model', model);
-      this.controllerFor('poll').set('model', model);
-    },
-    afterModel:function (job,arg) {
-      this.controllerFor('poll').get('pollster').start(job);
-    },
-    deactivate: function() {
-      this.controllerFor('poll').get('pollster').stop();
     },
-    renderTemplate: function() {
-      this.render('pig/scriptEdit', {controller: 'job' });
-      this.render('pig/job', {into:'pig/scriptEdit',outlet: 'main', controller: 'poll' });
-      this.render('pig/scriptResultsNav',{into:'pig/scriptEdit',outlet: 'nav'});
+    afterModel:function (job) {
+      this.controllerFor('pig').set('activeScriptId', job.get('scriptId'));
+      this.controllerFor('script').get('activeJobs').addObject(job);
+      this.controllerFor('script').set('activeTab',job.get('id'));
     }
 });
-
-App.JobIndexRoute = Em.Route.extend({
-  enter: function() {
-      this.controllerFor('pig').set('category',"");
-      this.controllerFor('job').set('category',"edit");
-    },
-});

+ 10 - 8
contrib/views/pig/src/main/resources/ui/pig-web/app/routes/splash.js

@@ -20,13 +20,15 @@ var App = require('app');
 
 App.SplashRoute = Em.Route.extend({
   model: function() {
-    return Ember.Object.create({storageTest: null,
-                                storageTestDone: null,
-                                webhcatTest: null,
-                                webhcatTestDone: null,
-                                hdfsTest: null,
-                                hdfsTestDone: null,
-                                percent: 0});
+    return Ember.Object.create({
+      storageTest: null,
+      storageTestDone: null,
+      webhcatTest: null,
+      webhcatTestDone: null,
+      hdfsTest: null,
+      hdfsTestDone: null,
+      percent: 0
+    });
   },
   renderTemplate: function() {
     this.render('splash');
@@ -41,7 +43,7 @@ App.SplashRoute = Em.Route.extend({
           if (previousTransition) {
             previousTransition.retry();
           } else {
-            self.transitionTo('pig.scriptList');
+            self.transitionTo('pig.scripts');
           }
         }, 1000);
       }

+ 420 - 45
contrib/views/pig/src/main/resources/ui/pig-web/app/styles/style.less

@@ -16,9 +16,17 @@
  * limitations under the License.
  */
 
+@import './../../bower_components/bootstrap/less/variables.less';
+
+@screen-md:                  900px;
+@modal-backdrop-opacity:      0;
+
+@import './../../bower_components/bootstrap/less/bootstrap.less';
+
 .wrap {
-	padding: 15px;
+  padding: 15px;
 }
+
 .moz-padding (@selector, @pre: ~'', @padding) {
   @-moz-document url-prefix() {
     @{pre}@{selector} {
@@ -51,9 +59,128 @@
 
 // navigation
 .navigation {
-  //position: fixed;
-  a {
-    cursor: pointer;
+  .well {
+    padding: 0;
+    background-color: #f5f5f5;
+    height: 200px;
+    overflow: hidden;
+    .nav-main {
+      &.collapsed {
+        a {
+          border-right: 1px solid transparent;
+        }
+      }
+      float: left;
+      width: 100%;
+      text-align:  center;
+      &.list-group-item:first-child{
+        border-top-left-radius: 4px;
+      }
+      a {
+        &.list-group-item:hover,
+        &.list-group-item:focus {
+          background-color: lighten(#f5f5f5, 3%);
+          border-color: #e9e9e9;
+        }
+        &.list-group-item.active,
+        &.list-group-item.active:hover,
+        &.list-group-item.active:focus {
+          z-index: 60;
+          color: black;
+          background-color: lighten(#f5f5f5, 3%);
+          border-color: #dadada;
+          .common-shadow-inset;
+        }
+        &.list-group-item:first-child{
+          border-radius: 0;
+          border-top: 0;
+        }
+        &.list-group-item:last-child {
+          border-radius: 0;
+        }
+        span {
+          bottom: 4px;
+          position: relative;
+          margin-left: 15px;
+        }
+        cursor: pointer;
+        background-color: transparent;
+        border-top: 1px solid transparent;
+        border-bottom: 1px solid transparent;
+        border-right: 0;
+        border-left:0;
+
+        .base-transition( background-color 200ms linear;);
+      }
+    }
+    .nav-script-wrap {
+      &.in {
+        &.reveal {
+          left: 125px;
+        }
+        left: 65px;
+      }
+      z-index: 70;
+      width: 100%;
+      left: 102%;
+      position: relative;
+      height: 100%;
+      box-shadow: -2px 0px 1px rgba(0, 0, 0, 0.20);
+      .base-transition( left 300ms linear;);
+
+      .nav-script {
+        position: absolute;
+        width: 100%;
+        background-color: @gray-dark;
+        height: 100%;
+
+        & div:first-child {
+          margin-bottom: 25px;
+          button {
+            position: relative;
+            float: right;
+            right: 65px;
+            color: @gray-light;
+            border: 0;
+            background-color: transparent;
+            margin: 1px;
+            &:hover {
+              color: @gray-lighter;
+            }
+          }
+        }
+
+        .script-title {
+          background: #fff;
+          padding: 8px;
+          //border: 1px solid #747474;
+          margin-bottom: 10px;
+        }
+       .script-actions {
+          padding-left: 10px;
+          padding-right: 10px;
+          li {
+              margin: 9px 0;
+            &.divider {
+              height: 1px;
+              margin: 9px 0;
+              overflow: hidden;
+              background-color: #e5e5e5;
+            }
+            a {
+              color: #fff;
+              text-align: left;
+              padding: 0;
+              &:hover {
+                color: darken(#fff, 30%);
+                text-decoration: none;
+              }
+            }
+          }
+       }
+
+      }
+    }
   }
   .pig-nav-item {
     word-break: break-word;
@@ -66,25 +193,101 @@
   }
 }
 
+.nav-tabs-script {
+  margin-bottom: 10px;
+  .job > a {
+    padding: 10px 25px 10px 15px;
+  }
+  .active > .btn {
+    color: #111;
+  }
+  .btn {
+    &:hover {
+      color: @brand-danger;
+    }
+    position: absolute;
+    color: @brand-primary;
+    top: 1px;
+    padding: 0px;
+    background: transparent;
+    right: 3px;
+    padding: 0px 4px;
+  }
+}
+
+.scriptlist, .panel-udfs, .panel-history {
+  .title-row {
+    height: 55px;
+  }
+}
+
 //script list
-.panel-scriptlist {
+.scriptlist {
   .new-script {
-    position: relative;
-    top:-5px;
+    margin-top: 15px;
+    /*position: relative;
+    top:-5px;*/
+  }
+  table {
+    th:last-child {
+      width: 260px;
+    }
+    td > h4 {
+      margin: 0;
+    }
+    td:last-child > a {
+      margin-right: 15px;
+    }
+  }
+}
+
+.pagination-block {
+  color: #7b7b7b;
+  & > div {
+    display: inline-block;
+    margin-left: 15px;
+  }
+  text-align: right;
+  .items-count {
+    vertical-align: top;
+    label {
+      font-weight: normal;
+    }
+    select {
+      display: inline-block;
+      width: auto;
+      padding: 0px 8px;
+      height: 28px;
+    }
+  }
+  .items-info {
+    vertical-align: top;
+    padding: 4px 0;
+  }
+  .items-buttons {
+    .pagination {
+      margin: 0;
+      & > li > a, & > li > span {
+        padding: 3px 12px;
+        border-color: transparent;
+        &:hover {
+          background-color: #fff;
+        }
+      }
+    }
   }
 }
 
 //udfs
 .panel-udfs {
   .upload-udf {
-    position: relative;
-    top:-5px;
+    margin-top: 15px;
   }
 }
 
 .argument-label {
   vertical-align: middle;
-  background-color: #999999;
+  background-color: @gray-light;
   display: inline-block;
   font-size: 75%;
   font-weight: bold;
@@ -103,6 +306,7 @@
 //edit script
 .edit-script {
   .panel-editscript {
+    margin-bottom: 5px;
     textarea {
       width: 100%;
       max-width: 100%;
@@ -116,7 +320,16 @@
     }
     .CodeMirror {
       .common-shadow-inset;
-
+      height:500px;
+      .CodeMirror-hscrollbar {
+        left: 0 !important;
+      }
+    }
+    #sgrip {
+      position: absolute;
+      left: 50%;
+      cursor: s-resize;
+      color: #999999;
     }
     .table-results {
       .argument {
@@ -128,19 +341,24 @@
       background-color: #f5f5f5;
     }
     .panel-heading{
-      .input-title {
-        width: 1%;
-        #title {
-          width: 250px;
-          display: inline-block;
+      .script-title {
+        display: inline-block;
+        h4 {
+          margin-bottom: 5px;
+          .btn-rename {
+            margin-left: 10px;
+          }
         }
+      }
 
-        .btn-rename-ask {
-          background-color: #f5f5f5;
+      .input-title {
+        width: 45%;
+        min-width: 200px;
+        .tooltip-arrow {
+          border-bottom-color: #a94442;
         }
-
-        .btn-rename-ask:hover {
-          background-color: #fff;
+        .tooltip-inner {
+          background-color: #a94442;
         }
 
         .btn-rename-cancel{
@@ -153,8 +371,8 @@
       }
     }
     .panel-body {
-      padding: 5px; 
-      background-color: #f5f5f5; 
+      padding: 5px;
+      background-color: #f5f5f5;
       border-bottom: 1px solid #dddddd;
       .argadd {
         font-size: 13px;
@@ -213,28 +431,173 @@
       }
     }
   }
-  .pigParams {
-    padding-bottom: 10px; 
+  .params-block {
+    .block-title {
+      border-bottom: 2px solid #dddddd;
+      margin-bottom: 10px;
+      padding: 5px 0;
+    }
+    .pigParams {
+      padding-bottom: 10px;
+    }
+  }
+  .arguments-block {
+    .block-title {
+      border-bottom: 2px solid #dddddd;
+      margin-bottom: 10px;
+      padding: 5px 0;
+    }
+    .alert {
+      padding: 6px 15px;
+      margin-bottom: 5px;
+    }
+    .arguments-wrap {
+      .label {
+        padding: 10px ;
+        display: inline-block;
+        font-size: 100%;
+        margin-bottom: 5px;
+        a {
+          margin-left: 5px;
+          font-size: 100%;
+        }
+      }
+    }
+  }
+}
+
+.script_history_container{
+  table {
+    th:last-child {
+      width: 250px;
+    }
+    td > h4 {
+      margin: 0;
+    }
+    td > a {
+      margin-right: 15px;
+    }
+  }
+}
+
+.panel-history {
+  table {
+    th:first-child {
+      width: 140px;
+    }
+    th:last-child {
+      width: 110px;
+    }
+    td > h4 {
+      margin: 0;
+    }
+    td > a {
+      margin-right: 15px;
+    }
+    .scriptLink {
+      &.inactive {
+        text-decoration: none;
+        background-color: transparent;
+        cursor: auto;
+        color: #999999;
+      }
+      cursor: pointer;
+    }
   }
 }
 
-.job-status-view {
-  .progress {
-    margin-bottom: 0;
+.job-container {
+  .progress-wrap {
+    .base-transition( width 200ms linear;);
+  }
+  .panel-title {
+    font-weight: normal;
+    a {
+      text-decoration: none;
+    }
+    .toggle-icon {
+      transition: all 0.3s;
+    }
+  }
+  #scriptResults .panel-body, #scriptLogs .panel-body {
+    max-height: 550px;
+    overflow-x: hidden;
+    background-color: #fcfcfc;
+    pre {
+      margin: 0;
+      padding: 0;
+      border: 0;
+      border-radius: 0;
+      background-color: #fcfcfc;
+    }
+  }
+  .CodeMirror {
+    .common-shadow-inset;
+    height: 215px;
+    border-bottom: 1px solid #dddddd;
+    .CodeMirror-gutters {
+      background-color: #fcfcfc;
+    }
+    .CodeMirror-hscrollbar {
+      left: 0 !important;
+    }
+  }
+  #sgrip {
+      position: absolute;
+      left: 50%;
+      cursor: s-resize;
+      color: #999999;
+    }
+
+  .arguments-wrap {
+    padding: 5px;
+    .label {
+      padding: 5px;
+      display: inline-block;
+      font-size: 100%;
+      margin-bottom: 5px;
+    }
+  }
+
+  #scriptDetails {
+    .body-title {
+      padding: 5px;
+      background-color: #fcfcfc;
+      border-bottom: 1px solid #dddddd;
+    }
+    .alert {
+      border: none;
+      border-radius: 0;
+      margin-bottom: 0;
+    }
   }
-  .kill-button {
-    float: left; 
-    margin-right: 10px;
+
+  .job-info {
+    td {
+     /* textarea {
+        width: 100%;
+        max-width: 100%;
+        height: 300px;
+      }*/
+      border: 0;
+      &:first-child {
+        width: 120px;
+      }
+    }
   }
+
 }
 
 //utils
 #alert-wrap {
-  position: fixed;
-  bottom: 0;
-  width: 16.666666666666664%;
-  padding-right: 30px;
+  //position: fixed;
+  //bottom: 0;
+  //width: 16.666666666666664%;
+  //padding-right: 30px;
   font-size: 0.9em;
+width: 100%;
+z-index: 3;
+opacity: 0.89;
   
   .alert {
     .common-shadow;
@@ -350,15 +713,6 @@
   border-radius:6px 0 6px 6px;
 }
 
-
-a.list-group-item.active{
-  z-index: 2;
-  color: #ffffff;
-  background-color: #428bca;
-  border-color: #428bca;
-  .base-transition(border-color 200ms linear, background-color 200ms linear;);
-}
-
 .empty-table-footer {
   text-align: center;
   font-size: 13pt;
@@ -373,6 +727,27 @@ a.list-group-item.active{
 .green {
     color: green;
 }
+
 .red {
     color: red;
-}
+}
+
+.resultsModal, .logModal {
+  .modal-header p {
+    width: 55%;
+  }
+  .modal-body {
+    height: 550px;
+    overflow-y: scroll;
+    background-color: #f5f5f5;
+    pre {
+      margin: 0;
+      padding: 0;
+      border: 0;
+      border-radius: 0;
+    }
+  }
+  .modal-footer {
+    margin-top: 0;
+  }
+}

+ 2 - 2
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/util/pigHelper.hbs → contrib/views/pig/src/main/resources/ui/pig-web/app/templates/components/pigHelper.hbs

@@ -6,9 +6,9 @@
    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.

+ 26 - 21
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/job.hbs → contrib/views/pig/src/main/resources/ui/pig-web/app/templates/components/scriptListRow.hbs

@@ -6,9 +6,9 @@
    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.
@@ -16,24 +16,29 @@
    limitations under the License.
 }}
 
-<div class="well well-sm">
-  <div class="jobStatus" style="margin-bottom: 5px;">
-    <small>
-    {{t 'job.job_status'}} <strong>{{content.status}}</strong>
-    </small>
-  </div>
-
-  {{#if content.needsPing}}
-    {{#unless content.isKilling}}
-      <button {{action "killjob" content}} type="button" class="btn btn-danger btn-xs kill-button">Kill</button>
-    {{/unless}}
-    {{#if content.isKilling}}
-      <div class="spinner-sm pull-left kill-button"></div>
+<td class="first">
+  {{#link-to 'script.edit' id}}
+    {{#if isNew}}
+      <div class="spinner-sm"></div>
     {{/if}}
+    {{#unless isNew}}
+      {{title}}
+    {{/unless}}
+  {{/link-to}}
+</td>
+<td>
+  {{#if view.currentJob}}
+    <span class="date">{{showDate view.currentJob.dateStarted 'YYYY-MM-DD HH:mm'}}</span>
+  {{else}}
+    {{t 'scripts.not_run_message'}}
   {{/if}}
-
-  {{view view.progressBar contentBinding="content"}}
-</div>
-
-
-{{outlet}}
+</td>
+<td>
+  {{view.currentJob.status}}
+</td>
+<td>
+  {{#unless isNew}}
+    {{#link-to 'script.history' id}}{{t 'common.history'}}{{/link-to}} -
+    <a href="#" {{action "deletescript" this}}>{{t 'common.delete'}}</a>
+  {{/unless}}
+</td>

+ 35 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/error.hbs

@@ -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.
+}}
+
+<div class="panel panel-danger">
+  <div class="panel-heading">
+    <div class="text-center">
+      <strong>{{content.status}}</strong>  {{content.message}}
+      {{#if content.trace}}
+        <a data-toggle="collapse" class="btn btn-danger btn-xs" href="#collapseTrace">Trace <span class="caret"></span></a>
+      {{/if}}
+    </div>
+  </div>
+  <div id="collapseTrace" class="panel-collapse collapse">
+    <div class="panel-body">
+      <pre>
+        {{content.trace}}
+      </pre>
+    </div>
+  </div>
+</div>

+ 0 - 18
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/index.hbs

@@ -1,18 +0,0 @@
-{{!
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-  
-       http://www.apache.org/licenses/LICENSE-2.0
-  
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-}}
-

+ 32 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/confirmAway.hbs

@@ -0,0 +1,32 @@
+{{!
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+}}
+
+{{#pig-modal ok='confirm' option="discard" }}
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+    <h4 class="modal-title">{{t 'common.warning'}}</h4>
+  </div>
+  <div class="modal-body">
+    {{t 'scripts.modal.unsaved_changes_warning'}}
+  </div>
+  <div class="modal-footer">
+    <button type="button" {{action "cancel" target="view"}} class="btn btn-default" >{{t 'common.cancel'}}</button>
+    <button type="button" {{action "option" target="view" }} {{bind-attr class=":btn :btn-danger"}}>{{t 'common.discard_changes'}}</button>
+    <button type="button" {{action "ok" target="view"}} {{bind-attr class=":btn :btn-success"}}>{{t 'common.save'}}</button>
+  </div>
+{{/pig-modal}}

+ 15 - 2
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/jobEdit.hbs → contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/confirmDelete.hbs

@@ -6,9 +6,9 @@
    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.
@@ -16,3 +16,16 @@
    limitations under the License.
 }}
 
+{{#pig-modal ok='confirm'}}
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+    <h4 class="modal-title">{{t 'scripts.modal.confirm_delete'}}</h4>
+  </div>
+  <div class="modal-body">
+  {{t 'scripts.modal.confirm_delete_massage' titleBinding="content.title" tagName="p"}}
+  </div>
+  <div class="modal-footer">
+    <button type="button" {{action "cancel" target="view"}} class="btn btn-default" data-dismiss="modal">{{t 'common.cancel'}}</button>
+    <button type="button" {{action "ok" content target="view"}} class="btn btn-danger">{{t 'common.delete'}}</button>
+  </div>
+{{/pig-modal}}

+ 42 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/createScript.hbs

@@ -0,0 +1,42 @@
+{{!
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+}}
+
+{{#pig-modal ok='confirm' close='cancel' escape-press='cancel'}}
+  <div class="modal-header">
+    <button type="button" class="close" {{action "cancel" target="view"}} aria-hidden="true">&times;</button>
+    <h4 class="modal-title">{{t 'scripts.modal.create_script'}}</h4>
+  </div>
+  <div class="modal-body">
+  <div class="form-group">
+      <label>{{t 'scripts.title'}}</label>
+      {{input class="form-control" placeholderTranslation="scripts.modal.script_title_placeholder" valueBinding="content.title"}}
+    </div>
+    {{#if titleError}}
+      <div class="alert alert-danger">{{titleError}}</div>
+    {{/if}}
+    <div class="form-group">
+      <label for="exampleInputPassword1">{{t 'scripts.path'}}</label>
+      {{input class="form-control" placeholderTranslation="scripts.modal.file_path_placeholder" valueBinding="filePath"}}
+      <small class="pull-right help-block">{{t 'scripts.modal.file_path_hint'}}</small>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button type="button" {{action "cancel" target="view"}} class="btn btn-default" >{{t 'common.cancel'}}</button>
+    <button type="button" {{action "ok" target="view"}} {{bind-attr class="titleError:disabled :btn :btn-success"}} >{{t 'common.create'}}</button>
+  </div>
+{{/pig-modal}}

+ 38 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/createUdf.hbs

@@ -0,0 +1,38 @@
+{{!
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+}}
+
+{{#pig-modal ok='confirm' close='cancel'}}
+  <div class="modal-header">
+    <button type="button" class="close" {{action "cancel" target="view"}} aria-hidden="true">&times;</button>
+    <h4 class="modal-title">{{t 'udfs.modal.create_udf'}}</h4>
+  </div>
+  <div class="modal-body">
+  <div class="form-group">
+      <label for="exampleInputEmail1">{{t 'common.name'}}</label>
+      {{input class="form-control" placeholderTranslation="udfs.modal.udf_name" valueBinding="content.name"}}
+    </div>
+    <div class="form-group">
+      <label for="exampleInputPassword1">{{t 'common.path'}}</label>
+      {{input class="form-control" placeholderTranslation="udfs.modal.hdfs_path" valueBinding="content.path"}}
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button type="button" {{action "cancel" content target="view"}} class="btn btn-default" >{{t 'common.cancel'}}</button>
+    <button type="button" {{action "ok" target="view"}} {{bind-attr class=":btn :btn-success udfInvalid:disabled"}}>{{t 'common.create'}}</button>
+  </div>
+{{/pig-modal}}

+ 15 - 3
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/index.hbs → contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/deleteJob.hbs

@@ -6,9 +6,9 @@
    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.
@@ -16,4 +16,16 @@
    limitations under the License.
 }}
 
-
+{{#pig-modal ok='confirm'}}
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+    <h4 class="modal-title">{{t 'scripts.modal.confirm_delete'}}</h4>
+  </div>
+  <div class="modal-body">
+  {{t 'job.modal.confirm_delete_massage' titleBinding="content.title" tagName="p"}}
+  </div>
+  <div class="modal-footer">
+    <button type="button" {{action "cancel" target="view"}} class="btn btn-default" data-dismiss="modal">{{t 'common.cancel'}}</button>
+    <button type="button" {{action "ok" content target="view"}} class="btn btn-danger">{{t 'common.delete'}}</button>
+  </div>
+{{/pig-modal}}

+ 15 - 13
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/modal/confirmdelete.hbs → contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/gotoCopy.hbs

@@ -6,9 +6,9 @@
    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.
@@ -16,14 +16,16 @@
    limitations under the License.
 }}
 
-<div class="modal-header">
-  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-  <h4 class="modal-title">{{t 'scripts.modal.confirm_delete'}}</h4>
-</div>
-<div class="modal-body">
-{{t 'scripts.modal.confirm_delete_massage' titleBinding="content.title" tagName="p"}}
-</div>
-<div class="modal-footer">
-  <button type="button" {{action "close" target="view"}} class="btn btn-default" data-dismiss="modal">{{t 'common.cancel'}}</button>
-  <button type="button" {{action "confirm" content target="view"}} class="btn btn-danger">{{t 'common.delete'}}</button>
-</div>
+{{#pig-modal ok='confirm'}}
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+    <h4 class="modal-title">{{t 'scripts.modal.copy_created'}}</h4>
+  </div>
+  <div class="modal-body">
+    {{t 'scripts.modal.copy_created_massage' titleBinding="content.title" tagName="p"}}
+  </div>
+  <div class="modal-footer">
+    <button type="button" {{action "cancel" target="view"}} class="btn btn-default" data-dismiss="modal">{{t 'scripts.modal.continue_editing'}}</button>
+    <button type="button" {{action "ok" content target="view"}} class="btn btn-primary">{{t 'scripts.modal.go_to_copy'}}</button>
+  </div>
+{{/pig-modal}}

+ 42 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/logDownload.hbs

@@ -0,0 +1,42 @@
+{{!
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+}}
+
+{{#pig-modal ok='confirm' size='lg' class='logModal'}}
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+    {{#if jobLogsLoader.isFulfilled}}
+    <p class="pull-right" ><a href="#" {{action 'download'}}><i class="fa fa-download"></i> {{t 'common.download'}}</a></p>
+    {{/if}}
+    <h4 class="modal-title">{{t 'job.logs'}} </h4>
+  </div>
+  <div class="modal-body">
+    {{#if jobLogsLoader.isPending}}
+      <pre><div class="spinner-sm"></div></pre>
+    {{else}}
+      {{#if jobLogsLoader.isFulfilled}}
+      <pre>{{jobLogsLoader.content.fileContent}}</pre>
+      {{/if}}
+      {{#if jobLogsLoader.isRejected}}
+        {{jobLogsLoader.reason}}
+      {{/if}}
+    {{/if}}
+  </div>
+  <div class="modal-footer">
+    <button type="button" {{action "cancel" target="view"}} class="btn btn-default" data-dismiss="modal">{{t 'common.close'}} </button>
+  </div>
+{{/pig-modal}}

+ 3 - 3
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/modal/modalLayout.hbs → contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/modalLayout.hbs

@@ -6,9 +6,9 @@
    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.
@@ -17,7 +17,7 @@
 }}
 
 <div class="modal fade in" data-backdrop="static">
-  <div class="modal-dialog">
+  <div {{bind-attr class=":modal-dialog large:modal-lg small:modal-sm" }}>
     <div class="modal-content">
       {{yield}}
     </div><!-- /.modal-content -->

+ 42 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/modal/resultsDownload.hbs

@@ -0,0 +1,42 @@
+{{!
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+}}
+
+{{#pig-modal ok='confirm' size='lg' class='resultsModal'}}
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+      {{#if jobResultsLoader.isFulfilled}}
+      <p class="pull-right" ><a href="#" {{action 'download'}}><i class="fa fa-download"></i> {{t 'common.download'}}</a></p>
+      {{/if}}
+    <h4 class="modal-title">{{t 'job.results'}}</h4>
+  </div>
+  <div class="modal-body">
+    {{#if jobResultsLoader.isPending}}
+      <pre><div class="spinner-sm"></div></pre>
+    {{else}}
+      {{#if jobResultsLoader.isFulfilled}}
+      <pre>{{jobResultsLoader.content.fileContent}}</pre>
+      {{/if}}
+      {{#if jobResultsLoader.isRejected}}
+        {{jobResultsLoader.reason}}
+      {{/if}}
+    {{/if}}
+  </div>
+  <div class="modal-footer">
+    <button type="button" {{action "cancel" target="view"}} class="btn btn-default" data-dismiss="modal">{{t 'common.close'}}</button>
+  </div>
+{{/pig-modal}}

+ 9 - 9
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/util/alert-content.hbs → contrib/views/pig/src/main/resources/ui/pig-web/app/templates/partials/alert-content.hbs

@@ -6,9 +6,9 @@
    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.
@@ -16,10 +16,10 @@
    limitations under the License.
 }}
 
-  <button type="button" class="close" >&times;</button>
-  <p>
-    {{view.content.message}}
-  </p>
-  {{#if view.content.trace}}
-      <a href="#" {{action 'showErrorLog' this}}>{{t 'common.showErrorLog'}}</a>
-  {{/if}}
+<button type="button" class="close" >&times;</button>
+<p>
+  {{view.content.message}}
+</p>
+{{#if view.content.trace}}
+    <a href="#" {{action 'showErrorLog' view.content}}>{{t 'common.showErrorLog'}}</a>
+{{/if}}

+ 42 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/partials/paginationControls.hbs

@@ -0,0 +1,42 @@
+{{!
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+}}
+
+<div class="pagination-block">
+  <div class="items-count">
+    <label >{{t 'common.show'}}
+      {{view Ember.Select content=perPageOptions class="form-control" value=perPage}}
+    </label>
+  </div>
+  <div class="items-info">
+    <span>{{paginationInfo}}</span>
+  </div>
+  <div class="items-buttons">
+    <ul class="pagination">
+      {{#if prevPage}}
+        <li><a href="#" {{action "selectPage" prevPage}}><i class="fa fa-arrow-left"></i></a></li>
+      {{else}}
+        <li class="disabled"><a><i class="fa fa-arrow-left"></i></a></li>
+      {{/if}}
+      {{#if nextPage}}
+        <li><a href="#" {{action "selectPage" nextPage}}><i class="fa fa-arrow-right"></i></a></li>
+      {{else}}
+        <li class="disabled"><a><i class="fa fa-arrow-right"></i></a></li>
+      {{/if}}
+    </ul>
+  </div>
+</div>

+ 29 - 3
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig.hbs

@@ -18,11 +18,37 @@
 
 <div class="container-fluid">
   <div class="row">
-    <div class="col-md-2 navigation">
-      {{view view.navItemsView}}
+    <div class="col-md-3 navigation">
+      <div class="well">
+
+        {{view view.navItemsView}}
+        <div class="nav-script-wrap">
+          <div class=" nav-script" >
+            <div>
+              <button type="button" class="close_script" {{action closeScript}}>
+                <i class="fa fa-times"></i>
+              </button>
+            </div>
+            <div class="script-title">
+              {{#if activeScript.label}}
+                <span>{{activeScript.label}}</span>
+                {{else}}
+                <span>...</span>
+              {{/if}}
+            </div>
+           <ul class="script-actions list-unstyled">
+              <li><a href="#" {{action "saveScript" activeScript}} {{bind-attr class=":btn :btn-block saveEnabled::disabled"}} ><i class="fa fa-fw fa-save"></i> {{t 'common.save'}}</a></li>
+              <li><a href="#" {{action "copyScript" activeScript}} {{bind-attr class=":btn :btn-block disableScriptControls:disabled"}} ><i class="fa fa-fw fa-copy"></i> {{t 'common.copy'}}</a></li>
+              <li class="divider"></li>
+              <li><a href="#" {{action "deletescript" activeScript}} {{bind-attr class=":btn :btn-block disableScriptControls:disabled"}}  ><i class="fa fa-fw fa-trash-o"></i> {{t 'common.delete'}}</a></li>
+            </ul>
+          </div>
+        </div>
+      </div>
+
       {{outlet alert}}
     </div>
-    <div class="col-md-10">
+    <div class="col-md-9">
       {{outlet}}
     </div>
   </div>

+ 3 - 3
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/util/alert.hbs → contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/alert.hbs

@@ -6,9 +6,9 @@
    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.
@@ -17,5 +17,5 @@
 }}
 
 <div id="alert-wrap">
-   {{view view.alertsView}}
+ {{view view.alertsView}}
 </div>

+ 37 - 21
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/history.hbs

@@ -16,37 +16,53 @@
    limitations under the License.
 }}
 
-<div class="panel panel-default panel-history">
-  <div class="panel-heading">
-    <span class="panel-title">
-      {{t 'common.history'}}
-    </span>
+<div class="panel-history">
+  <div class="title-row">
+    <h3 class="pull-left"> {{t 'common.history'}}</h3>
   </div>
-  
-  <table class="table table-bordered table-striped ">
+  <table class="table table-striped ">
     <thead> 
       <tr class="label-row">
-        <th class="first">{{t 'job.name'}}</th>
-        <th>{{t 'job.started'}}</th> 
+        <th>{{t 'common.date'}}</th>
+        <th>{{t 'scripts.script'}}</th>
         <th>{{t 'job.status'}}</th> 
-        <th></th> 
+        <th>{{t 'history.duration'}}</th>
+        <th>{{t 'common.actions'}}</th>
       </tr>
     </thead>
     <tbody> 
-    {{#each jobs}}
-      {{#view view.historyTableRow}}
-        <tr> 
-          <td class="first">{{#link-to 'job' this.id }} {{this.title}}{{/link-to}}</td>
-          <td><span class="date">{{showDate this.dateStarted 'YYYY-MM-DD HH:mm'}}</span></td>
-          <td><span {{bind-attr class=":label view.labelClass"}}>{{this.status}}</span></td>
-          <td>{{#link-to 'job.results' this.id }} {{t 'job.results'}} {{/link-to}}</td>
-        </tr>
-      {{/view}}
+    {{#each paginatedContent}}
+      <tr>
+        <td>{{#link-to 'script.job' this.id }} {{showDate this.dateStarted 'YYYY-MM-DD HH:mm'}} {{/link-to}}</td>
+        <td>
+          {{#view 'view.scriptLink' scriptId=this.scriptId allIds=controller.scriptIds}}{{this.title}}{{/view}}
+        </td>
+        <td>
+          <h4>
+            <span {{bind-attr class=":label jobSuccess:label-success jobError:label-danger jobInProgress:label-warning "}} class="label label-success">{{status}}</span>
+          </h4>
+        </td>
+        <td>{{#unless jobInProgress}}{{durationTime}}{{/unless}}</td>
+        <td>
+          {{#unless jobInProgress}}
+            <a {{action 'logsPopup' this}} href="#" title="Logs"> <i class="fa fa-file-text-o"></i></a>
+            <a {{action 'resultsPopup' this}} href="#" title="Results"> <i class="fa fa-table"></i></a>
+          {{/unless}}
+          <a href="#" {{action 'deleteJob' this}} title="Delete"><i class="fa fa-trash-o"></i></a>
+        </td>
+      </tr>
     {{/each}}
     </tbody>
   </table>
-
   {{#unless content}}
-    <div class="panel-footer empty-table-footer">{{t 'job.noJobs'}}</div>
+    <div class="alert alert-info" role="alert">{{t 'history.no_jobs_message'}}</div>
   {{/unless}}
 </div>
+
+{{#if content}}
+  {{partial 'partials/paginationControls'}}
+{{/if}}
+
+{{#if content}}
+  {{partial 'partials/paginationControls'}}
+{{/if}}

+ 0 - 19
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/jobResults.hbs

@@ -1,19 +0,0 @@
-{{!
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-  
-       http://www.apache.org/licenses/LICENSE-2.0
-  
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-}}
-
-{{view view.outputView}}

+ 0 - 40
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/jobResultsOutput.hbs

@@ -1,40 +0,0 @@
-{{!
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-  
-       http://www.apache.org/licenses/LICENSE-2.0
-  
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-}}
-
-<div class="panel-heading">
-  <div class="btn-group" data-toggle="buttons" >
-    <label id='btn-stdout' {{action 'getOutput' 'stdout' target="view"}} {{bind-attr class=":btn :btn-success view.isLoadingOutput:disabled:" }} >
-      <input type="radio" name="options"> {{t  'job.job_results.stdout'}}
-    </label>
-    <label id='btn-stderr' {{action 'getOutput' 'stderr' target="view"}} {{bind-attr class=":btn :btn-danger view.isLoadingOutput:disabled:" }}>
-      <input type="radio" name="options"> {{t  'job.job_results.stderr'}}
-    </label>
-    <label id='btn-exitcode' {{action 'getOutput' 'exitcode' target="view"}} {{bind-attr class=":btn :btn-default view.isLoadingOutput:disabled:" }}>
-      <input type="radio" name="options"> {{t  'job.job_results.exitcode'}}
-    </label>
-  </div>
-</div>
-<div class="panel-body">
-
-  {{#unless view.isLoadingOutput}}
-    <pre>{{view.activeOutput}}</pre>
-  {{/unless}}
-  {{#if view.isLoadingOutput}}
-    <div class="spinner-sm"></div>
-  {{/if}}
-</div>

+ 0 - 18
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/jobStatus.hbs

@@ -1,18 +0,0 @@
-{{!
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-  
-       http://www.apache.org/licenses/LICENSE-2.0
-  
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-}}
-

+ 0 - 40
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/modal/createScript.hbs

@@ -1,40 +0,0 @@
-{{!
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-  
-       http://www.apache.org/licenses/LICENSE-2.0
-  
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-}}
-
-<div class="modal-header">
-  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-  <h4 class="modal-title">{{t 'scripts.modal.create_script'}}</h4>
-</div>
-<div class="modal-body">
-<div class="form-group">
-  {{#if error}}
-    <div class="alert alert-danger">{{error}}</div>
-  {{/if}}
-    <label for="exampleInputEmail1">{{t 'scripts.title'}}</label>
-    {{input class="form-control" placeholderTranslation="scripts.modal.script_title_placeholder" valueBinding="content.title"}}
-  </div>
-  <div class="form-group">
-    <label for="exampleInputPassword1">{{t 'scripts.path'}}</label>
-    {{input class="form-control" placeholderTranslation="scripts.modal.file_path_placeholder" valueBinding="filePath"}}
-    <small class="pull-right help-block">{{t 'scripts.modal.file_path_hint'}}</small>
-  </div>
-</div>
-<div class="modal-footer">
-  <button type="button" {{action "close" content target="view"}} class="btn btn-default" >{{t 'common.cancel'}}</button>
-  <button type="button" {{action "create" content target="view"}} {{bind-attr disabled="view.udfInvalid"}} class="btn btn-success">{{t 'common.create'}}</button>
-</div>

+ 0 - 36
contrib/views/pig/src/main/resources/ui/pig-web/app/templates/pig/modal/createUdf.hbs

@@ -1,36 +0,0 @@
-{{!
-   Licensed to the Apache Software Foundation (ASF) under one
-   or more contributor license agreements.  See the NOTICE file
-   distributed with this work for additional information
-   regarding copyright ownership.  The ASF licenses this file
-   to you under the Apache License, Version 2.0 (the
-   "License"); you may not use this file except in compliance
-   with the License.  You may obtain a copy of the License at
-  
-       http://www.apache.org/licenses/LICENSE-2.0
-  
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-}}
-
-<div class="modal-header">
-  <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-  <h4 class="modal-title">{{t 'udfs.modal.create_udf'}}</h4>
-</div>
-<div class="modal-body">
-<div class="form-group">
-    <label for="exampleInputEmail1">{{t 'common.name'}}</label>
-    {{input class="form-control" placeholderTranslation="udfs.modal.udf_name" valueBinding="content.name"}}
-  </div>
-  <div class="form-group">
-    <label for="exampleInputPassword1">{{t 'common.path'}}</label>
-    {{input class="form-control" placeholderTranslation="udfs.modal.hdfs_path" valueBinding="content.path"}}
-  </div>
-</div>
-<div class="modal-footer">
-  <button type="button" {{action "close" content target="view"}} class="btn btn-default" >{{t 'common.cancel'}}</button>
-  <button type="button" {{action "createUdf" content target="view"}} {{bind-attr disabled="view.udfInvalid"}} class="btn btn-success">{{t 'common.create'}}</button>
-</div>

Некоторые файлы не были показаны из-за большого количества измененных файлов