Kaynağa Gözat

AMBARI-8950. Views: Pig, add autocomplete for path inputs. (alexantonenko)

Alex Antonenko 10 yıl önce
ebeveyn
işleme
d40a2e5f1b

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

@@ -25,6 +25,7 @@ import org.apache.ambari.view.pig.services.BaseService;
 import org.apache.ambari.view.pig.utils.*;
 import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.FileAlreadyExistsException;
+import org.apache.hadoop.fs.FileStatus;
 import org.json.simple.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -34,6 +35,8 @@ import javax.ws.rs.*;
 import javax.ws.rs.core.*;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
 
 /**
  * File access resource
@@ -60,9 +63,21 @@ public class FileService extends BaseService {
   @GET
   @Path("{filePath:.*}")
   @Produces(MediaType.APPLICATION_JSON)
-  public Response getFile(@PathParam("filePath") String filePath, @QueryParam("page") Long page) throws IOException, InterruptedException {
-    LOG.debug("Reading file " + filePath);
+  public Response getFile(@PathParam("filePath") String filePath,
+                          @QueryParam("page") Long page,
+                          @QueryParam("action") String action) throws IOException, InterruptedException {
     try {
+      if (action != null && action.equals("ls")) {
+        LOG.debug("List directory " + filePath);
+        List<String> ls = new LinkedList<String>();
+        for (FileStatus fs : getHdfsApi().listdir(filePath)) {
+          ls.add(fs.getPath().toString());
+        }
+        JSONObject object = new JSONObject();
+        object.put("ls", ls);
+        return Response.ok(object).status(200).build();
+      }
+      LOG.debug("Reading file " + filePath);
       FilePaginator paginator = new FilePaginator(filePath, context);
 
       if (page == null)

+ 17 - 7
contrib/views/pig/src/main/java/org/apache/ambari/view/pig/templeton/client/Request.java → contrib/views/pig/src/main/java/org/apache/ambari/view/pig/templeton/client/RequestWrapper.java

@@ -36,7 +36,7 @@ import java.util.Map;
  * Request handler, supports GET, POST, PUT, DELETE methods
  * @param <RESPONSE> data type to deserialize response from JSON
  */
-public class Request<RESPONSE> {
+public class RequestWrapper<RESPONSE> {
   protected final Class<RESPONSE> responseClass;
   protected final ViewContext context;
   protected final WebResource resource;
@@ -44,7 +44,7 @@ public class Request<RESPONSE> {
   protected final Gson gson = new Gson();
 
   protected final static Logger LOG =
-      LoggerFactory.getLogger(Request.class);
+      LoggerFactory.getLogger(RequestWrapper.class);
 
   /**
    * Constructor
@@ -52,7 +52,7 @@ public class Request<RESPONSE> {
    * @param responseClass model class
    * @param context View Context instance
    */
-  public Request(WebResource resource, Class<RESPONSE> responseClass, ViewContext context) {
+  public RequestWrapper(WebResource resource, Class<RESPONSE> responseClass, ViewContext context) {
     this.resource = resource;
     this.responseClass = responseClass;
     this.context = context;
@@ -69,7 +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() + "\""));
+    recordLastCurlCommand(String.format("curl \"" + resource.toString() + "\""));
     String responseJson = IOUtils.toString(inputStream);
     LOG.debug(String.format("RESPONSE => %s", responseJson));
     return gson.fromJson(responseJson, responseClass);
@@ -117,7 +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() + "\""));
+    recordLastCurlCommand(String.format("curl " + curlBuilder.toString() + " \"" + resource.toString() + "\""));
     InputStream inputStream = context.getURLStreamProvider().readFrom(resource.toString(),
         "POST", builder.build().getRawQuery(), headers);
     String responseJson = IOUtils.toString(inputStream);
@@ -172,7 +172,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 -X PUT " + curlBuilder.toString() + " \"" + resource.toString() + "\""));
+    recordLastCurlCommand(String.format("curl -X PUT " + curlBuilder.toString() + " \"" + resource.toString() + "\""));
 
     InputStream inputStream = context.getURLStreamProvider().readFrom(resource.toString(),
         "PUT", builder.build().getRawQuery(), headers);
@@ -228,7 +228,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 -X DELETE " + curlBuilder.toString() + " \"" + resource.toString() + "\""));
+    recordLastCurlCommand(String.format("curl -X DELETE " + curlBuilder.toString() + " \"" + resource.toString() + "\""));
 
     InputStream inputStream = context.getURLStreamProvider().readFrom(resource.toString(),
         "DELETE", builder.build().getRawQuery(), headers);
@@ -258,4 +258,14 @@ public class Request<RESPONSE> {
   public RESPONSE delete(MultivaluedMapImpl params, MultivaluedMapImpl data) throws IOException {
     return delete(resource.queryParams(params), data);
   }
+
+  private static String lastCurlCommand = null;
+  private static void recordLastCurlCommand(String curl) {
+    LOG.info(curl);
+    lastCurlCommand = curl;
+  }
+
+  public static String getLastCurlCommand() {
+    return lastCurlCommand;
+  }
 }

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

@@ -31,7 +31,7 @@ import java.io.IOException;
  * GET parameters to every request
  * @param <RESPONSE> data type to deserialize response from JSON
  */
-public class TempletonRequest<RESPONSE> extends Request<RESPONSE> {
+public class TempletonRequest<RESPONSE> extends RequestWrapper<RESPONSE> {
   private String username;
   private String doAs;
 

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

@@ -0,0 +1,50 @@
+/*
+ * 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.PathInputComponent = Em.TextField.extend({
+
+  typeaheadInit:function () {
+    this.$().typeahead({
+      source:Em.run.bind(this,this.typeaheadSource)
+    });
+  }.on('didInsertElement'),
+
+  typeaheadSource:function (query,process) {
+
+    var adapter = this.store.adapterFor(App.File),
+        url = adapter.buildURL(true, query.replace(/[^/]+$/,''));
+
+    adapter.ajax(url, 'GET', {data:{action:'ls'}}).then(function (data) {
+
+      var ls = data.ls.map(function(item){
+          var parser = document.createElement('a');
+          parser.href = item.replace(/.*?:/, "");
+          return decodeURI(parser.pathname);
+      });
+
+      process(ls);
+    });
+  },
+
+  typeaheadClear:function () {
+    this.$().typeahead('destroy');
+  }.on('willClearRender')
+
+});

+ 1 - 0
contrib/views/pig/src/main/resources/ui/pig-web/app/initialize.js

@@ -170,3 +170,4 @@ require("components/jobProgress");
 require("components/pigHelper");
 require("components/scriptListRow");
 require("components/tabControl");
+require("components/pathInput");

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

@@ -30,8 +30,8 @@
       <div  {{bind-attr class=":alert :alert-danger titleErrorMessage::hide"}}>{{titleErrorMessage}}</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"}}
+      <label>{{t 'scripts.path'}}</label>
+      {{path-input class="form-control" placeholderTranslation="scripts.modal.file_path_placeholder" valueBinding="filePath" storeBinding="content.store"}}
       <small class="pull-right help-block">{{t 'scripts.modal.file_path_hint'}}</small>
     </div>
   </div>

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

@@ -23,12 +23,12 @@
   </div>
   <div class="modal-body">
   <div class="form-group">
-      <label for="exampleInputEmail1">{{t 'common.name'}}</label>
+      <label>{{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 class="form-group has-feedback">
+      <label>{{t 'common.path'}}</label>
+      {{path-input class="form-control isLoading" placeholderTranslation="udfs.modal.hdfs_path" valueBinding="content.path" storeBinding="content.store"}}
     </div>
   </div>
   <div class="modal-footer">

+ 2 - 1
contrib/views/pig/src/main/resources/ui/pig-web/bower.json

@@ -11,7 +11,8 @@
     "moment": "~2.8.1",
     "ember-i18n": "~1.6.0",
     "font-awesome": "4.2",
-    "file-saver": "*"
+    "file-saver": "*",
+    "bootstrap3-typeahead": "~3.0.3"
   },
   "overrides": {
     "codemirror":{

+ 8 - 8
contrib/views/pig/src/test/java/org/apache/ambari/view/pig/test/FileTest.java

@@ -125,7 +125,7 @@ public class FileTest extends HDFSTest {
     Response response = fileService.updateFile(request, filePath);
     Assert.assertEquals(204, response.getStatus());
 
-    Response response2 = fileService.getFile(filePath, 0L);
+    Response response2 = fileService.getFile(filePath, 0L, null);
     Assert.assertEquals(200, response2.getStatus());
 
     JSONObject obj = ((JSONObject) response2.getEntity());
@@ -140,7 +140,7 @@ public class FileTest extends HDFSTest {
 
     doCreateFile(name, "1234567890");
 
-    Response response = fileService.getFile(filePath, 0L);
+    Response response = fileService.getFile(filePath, 0L, null);
     Assert.assertEquals(200, response.getStatus());
 
     JSONObject obj = ((JSONObject) response.getEntity());
@@ -151,7 +151,7 @@ public class FileTest extends HDFSTest {
     Assert.assertTrue(((FileResource) obj.get("file")).isHasNext());
     Assert.assertEquals(filePath, ((FileResource) obj.get("file")).getFilePath());
 
-    response = fileService.getFile(filePath, 1L);
+    response = fileService.getFile(filePath, 1L, null);
     Assert.assertEquals(200, response.getStatus());
 
     obj = ((JSONObject) response.getEntity());
@@ -159,7 +159,7 @@ public class FileTest extends HDFSTest {
     Assert.assertEquals(1, ((FileResource) obj.get("file")).getPage());
     Assert.assertTrue(((FileResource) obj.get("file")).isHasNext());
 
-    response = fileService.getFile(filePath, 2L);
+    response = fileService.getFile(filePath, 2L, null);
     Assert.assertEquals(200, response.getStatus());
 
     obj = ((JSONObject) response.getEntity());
@@ -168,7 +168,7 @@ public class FileTest extends HDFSTest {
     Assert.assertFalse(((FileResource) obj.get("file")).isHasNext());
 
     thrown.expect(BadRequestFormattedException.class);
-    fileService.getFile(filePath, 3L);
+    fileService.getFile(filePath, 3L, null);
   }
 
   @Test
@@ -178,7 +178,7 @@ public class FileTest extends HDFSTest {
 
     doCreateFile(name, "");
 
-    Response response = fileService.getFile(filePath, 0L);
+    Response response = fileService.getFile(filePath, 0L, null);
     Assert.assertEquals(200, response.getStatus());
     JSONObject obj = ((JSONObject) response.getEntity());
     Assert.assertEquals("", ((FileResource) obj.get("file")).getFileContent());
@@ -189,7 +189,7 @@ public class FileTest extends HDFSTest {
   @Test
   public void testFileNotFound() throws IOException, InterruptedException {
     thrown.expect(NotFoundFormattedException.class);
-    fileService.getFile("/tmp/notExistentFile", 2L);
+    fileService.getFile("/tmp/notExistentFile", 2L, null);
   }
 
   @Test
@@ -202,6 +202,6 @@ public class FileTest extends HDFSTest {
     Assert.assertEquals(204, response.getStatus());
 
     thrown.expect(NotFoundFormattedException.class);
-    fileService.getFile(filePath, 0L);
+    fileService.getFile(filePath, 0L, null);
   }
 }