Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id D8EB1200BC5 for ; Mon, 7 Nov 2016 22:27:59 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id D7791160AF9; Mon, 7 Nov 2016 21:27:59 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 88117160AE0 for ; Mon, 7 Nov 2016 22:27:57 +0100 (CET) Received: (qmail 51114 invoked by uid 500); 7 Nov 2016 21:27:56 -0000 Mailing-List: contact commits-help@ambari.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: ambari-dev@ambari.apache.org Delivered-To: mailing list commits@ambari.apache.org Received: (qmail 51105 invoked by uid 99); 7 Nov 2016 21:27:56 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 07 Nov 2016 21:27:56 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 86F89E2F01; Mon, 7 Nov 2016 21:27:56 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: mithmatt@apache.org To: commits@ambari.apache.org Date: Mon, 07 Nov 2016 21:27:57 -0000 Message-Id: <7ec150a938fc44ec87f51efad9c6f854@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [2/2] ambari git commit: AMBARI-18779: Fix the backend for HAWQ View BETA (adenissov via mithmatt) archived-at: Mon, 07 Nov 2016 21:28:00 -0000 AMBARI-18779: Fix the backend for HAWQ View BETA (adenissov via mithmatt) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/8ac9fa10 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/8ac9fa10 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/8ac9fa10 Branch: refs/heads/trunk Commit: 8ac9fa10cf09b4d49efef7b4a9d6a244e86e9244 Parents: 4066b7e Author: Matt Authored: Mon Nov 7 13:27:41 2016 -0800 Committer: Matt Committed: Mon Nov 7 13:27:41 2016 -0800 ---------------------------------------------------------------------- contrib/views/hawq/README.md | 94 ++--- contrib/views/hawq/pom.xml | 381 +++++++++++-------- .../apache/ambari/view/hawq/HAWQDataSource.java | 94 +++++ .../ambari/view/hawq/HAWQViewServlet.java | 109 ------ .../ambari/view/hawq/JsonApiResource.java | 98 +++++ .../apache/ambari/view/hawq/QueryResource.java | 36 ++ .../ambari/view/hawq/QueryResourceProvider.java | 225 +++++++++++ .../apache/ambari/view/hawq/QueryService.java | 70 ++++ .../hawq/src/main/resources/WEB-INF/web.xml | 37 -- .../src/main/resources/ui/app/adapters/query.js | 14 +- .../views/hawq/src/main/resources/ui/app/app.js | 2 +- .../resources/ui/app/components/query-table.js | 6 +- .../src/main/resources/ui/app/models/query.js | 32 +- .../hawq/src/main/resources/ui/app/resolver.js | 2 +- .../hawq/src/main/resources/ui/app/router.js | 6 +- .../main/resources/ui/app/routes/application.js | 5 +- .../src/main/resources/ui/app/routes/main.js | 31 +- .../main/resources/ui/app/serializers/query.js | 51 ++- .../src/main/resources/ui/app/styles/app.scss | 10 +- .../resources/ui/app/templates/application.hbs | 2 +- .../ui/app/templates/components/query-table.hbs | 12 +- .../main/resources/ui/app/templates/main.hbs | 2 +- .../src/main/resources/ui/app/utils/utils.js | 72 ++-- .../views/hawq/src/main/resources/ui/bower.json | 5 +- .../src/main/resources/ui/config/environment.js | 4 +- .../hawq/src/main/resources/ui/package.json | 4 +- .../ui/tests/acceptance/application-test.js | 23 +- .../ui/tests/helpers/module-for-acceptance.js | 4 +- .../resources/ui/tests/helpers/test-helper.js | 103 +++-- .../integration/components/query-table-test.js | 77 ++-- .../src/main/resources/ui/tests/test-helper.js | 6 +- .../ui/tests/unit/adapters/query-test.js | 13 +- .../ui/tests/unit/models/query-test.js | 13 +- .../resources/ui/tests/unit/routes/main-test.js | 11 +- .../ui/tests/unit/serializers/query-test.js | 71 +++- .../resources/ui/tests/unit/utils/utils-test.js | 80 ++-- .../src/main/resources/view.log4j.properties | 27 ++ contrib/views/hawq/src/main/resources/view.xml | 45 +++ .../ambari/view/hawq/HAWQDataSourceTest.java | 170 +++++++++ .../view/hawq/QueryResourceProviderTest.java | 200 ++++++++++ contrib/views/pom.xml | 1 + 41 files changed, 1598 insertions(+), 650 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/README.md ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/README.md b/contrib/views/hawq/README.md index b8b51ba..aeaa868 100644 --- a/contrib/views/hawq/README.md +++ b/contrib/views/hawq/README.md @@ -13,88 +13,52 @@ [](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.) -# HAWQ Monitoring View for Ambari -This view provides a UI to monitor HAWQ queries. -### Overview and Examples -You may find instructive the [Ambari Views Overview], which demonstrates how Ambari uses third-party views and how to create your own view. [Here][view example] you may find a view example. +# HAWQ View -### Build All Views (must be done at least once) -```sh -cd $AMBARI_DIR/contrib/views -mvn install -DskipTests -``` +**HAWQ View** provides a **Query Monitor** for HAWQ which displays the current running queries. -### Build HAWQ View -```sh -cd $AMBARI_DIR/contrib/views/hawq -mvn install [-DskipTests] -``` +The HAWQ View frontend is built based on EmberJS framework. The Java backend extends the framework provided by the ambari-views project. -### Setting-Up The Enviornment -In order to prepare a vagrant environment, firstly follow the instructions in the [Ambari Dev Quick Start Guide]. +The frontend polls the REST API periodically (5 seconds). The REST endpoint is responsible for querying the data from the *pg_stat_activity* table for every ```GET``` request on ```/queries``` resource. -### Deploy JAR file -```sh -vagrant ssh -sudo -i -ln -s /vagrant/ambari/contrib/views/hawq/target/hawq-view-X.Y.Z.Q-SNAPSHOT.jar /var/lib/ambari-server/resources/views/hawq-view-X.Y.Z.Q-SNAPSHOT.jar -ambari-server restart -``` -- Create an instance of view from “Manage Ambari” category in Ambari. -If you wish to overwrite an installation of a view, then enter the vagrant box as root and -```sh -rm -rf /var/lib/ambari-server/resources/views/work/HAWQ\{X.Y.Z\} -``` -(note that there is no trailing `/`) before restarting the Ambari server. If you have made changes to the view, and those changes have not been reflected in the UI, then create a temporary throwaway view. This may prompt Ambari to remove any stale references to the old view JAR in place of what you have just uploaded. +## Building and Deploying HAWQ View -### Ember Development -The Hawq Monitoring View has been implemented using Ember 2.4.2; the tooling framework relies on Node 4.3.2. There are a number of tools which you may need to install locally, starting with `nvm` (Node Version Manager). You may wish to install the following tools while located in `$AMBARI_DIR`: +#### Building -```sh -nvm install 4.3.2 -nvm use 4.3.2 -npm install ember-cli -``` +The HAWQ View is dependent on the ambari-views artifact. As a pre-requisite, build the *ambari-views* project. -This set of tools should allow you to use the Ember CLI tools for creating stub-files for controllers, routes, models, etc., in addition to `ember` for compiling and testing. At the moment, `npm build`, `npm start`, and `npm test` all invoke the `ember` CLI tool. +```$AMBARI_DIR``` refers to the top-level directory for Ambari source code. -### Local Javascript testing without the overhead of Maven -To do iterative unit testing while coding, firstly make a build using maven. Afterward, -```sh -cd $AMBARI_DIR/contrib/views/hawq/src/main/resources/ui/ -npm start # To continuously test that your code compiles -``` -and, when you want to test the code, open another terminal and ```sh -npm test +# Build ambari-views project +cd $AMBARI_DIR/ambari-views +mvn install [-DskipTests] +# Build HAWQ View +cd $AMBARI_DIR/contrib/views/hawq +mvn install [-DskipTests] ``` -### Ambari Versions -Be careful when moving this code from branch to branch: the Ambari version referenced in pom.xml must match the branch. You may have to reference other views (e.g. hive or pig) in the destination branch to get some idea of what you must change. +#### Deploying -### Debug Setup -On the machine hosting vagrant: +Copy the hawq-view jar to the ambari-server host and restart ambari-server. ```sh -vagrant ssh -sudo -i -cd /var/lib/ambari-server/resources/views/work # if this directory does not exist, you have not started ambari-server; run "ambari-server start" to start it -rm -rf HAWQ\{X.Y.Z\} -ln -s /vagrant/ambari/contrib/views/hawq/src/main/resources/ui/dist HAWQ\{X.Y.Z\} -ln -s /vagrant/ambari/contrib/views/hawq/target/classes/org/ HAWQ\{X.Y.Z\}/org -ln -s /vagrant/ambari/contrib/views/hawq/target/classes/WEB-INF/ HAWQ\{X.Y.Z\}/WEB-INF -ln -s /vagrant/ambari/contrib/views/hawq/src/main/resources/view.xml HAWQ\{X.Y.Z\}/view.xml -ambari-server restart +scp $AMBARI_DIR/contrib/views/hawq/target/hawq-view-${version}.jar ambari.server.host:/var/lib/ambari-server/resources/views/ +ambari-server start ``` -Note: if you want to remove the symbolic link `/var/lib/ambari-server/resources/views/work/HAWQ\{X.Y.Z\}`, use `rm` and not `rm -rf`. +## Creating an Instance of HAWQ View + +The HAWQ View instance connects to the HAWQ Master through JDBC. Ssh into the HAWQ Master host and update *pg_hba.conf* to allow connections from the ambari-server host for the user which has access to the *pg_stat_activity* table. + +By default the *gpadmin* user has aceess to the *pg_stat_activity* table. Restart HAWQ Master from Ambari dashboard for changes to take effect. -### Incremental Builds For Java Proxy -The symbolic links generated in the Debug Setup section allow for the incremental updating of the Java proxy. Each build with `mvn` deletes the symlinks from Debug Setup. They must be recreated, and then the Ambari server must be restarted. Additionally, each invocation of `npm start` or `ember serve` will destroy the links and require them to be recreated using the instructions in Debug Setup. However, while the local Ember server is runnig, the links will not be removed by the server. +Example of entry in *pg_hba.conf*, where ```192.168.64.101``` is my ambari-server host: +``` +host all gpadmin 192.168.64.101/32 trust +``` -[//]: # +Navigate to the *Views* tab on *Manage Ambari* page. Click on *Create Instance* under *HAWQ* tab. Under *Settings* section, provide the HAWQ database username and password of the user who has access to the *pg_stat_activity* table. (The same user that was added to the *pg_hba.conf* for the ambari-server host entry) -[ambari views overview]: -[view example]: -[ambari dev quick start guide]: +Upon clicking *Save*, the view will be created. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/pom.xml ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/pom.xml b/contrib/views/hawq/pom.xml index e49a23b..4fc543f 100644 --- a/contrib/views/hawq/pom.xml +++ b/contrib/views/hawq/pom.xml @@ -15,162 +15,231 @@ limitations under the License. --> - 4.0.0 - org.apache.ambari.contrib.views - hawq-view - 1.0.0.0-SNAPSHOT - HAWQ - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 org.apache.ambari.contrib.views - ambari-contrib-views - 2.0.0.0-SNAPSHOT - - - - junit - junit - test - - - javax.servlet - servlet-api - 2.5 - provided - - + hawq-view + 1.0.0.0-SNAPSHOT + HAWQ - - ${project.parent.parent.parent.basedir} - ${basedir}/src/main/resources/ui - 1.0.0 - 2.0.0.0-SNAPSHOT - linux - / - node - npm - - + + org.apache.ambari.contrib.views + ambari-contrib-views + 2.0.0.0-SNAPSHOT + - - - - - org.apache.maven.plugins - maven-clean-plugin - 2.5 - - - - ${ui.dir} - false - - dist/** - tmp/** - node/** - node_modules/** - bower_components/** - - - dist/view.xml - dist/WEB-INF - dist/org - - - - - - - - com.github.eirslett - frontend-maven-plugin - - v4.5.0 - 2.15.0 - ${ui.dir} - - - - install node and npm - generate-sources - - install-node-and-npm - - - - npm install - generate-sources - - npm - - - install --unsafe-perm - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.3.2 - - - HAWQ View build - generate-sources - - exec - - - ${ui.dir} - node/node - - node_modules/.bin/ember - build - --environment=production - - - - - HAWQ View Javascript Tests - test - - exec - - - ${skipTests} - ${executable.npm} - ${ui.dir} - ${args.npm} test - - - - - - - - src/main/resources - false - - view.xml - WEB-INF/** - - - - ${ui.dir}/dist - false - - index.html - crossdomain.xml - robots.txt - assets/**/* - - - assets/**/test*.* - assets/*.map - assets/passed.png - assets/failed.png - - - - - + + + postgresql + postgresql + 9.1-901-1.jdbc4 + + + junit + junit + test + + + org.easymock + easymock + test + + + javax.servlet + servlet-api + 2.5 + provided + + + com.sun.jersey + jersey-server + 1.8 + + + javax.inject + javax.inject + 1 + + + org.apache.ambari + ambari-views + provided + + + org.slf4j + slf4j-api + 1.7.5 + + + c3p0 + c3p0 + 0.9.1.2 + + + org.powermock + powermock-module-junit4 + 1.6.3 + test + + + org.powermock + powermock-api-easymock + 1.6.3 + test + + + + + ${project.parent.parent.parent.basedir} + ${basedir}/src/main/resources/ui + 1.0.0 + 2.0.0.0-SNAPSHOT + node/node + npm + + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 2.5 + + + + ${ui.dir} + false + + dist/** + tmp/** + node/** + node_modules/** + bower_components/** + + + dist/view.xml + dist/WEB-INF + dist/org + + + + + + + + + com.github.eirslett + frontend-maven-plugin + + v4.5.0 + 2.15.0 + ${ui.dir} + + + + install node and npm + generate-sources + + install-node-and-npm + + + + npm install + generate-sources + + npm + + + install --unsafe-perm + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.3.2 + + + HAWQ View build + generate-sources + + exec + + + ${ui.dir} + ${executable.node} + + node_modules/.bin/ember + build + --environment=production + + + + + HAWQ View Javascript Tests + test + + exec + + + ${skipTests} + ${executable.npm} + ${ui.dir} + ${args.npm} test + + + + + + + maven-dependency-plugin + + + generate-resources + + copy-dependencies + + + ${project.build.outputDirectory}/WEB-INF/lib + runtime + + + + + + + + + src/main/resources + false + + view.xml + view.log4j.properties + + + + + ${ui.dir}/dist + false + + index.html + crossdomain.xml + robots.txt + assets/**/* + + + assets/**/test*.* + assets/*.map + assets/passed.png + assets/failed.png + + + + + + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/HAWQDataSource.java ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/HAWQDataSource.java b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/HAWQDataSource.java new file mode 100644 index 0000000..ab2e230 --- /dev/null +++ b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/HAWQDataSource.java @@ -0,0 +1,94 @@ +/* + 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.hawq; + +import com.mchange.v2.c3p0.ComboPooledDataSource; +import org.apache.ambari.view.SystemException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.SQLException; + +class HAWQDataSource { + + private final static Logger LOG = LoggerFactory.getLogger(HAWQDataSource.class); + + private volatile ComboPooledDataSource dataSource; + private volatile String dataSourceKey; + + Connection getConnection(String host, String port, String user, String password) throws SystemException, SQLException { + + LOG.debug("Getting connection for = {}:{}:{}", host, port, user); + + // check if configuration changed from when the data source was created + String key = composeKey(host, port, user, password); + if (!key.equals(dataSourceKey)) { + resetDataSource(host, port, user, password); + } + + Connection conn = dataSource.getConnection(); + LOG.debug("Acquired connection = {}", conn); + return conn; + } + + private String composeKey(String host, String port, String user, String password) { + return host + port + user + password; + } + + private synchronized void resetDataSource(String host, String port, String user, String password) throws SystemException { + // double check locking using volatile members + String key = composeKey(host, port, user, password); + if (key.equals(dataSourceKey)) { + return; + } + + // close old datasource + if (dataSource != null) { + dataSource.close(); + } + + // create new datasource + ComboPooledDataSource cpds = new ComboPooledDataSource(); + try { + cpds.setDriverClass("org.postgresql.Driver"); //loads the jdbc driver + } catch (Exception e) { + throw new SystemException("Failed to load JDBC driver.", e); + } + String url = String.format("jdbc:postgresql://%s:%s/template1", host, port); + cpds.setJdbcUrl(url); + cpds.setUser(user); + cpds.setPassword(password); + cpds.setInitialPoolSize(0); + cpds.setMinPoolSize(0); + cpds.setMaxPoolSize(1); + cpds.setMaxIdleTime(600); // 10 minutes + // spend no more than 5 secs trying to acquire a connection + cpds.setAcquireRetryAttempts(5); + cpds.setAcquireRetryDelay(1000); + + LOG.info("Set data source for url = {} and user = {}", url, user); + + // update volatile members before relinquishing the lock + dataSource = cpds; + dataSourceKey = key; + } + +} + http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/HAWQViewServlet.java ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/HAWQViewServlet.java b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/HAWQViewServlet.java deleted file mode 100644 index 4202eef..0000000 --- a/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/HAWQViewServlet.java +++ /dev/null @@ -1,109 +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. - */ - -package org.apache.ambari.view.hawq; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; -import java.util.Random; - -/** - * Servlet for HAWQ Queries list view. - */ -public class HAWQViewServlet extends HttpServlet { - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - // TODO use constant - response.setContentType("application/json"); - response.setStatus(HttpServletResponse.SC_OK); - - Random randNo = new Random(); - - TimeZone timeZone = TimeZone.getTimeZone("PST"); - DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - df.setTimeZone(timeZone); - - StringBuilder responseString = new StringBuilder("{ \"data\": ["); - - String[] databaseNames = {"gpadmin", "postgres", "template1", "template2", "employee", "users", "payroll", "taxes", "benefits", "projects"}; - String[] userNames = {"newton aLex", "alex DeniSSov", "v", "jun", "bhuvNeshChaudhary", "laVjAiN", "m@tt", "5Z", "gt"}; - String[] clientHosts = {"c6401.ambari.apache.org", "c6402.ambari.apache.org", "c6403.ambari.apache.org", "c6404.ambari.apache.org", "c6405.ambari.apache.org"}; - String[] applicationNames = {"hawq", "psql", "tableau", "excel", "hbase", "hive"}; - String[] duration = {"00:00:00", "02:01:00", "03:04:00"}; - - int distribution = randNo.nextInt(3); - int maxNoOfQUeries = 100; - int noOfQueries = 0; - - switch(distribution) { - case 0: - noOfQueries = 0; - break; - case 2: - noOfQueries = maxNoOfQUeries; - break; - case 1: - default: - noOfQueries = randNo.nextInt(maxNoOfQUeries); - } - - int index = 1; - long currTime = System.currentTimeMillis() / 1000; - - while(index <= noOfQueries) { - long queryStartTime = currTime - (randNo.nextInt(55000) + 1) * 1000; - long transactionStartTime = queryStartTime - (randNo.nextInt(40000) + 1) * 1000; - long backendStartTime = transactionStartTime - (randNo.nextInt(100000) + 1) * 1000; - - String query = "{"+ - " \"id\": " + (index++) + ","+ - " \"type\": \"query\","+ - " \"attributes\": {"+ - " \"database-name\": \"" + databaseNames[randNo.nextInt(databaseNames.length)] + "\","+ - " \"pid\": " + (randNo.nextInt(99999) + 1) + ","+ - " \"user-name\": \"" + userNames[randNo.nextInt(userNames.length)] + "\","+ - " \"waiting\": " + randNo.nextBoolean() + ","+ - " \"waiting-resource\": " + randNo.nextBoolean() + ","+ - " \"duration\": \"" + duration[randNo.nextInt(duration.length)] + "\","+ - " \"query-start-time\": \"" + df.format(queryStartTime) + "\","+ - " \"transaction-start-time\": \"" + df.format(transactionStartTime) + "\","+ - " \"client-host\": \"" + clientHosts[randNo.nextInt(clientHosts.length)] + "\","+ - " \"client-port\": \"" + (randNo.nextInt(99999) + 1) + "\","+ - " \"application-name\": \"" + applicationNames[randNo.nextInt(applicationNames.length)] + "\""+ - " }"+ - " }"; - - responseString.append(query).append(index <= noOfQueries ? "," : ""); - } - - responseString.append("]}"); - - PrintWriter writer = response.getWriter(); - writer.print(responseString.toString()); - } -} http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/JsonApiResource.java ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/JsonApiResource.java b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/JsonApiResource.java new file mode 100644 index 0000000..79bcacc --- /dev/null +++ b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/JsonApiResource.java @@ -0,0 +1,98 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at +

+ http://www.apache.org/licenses/LICENSE-2.0 +

+ Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package org.apache.ambari.view.hawq; + + +import java.util.Map; + +/** + * JsonApi resource -- represents an object in JSON-API format. + */ +class JsonApiResource { + + /** + * The object id. + */ + private String id; + + /** + * The object type. + */ + private String type; + + /** + * The query attributes. + */ + private Map attributes; + + /** + * Get the object id. + * + * @return the id + */ + public String getId() { + return id; + } + + /** + * Set the object id. + * + * @param id the id + */ + public void setId(String id) { + this.id = id; + } + + /** + * Get the object type. + * + * @return the type + */ + public String getType() { + return type; + } + + /** + * Set the object type. + * + * @param type the type + */ + public void setType(String type) { + this.type = type; + } + + /** + * Get the object attributes. + * + * @return the object attributes + */ + public Map getAttributes() { + return attributes; + } + + /** + * Set the object attributes. + * + * @param attributes the object attributes + */ + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryResource.java ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryResource.java b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryResource.java new file mode 100644 index 0000000..d9b3af9 --- /dev/null +++ b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryResource.java @@ -0,0 +1,36 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at +

+ http://www.apache.org/licenses/LICENSE-2.0 +

+ Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package org.apache.ambari.view.hawq; + +/** + * Query resource -- represents a query information. + */ +public class QueryResource extends JsonApiResource { + + private static final String TYPE = "query"; + + static final String ID_PROPERTY = "procpid"; + + /** + * Constructor. + */ + QueryResource() { + this.setType(TYPE); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryResourceProvider.java ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryResourceProvider.java b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryResourceProvider.java new file mode 100644 index 0000000..162d309 --- /dev/null +++ b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryResourceProvider.java @@ -0,0 +1,225 @@ +/* + 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.hawq; + +import org.apache.ambari.view.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.sql.*; +import java.util.*; + +/** + * Resource provider for query resource. + */ +public class QueryResourceProvider implements ResourceProvider { + + /** + * The logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(QueryResourceProvider.class); + + /** + * The View Context Properties. + */ + private static final String USER_PROP = "hawq.user.name"; + private static final String USER_DESC = "HAWQ User Name"; + + private static final String PASSWORD_PROP = "hawq.user.password"; + private static final String PASSWORD_DESC = "HAWQ User Password"; + + private static final String HOSTNAME_PROP = "hawq.master.host"; + private static final String HOSTNAME_DESC = "HAWQ Master Host"; + + private static final String HOSTPORT_PROP = "hawq.master.port"; + private static final String HOSTPORT_DESC = "HAWQ Master Port"; + + /** + * The query to run in the database. + */ + private static final String QUERY = + "SELECT datid, datname, procpid, sess_id, usesysid, usename, application_name," + + " current_query, waiting, waiting_resource, HOST(client_addr) as client_addr, client_port," + + " TO_CHAR(query_start, 'YYYY-MM-DD HH24:MI:SS') AS query_start," + + " TO_CHAR(backend_start, 'YYYY-MM-DD HH24:MI:SS') AS backend_start," + + " TO_CHAR(xact_start, 'YYYY-MM-DD HH24:MI:SS') AS xact_start," + + " ROUND(EXTRACT(EPOCH FROM DATE_TRUNC('second', NOW() - query_start))) AS query_duration " + + "FROM pg_stat_activity " + + "WHERE current_query NOT LIKE '' AND current_query NOT LIKE '%pg_stat_activity%';"; + + + /** + * The view context. + */ + @Inject + ViewContext viewContext; + + /** + * The datasource wrapper on top of connection pool used to connect to the HAWQ Master. + */ + private HAWQDataSource dataSource = new HAWQDataSource(); + + // ----- ResourceProvider -------------------------------------------------- + + @Override + public QueryResource getResource(String resourceId, Set propertyIds) throws + SystemException, NoSuchResourceException, UnsupportedPropertyException { + throw new UnsupportedOperationException("Getting query resource is not currently supported."); + } + + @Override + public Set getResources(ReadRequest request) throws + SystemException, NoSuchResourceException, UnsupportedPropertyException { + + Set resources; + try { + LOG.debug("Get queries: View Context Properties = {}", viewContext.getProperties()); + resources = getQueries(); + LOG.debug("Retrieved {} queries from HAWQ Master", resources.size()); + } catch (SQLException e) { + LOG.error(e.getMessage(), e); + throw new SystemException("Can't get current queries.", e); + } + + return resources; + } + + @Override + public void createResource(String resourceId, Map stringObjectMap) throws + SystemException, ResourceAlreadyExistsException, NoSuchResourceException, UnsupportedPropertyException { + throw new UnsupportedOperationException("Creating query resources is not currently supported."); + } + + @Override + public boolean updateResource(String resourceId, Map stringObjectMap) throws + SystemException, NoSuchResourceException, UnsupportedPropertyException { + throw new UnsupportedOperationException("Updating query resources is not currently supported."); + } + + @Override + public boolean deleteResource(String resourceId) throws + SystemException, NoSuchResourceException, UnsupportedPropertyException { + throw new UnsupportedOperationException("Deleting query resources is not currently supported."); + } + + + // ----- helper methods ---------------------------------------------------- + + /** + * Get the property from the view context. + * + * @param property property name + * @param description property description + * @throws SystemException if the property value is not specified + */ + private String getViewContextProperty(String property, String description) throws SystemException { + String value = viewContext.getProperties().get(property); + if (value == null || value.isEmpty()) { + throw new SystemException(description + " property is not specified for the view instance.", new Exception()); + } + return value; + } + + /** + * Retrieves a set of current queries from the HAWQ Master. + * + * @return a set of queries + * @throws SystemException if configuration parameters are invalid + * @throws SQLException if data can't be retrieved from the database + */ + private Set getQueries() throws SystemException, SQLException { + Set result = new HashSet<>(); + + Connection conn = null; + Statement st = null; + ResultSet rs = null; + try { + conn = dataSource.getConnection(getViewContextProperty(HOSTNAME_PROP, HOSTNAME_DESC), + getViewContextProperty(HOSTPORT_PROP, HOSTPORT_DESC), + getViewContextProperty(USER_PROP, USER_DESC), + getViewContextProperty(PASSWORD_PROP, PASSWORD_DESC)); + st = conn.createStatement(); + + LOG.debug("Executing query"); + rs = st.executeQuery(QUERY); + + ResultSetMetaData md = rs.getMetaData(); + int columns = md.getColumnCount(); + + // iterate over ResultSet, creating QueryResource objects from the records + while (rs.next()) { + Map row = new HashMap<>(columns); + for (int i = 1; i <= columns; ++i) { + row.put(md.getColumnName(i), rs.getObject(i)); + } + QueryResource query = new QueryResource(); + query.setId(row.get(QueryResource.ID_PROPERTY).toString()); + query.setAttributes(row); + LOG.debug("row={}", row); + result.add(query); + } + } finally { + closeResultSet(rs); + closeStatement(st); + closeConnection(conn); + } + return result; + } + + /** + * Closes a result set, ignoring any exceptions. + * + * @param rs ResultSet to close + */ + private void closeResultSet(ResultSet rs) { + try { + if (rs != null) rs.close(); + } catch (Exception e) { + // ignore + } + } + + /** + * Closes a statement ignoring any exceptions. + * + * @param st Statement to close + */ + private void closeStatement(Statement st) { + try { + if (st != null) st.close(); + } catch (Exception e) { + // ignore + } + } + + /** + * Closes a connection ignoring any exceptions. Pooled connection would be returned to the pool. + * + * @param conn connection to close + */ + private void closeConnection(Connection conn) { + try { + if (conn != null) conn.close(); + } catch (Exception e) { + // ignore + } + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryService.java ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryService.java b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryService.java new file mode 100644 index 0000000..9258553 --- /dev/null +++ b/contrib/views/hawq/src/main/java/org/apache/ambari/view/hawq/QueryService.java @@ -0,0 +1,70 @@ +/* + 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.hawq; + +import org.apache.ambari.view.ViewContext; +import org.apache.ambari.view.ViewResourceHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +/** + * The Query service. + */ +public class QueryService { + + private final static Logger LOG = + LoggerFactory.getLogger(QueryResourceProvider.class); + + + /** + * The resource request handler. + */ + @Inject + ViewResourceHandler resourceHandler; + + /** + * The view context. + */ + @Inject + ViewContext context; + + /** + * Handles: GET /queries Get all queries. + * + * @param headers http headers + * @param ui uri info + * @return query collection resource representation + */ + @GET + @Produces({"text/plain", "application/json"}) + public Response getQueries(@Context HttpHeaders headers, @Context UriInfo ui) { + + Response response = resourceHandler.handleRequest(headers, ui, null); + LOG.debug("Response Entity = {}", response.getEntity()); + return response; + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/WEB-INF/web.xml b/contrib/views/hawq/src/main/resources/WEB-INF/web.xml deleted file mode 100644 index e12f314..0000000 --- a/contrib/views/hawq/src/main/resources/WEB-INF/web.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - HAWQ View - - This is the HAWQ View application. - - - HAWQViewServlet - org.apache.ambari.view.hawq.HAWQViewServlet - - - HAWQViewServlet - /api/v1/* - - \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/adapters/query.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/adapters/query.js b/contrib/views/hawq/src/main/resources/ui/app/adapters/query.js index f82c00d..5ec182a 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/adapters/query.js +++ b/contrib/views/hawq/src/main/resources/ui/app/adapters/query.js @@ -16,12 +16,16 @@ * limitations under the License. */ -import Ember from 'ember'; import DS from 'ember-data'; import Utils from 'hawq-view/utils/utils'; export default DS.JSONAPIAdapter.extend({ - namespace: Ember.computed(function() { - return Utils.getNamespace(); - }) -}); + headers: { + 'X-Requested-By': 'ambari', + 'Accept': '*/*' + }, + + buildURL() { + return Utils.getNamespace() + this._super.apply(this, arguments); + } +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/app.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/app.js b/contrib/views/hawq/src/main/resources/ui/app/app.js index af4fdc4..4adda94 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/app.js +++ b/contrib/views/hawq/src/main/resources/ui/app/app.js @@ -33,4 +33,4 @@ App = Ember.Application.extend({ loadInitializers(App, config.modulePrefix); -export default App; +export default App; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/components/query-table.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/components/query-table.js b/contrib/views/hawq/src/main/resources/ui/app/components/query-table.js index d28e2f2..3f3ca81 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/components/query-table.js +++ b/contrib/views/hawq/src/main/resources/ui/app/components/query-table.js @@ -21,8 +21,10 @@ import Ember from 'ember'; export default Ember.Component.extend({ didRender() { this._super(...arguments); + // Enable tooltips + Ember.$('[data-toggle="tooltip"]').tooltip(); // Ember.$ is contextual == #bootstrapSortable is called by default but at too high a level within Ember // (and Ambari) to find the table which it is supposed to modify. - Ember.$.bootstrapSortable(true); + // Ember.$.bootstrapSortable({applyLast: true, sign: 'reversed'}); } -}); +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/models/query.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/models/query.js b/contrib/views/hawq/src/main/resources/ui/app/models/query.js index 5906c34..0fde6d3 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/models/query.js +++ b/contrib/views/hawq/src/main/resources/ui/app/models/query.js @@ -21,27 +21,27 @@ import DS from 'ember-data'; import Utils from 'hawq-view/utils/utils'; export default DS.Model.extend({ + databaseName: DS.attr('string'), + pid: DS.attr('number'), + userName: DS.attr('string'), + queryText: DS.attr('string'), + waiting: DS.attr('boolean'), + waitingResource: DS.attr('boolean'), + duration: DS.attr('number'), + queryStartTime: DS.attr('string'), + clientHost: DS.attr('string'), + clientPort: DS.attr('number'), + applicationName: DS.attr('string'), - databaseName: DS.attr('string'), - pid: DS.attr('number'), - userName: DS.attr('string'), - queryText: DS.attr('string'), - waiting: DS.attr('boolean'), - waitingResource: DS.attr('boolean'), - duration: DS.attr('string'), - queryStartTime: DS.attr('date'), - clientHost: DS.attr('string'), - clientPort: DS.attr('number'), - applicationName: DS.attr('string'), - - clientAddress: Ember.computed('clientHost', 'clientPort', function() { + clientAddress: Ember.computed('clientHost', 'clientPort', function () { return Utils.computeClientAddress(this.get('clientHost'), this.get('clientPort')); }), - status: Ember.computed('waiting', 'waitingResource', function() { + status: Ember.computed('waiting', 'waitingResource', function () { return Utils.generateStatusString(this.get('waiting'), this.get('waitingResource')); }), - formattedQueryStartTime: Ember.computed('queryStartTime', function () { - return Utils.formatTimeOfDay(this.get('queryStartTime')); + + formattedDuration: Ember.computed('duration', function () { + return Utils.formatDuration(this.get('duration')); }) }); http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/resolver.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/resolver.js b/contrib/views/hawq/src/main/resources/ui/app/resolver.js index b9eabe4..9d58c60 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/resolver.js +++ b/contrib/views/hawq/src/main/resources/ui/app/resolver.js @@ -18,4 +18,4 @@ import Resolver from 'ember-resolver'; -export default Resolver; +export default Resolver; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/router.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/router.js b/contrib/views/hawq/src/main/resources/ui/app/router.js index f058f78..d7c9c50 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/router.js +++ b/contrib/views/hawq/src/main/resources/ui/app/router.js @@ -23,8 +23,8 @@ const Router = Ember.Router.extend({ location: config.locationType }); -Router.map(function() { - this.route('main', { path: '/' }); +Router.map(function () { + this.route('main', {path: '/'}); }); -export default Router; +export default Router; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/routes/application.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/routes/application.js b/contrib/views/hawq/src/main/resources/ui/app/routes/application.js index 4ad870b..109d05a 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/routes/application.js +++ b/contrib/views/hawq/src/main/resources/ui/app/routes/application.js @@ -19,5 +19,6 @@ import Ember from 'ember'; export default Ember.Route.extend({ - model() {} -}); + model() { + } +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/routes/main.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/routes/main.js b/contrib/views/hawq/src/main/resources/ui/app/routes/main.js index a05324a..cea6bbf 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/routes/main.js +++ b/contrib/views/hawq/src/main/resources/ui/app/routes/main.js @@ -21,29 +21,20 @@ import ENV from 'hawq-view/config/environment'; export default Ember.Route.extend({ model() { - return this.store.findAll('query'); + return this.store.query('query', {fields: '*'}); }, - setupController: function(controller, model) { - this._super(controller, model); - if(ENV.shouldPoll) { - this.startRefreshing(); - } - }, - startRefreshing: function() { - this.set('refreshing', true); - Ember.run.later(this, this.refresh, ENV.pollingInterval); - }, - refresh: function() { - if(!this.get('refreshing')) { + + activate() { + if (ENV.environment === 'test') { return; } - this.store.unloadAll('query'); - this.store.findAll('query'); - Ember.run.later(this, this.refresh, ENV.pollingInterval); + this.set('timer', setInterval(this.refresh.bind(this), ENV.pollingInterval)); }, - actions: { - willTransition: function() { - this.set('refreshing', false); + + deactivate() { + if (ENV.environment === 'test') { + return; } + clearTimeout(this.get('timer')); } -}); +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/serializers/query.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/serializers/query.js b/contrib/views/hawq/src/main/resources/ui/app/serializers/query.js index 9546da4..8958aaf 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/serializers/query.js +++ b/contrib/views/hawq/src/main/resources/ui/app/serializers/query.js @@ -16,10 +16,55 @@ * limitations under the License. */ +import Ember from 'ember'; import DS from 'ember-data'; export default DS.JSONAPISerializer.extend({ - normalizeResponse() { - return this._super(...arguments); + normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) { + this.transformPayload(payload); + return this._super(store, primaryModelClass, payload, id, requestType); + }, + + keyForAttribute() { + // attribute keys are in underscore format + return Ember.String.underscore(...arguments); + }, + + transformPayload(payload) { + // map attribute key name to the name expected by Query model + const attributesMap = { + "datname": "database_name", + "procpid": "pid", + "usename": "user_name", + "current_query": "query_text", + "query_duration": "duration", + "query_start": "query_start_time", + "client_addr": "client_host" + }; + + const attributesMapKeys = Object.keys(attributesMap); + + // generate data as expected by Ember DS + payload.data = []; + + payload.items.forEach(function (dataItem) { + let tempItem = {"attributes": {}}; + + Object.keys(dataItem).forEach(function (dataItemKey) { + // if key is 'type' update value of 'type' as 'queries' for array response + tempItem[dataItemKey] = (dataItemKey === 'type') ? 'queries' : dataItem[dataItemKey]; + }, this); + + Object.keys(dataItem.attributes).forEach(function (attributeKey) { + tempItem.attributes[attributesMapKeys.contains(attributeKey) ? attributesMap[attributeKey] : attributeKey] = dataItem.attributes[attributeKey]; + }, this); + + payload.data.push(tempItem); + }, this); + + // delete additional key-value pairs from payload + delete payload.items; + delete payload.href; + // the payload will contain only one key (data) at this stage } -}); +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/styles/app.scss ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/styles/app.scss b/contrib/views/hawq/src/main/resources/ui/app/styles/app.scss index 7470867..6cf1492 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/styles/app.scss +++ b/contrib/views/hawq/src/main/resources/ui/app/styles/app.scss @@ -17,7 +17,7 @@ */ @import './../../bower_components/bootstrap-sass/assets/stylesheets/_bootstrap.scss'; -$border-grey: #DDD; +$border-grey: #dddddd; .container-fluid { font-size: 14px; @@ -48,10 +48,14 @@ $border-grey: #DDD; font-weight: bold; } +.beta { + color: #12526e; +} + .green { - color: #6CC16F + color: #6cc16f; } .orange { - color: #FB7F00 + color: #fb7f00; } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/templates/application.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/templates/application.hbs b/contrib/views/hawq/src/main/resources/ui/app/templates/application.hbs index 4c831d1..448c635 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/templates/application.hbs +++ b/contrib/views/hawq/src/main/resources/ui/app/templates/application.hbs @@ -19,4 +19,4 @@

{{outlet}} -
+ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/templates/components/query-table.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/templates/components/query-table.hbs b/contrib/views/hawq/src/main/resources/ui/app/templates/components/query-table.hbs index 5af548a..63a4732 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/templates/components/query-table.hbs +++ b/contrib/views/hawq/src/main/resources/ui/app/templates/components/query-table.hbs @@ -16,10 +16,9 @@ limitations under the License. }} -
-
Query Monitor
+
Query Monitor BETA
@@ -41,13 +40,14 @@ {{else}} {{#each queries as |query index|}} - + - - + + {{/each}} @@ -56,4 +56,4 @@
{{query.pid}} {{query.status}} {{query.userName}} {{query.databaseName}}{{query.formattedQueryStartTime}}{{query.duration}}{{query.queryStartTime}}{{query.formattedDuration}} {{query.clientAddress}}
-{{yield}} \ No newline at end of file +{{yield}} http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/templates/main.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/templates/main.hbs b/contrib/views/hawq/src/main/resources/ui/app/templates/main.hbs index 6baba0c..a2a1607 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/templates/main.hbs +++ b/contrib/views/hawq/src/main/resources/ui/app/templates/main.hbs @@ -17,4 +17,4 @@ }} {{query-table queries=model}} -{{outlet}} +{{outlet}} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/app/utils/utils.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/app/utils/utils.js b/contrib/views/hawq/src/main/resources/ui/app/utils/utils.js index 7b02cca..ebb82a9 100644 --- a/contrib/views/hawq/src/main/resources/ui/app/utils/utils.js +++ b/contrib/views/hawq/src/main/resources/ui/app/utils/utils.js @@ -17,62 +17,60 @@ */ /*jshint node:true*/ -/* global moment */ import ENV from 'hawq-view/config/environment'; export default { - formatTimeOfDay: function(date) { - return moment(date).local().format("HH:mm:ss"); + computeClientAddress(clientHost, clientPort) { + return (clientPort === -1 || !clientHost) ? 'local' : `${clientHost}:${clientPort}`; }, - calculateTimeDelta: function(time1, time2) { - return moment(time2).diff(moment(time1)); - }, - - formatTimeDelta: function(time1, time2) { - let response = { - value: Math.max(0, this.calculateTimeDelta(time1, time2)) - }; - - let delta = Math.round((response.value) / 1000); - let deltaString = `${delta % 60}s`; - delta = Math.floor(delta / 60); - - if (delta > 0) { - deltaString = `${delta % 60}m ${deltaString}`; - delta = Math.floor(delta / 60); + formatDuration(duration) { + if (isNaN(duration)) { + return "00:00:00"; } - if (delta > 0) { - deltaString = `${delta}h ${deltaString}`; - } + let hours = Math.floor(duration / 3600); + let durationForMinutes = duration % 3600; + let minutes = Math.floor(durationForMinutes / 60); + let seconds = Math.ceil(durationForMinutes % 60); - response.text = deltaString; - return response; - }, + hours = ( hours < 10 ? "0" : "") + hours; + minutes = (minutes < 10 ? "0" : "") + minutes; + seconds = (seconds < 10 ? "0" : "") + seconds; - computeClientAddress: function(clientHost, clientPort) { - if (clientPort === -1 || !clientHost) { - return 'local'; - } - return `${clientHost}:${clientPort}`; + return `${hours}:${minutes}:${seconds}`; }, - getWindowPathname: function() { + getWindowPathname() { return window.location.pathname; }, - getNamespace: function() { - return (ENV.environment === 'test' ? '' : this.getWindowPathname()) + ENV.apiURL; + getNamespace() { + let version = '1.0.0', + instanceName = 'HAWQ', + apiPrefix = '/api/v1/views/HAWQ/versions/', + instancePrefix = '/instances/'; + + let params = this.getWindowPathname().split('/').filter(function (param) { + return !!param; + }); + + if (params[params.length - 3] === 'HAWQ') { + version = params[params.length - 2]; + instanceName = params[params.length - 1]; + } + + let hawqViewInstanceURL = `${apiPrefix}${version}${instancePrefix}${instanceName}`; + return ENV.environment === 'test' ? ENV.apiURL : hawqViewInstanceURL; }, - generateStatusString: function(waiting, waitingResource) { - if(waitingResource) { + generateStatusString(waiting, waitingResource) { + if (waitingResource) { return 'Queued'; - } else if(waiting) { + } else if (waiting) { return 'Waiting on Lock'; } return 'Running'; } -}; +}; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/bower.json ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/bower.json b/contrib/views/hawq/src/main/resources/ui/bower.json index 5bab18f..b4f1ab0 100644 --- a/contrib/views/hawq/src/main/resources/ui/bower.json +++ b/contrib/views/hawq/src/main/resources/ui/bower.json @@ -7,8 +7,7 @@ "ember-qunit-notifications": "0.1.0", "bootstrap": "~3.3.6", "bootstrap-sass": "~3.3.6", - "bootstrap-sortable": "2.0.0", - "pretender": "^0.10.0", - "moment": "2.12.0" + "bootstrap-sortable": "2.0.1", + "pretender": "^0.10.0" } } http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/config/environment.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/config/environment.js b/contrib/views/hawq/src/main/resources/ui/config/environment.js index 4c742ef..0d34974 100644 --- a/contrib/views/hawq/src/main/resources/ui/config/environment.js +++ b/contrib/views/hawq/src/main/resources/ui/config/environment.js @@ -18,14 +18,14 @@ /* jshint node: true */ -module.exports = function(environment) { +module.exports = function (environment) { var ENV = { modulePrefix: 'hawq-view', environment: environment, baseURL: '/', apiURL: 'api/v1', locationType: 'hash', - pollingInterval: 7500, + pollingInterval: 5000, shouldPoll: true, EmberENV: { FEATURES: { http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/package.json ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/package.json b/contrib/views/hawq/src/main/resources/ui/package.json index f1b4116..e13cb0d 100644 --- a/contrib/views/hawq/src/main/resources/ui/package.json +++ b/contrib/views/hawq/src/main/resources/ui/package.json @@ -15,7 +15,7 @@ }, "repository": "", "engines": { - "node": ">= 0.10.0" + "node": ">= 4.5.0" }, "author": "", "license": "MIT", @@ -43,7 +43,7 @@ "ember-sinon": "0.5.0", "ember-sinon-qunit": "1.3.0", "loader.js": "^4.0.0", - "node-sass": "3.4.1", + "node-sass": "^3.11.1", "phantomjs": "^1.9.2" } } http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/tests/acceptance/application-test.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/tests/acceptance/application-test.js b/contrib/views/hawq/src/main/resources/ui/tests/acceptance/application-test.js index c7ce7c7..22ee126 100644 --- a/contrib/views/hawq/src/main/resources/ui/tests/acceptance/application-test.js +++ b/contrib/views/hawq/src/main/resources/ui/tests/acceptance/application-test.js @@ -16,9 +16,9 @@ * limitations under the License. */ -import { test } from 'qunit'; +import {test} from 'qunit'; import moduleForAcceptance from 'hawq-view/tests/helpers/module-for-acceptance'; -import { getMockPayload } from 'hawq-view/tests/helpers/test-helper'; +import {getMockPayload} from 'hawq-view/tests/helpers/test-helper'; import Utils from 'hawq-view/utils/utils'; moduleForAcceptance('Acceptance | application'); @@ -31,25 +31,24 @@ test('visiting /', function (assert) { assert.equal(find('img#hawq-logo').length, 1); // Test Row Data - var data = getMockPayload().data; + var data = getMockPayload().items; for (var i = 0, ii = data.length; i < ii; i++) { let rowName = `query-table-row${i}`; assert.equal(this.$(`tr#${rowName}`).length, 1); let attr = data[i].attributes; - assert.equal(this.$(`td#${rowName}-pid`).text(), attr['pid']); - assert.equal(this.$(`td#${rowName}-databasename`).text(), attr['database-name']); - assert.equal(this.$(`td#${rowName}-duration`).text(), attr['duration']); + assert.equal(this.$(`td#${rowName}-pid`).text(), attr['procpid']); + assert.equal(this.$(`td#${rowName}-databasename`).text(), attr['datname']); + assert.equal(this.$(`td#${rowName}-duration`).text(), Utils.formatDuration(attr['query_duration'])); let statusDOM = this.$(`td#${rowName}-status`); - assert.equal(statusDOM.text(), Utils.generateStatusString(attr['waiting'], attr['waiting-resource'])); + assert.equal(statusDOM.text(), Utils.generateStatusString(attr['waiting'], attr['waiting_resource'])); - let mockStatusClass = attr['waiting-resource'] ? '' : (attr['waiting'] ? 'orange' : 'green'); + let mockStatusClass = attr['waiting_resource'] ? '' : (attr['waiting'] ? 'orange' : 'green'); assert.ok(statusDOM.attr('class').match(mockStatusClass)); - assert.equal(this.$(`td#${rowName}-username`).text(), attr['user-name']); - assert.equal(this.$(`td#${rowName}-clientaddress`).text(), Utils.computeClientAddress(attr['client-host'], attr['client-port'])); - + assert.equal(this.$(`td#${rowName}-username`).text(), attr['usename']); + assert.equal(this.$(`td#${rowName}-clientaddress`).text(), Utils.computeClientAddress(attr['client_addr'], attr['client_port'])); } }); -}); +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/tests/helpers/module-for-acceptance.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/tests/helpers/module-for-acceptance.js b/contrib/views/hawq/src/main/resources/ui/tests/helpers/module-for-acceptance.js index 3efeee7..1a82b7b 100644 --- a/contrib/views/hawq/src/main/resources/ui/tests/helpers/module-for-acceptance.js +++ b/contrib/views/hawq/src/main/resources/ui/tests/helpers/module-for-acceptance.js @@ -21,7 +21,7 @@ import startApp from 'hawq-view/tests/helpers/start-app'; import destroyApp from 'hawq-view/tests/helpers/destroy-app'; import Pretender from 'pretender'; import { getMockPayload } from 'hawq-view/tests/helpers/test-helper'; -import ENV from 'hawq-view/config/environment'; +import Utils from 'hawq-view/utils/utils'; let server; @@ -30,7 +30,7 @@ export default function(name, options = {}) { beforeEach() { this.application = startApp(); server = new Pretender(function() { - this.get(ENV.apiURL + '/queries', function () { + this.get(Utils.getNamespace() + '/queries', function () { return [200, {'Content-Type': 'application/json'}, JSON.stringify(getMockPayload())]; }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/tests/helpers/test-helper.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/tests/helpers/test-helper.js b/contrib/views/hawq/src/main/resources/ui/tests/helpers/test-helper.js index 8c70dbc..1a08019 100644 --- a/contrib/views/hawq/src/main/resources/ui/tests/helpers/test-helper.js +++ b/contrib/views/hawq/src/main/resources/ui/tests/helpers/test-helper.js @@ -20,40 +20,61 @@ import Ember from 'ember'; export function getMockPayload() { return { - data: [ + "href": "http://c6401.ambari.apache.org:8080/api/v1/views/HAWQ/versions/1.0.0/instances/HAWQ/queries?fields=*", + "items": [ { - id: 1, - type: 'query', - attributes: { - 'database-name': 'template1', - 'pid': 90201, - 'user-name': 'gpadmin', - 'waiting': false, - 'waiting-resource': false, - 'duration': '00:00:12', - 'query-start-time': '2016-02-16T16:41:13-08:00', - 'formatted-query-start-time': '2016-02-16T16:41:13-08:00', - 'client-host': '127.0.0.1', - 'client-port': 9999, - 'application-name': 'psql' + "href": "http://c6401.ambari.apache.org:8080/api/v1/views/HAWQ/versions/1.0.0/instances/HAWQ/queries/116662", + "id": "116662", + "instance_name": "HAWQ", + "type": "query", + "version": "1.0.0", + "view_name": "HAWQ", + "attributes": { + "application_name": "", + "backend_start": "2016-10-25 19:24:19", + "client_addr": "192.168.64.101", + "client_port": 34941, + "current_query": "SELECT * FROM pg_stat_activity", + "datid": 1, + "datname": "template1", + "procpid": 116662, + "query_duration": 0.0, + "query_start": "2016-10-25 19:31:04", + "sess_id": 420, + "usename": "gpadmin", + "usesysid": 10, + "waiting": false, + "waiting_resource": false, + "xact_start": "2016-10-25 19:31:04" } - }, { - id: 2, - type: 'query', - attributes: { - 'database-name': 'gpadmin', - 'pid': 13345, - 'user-name': 'foo', - 'waiting': true, - 'waiting-resource': true, - 'duration': '00:20:12', - 'query-start-time': '1963-10-21T00:00:00-08:00', - 'formatted-query-start-time': '2016-02-16T16:41:13-08:00', - 'client-port': -1, - 'application-name': 'mock' + }, + { + "href": "http://c6401.ambari.apache.org:8080/api/v1/views/HAWQ/versions/1.0.0/instances/HAWQ/queries/12345", + "id": "12345", + "instance_name": "HAWQ", + "type": "query", + "version": "1.0.0", + "view_name": "HAWQ", + "attributes": { + "application_name": "", + "backend_start": "2016-10-25 19:24:19", + "client_addr": "192.168.64.104", + "client_port": 42811, + "current_query": "SELECT * FROM customers", + "datid": 1, + "datname": "gpadmin", + "procpid": 12345, + "query_duration": 0.0, + "query_start": "2016-10-25 19:31:24", + "sess_id": 420, + "usename": "gpadmin", + "usesysid": 10, + "waiting": true, + "waiting_resource": true, + "xact_start": "2016-10-25 19:31:24" } - }], - 'server-time': '1963-10-21T00:43:15-08:00' + } + ] }; } @@ -71,9 +92,9 @@ export function makeQueryObjects() { 'waitingResource': false, 'statusClass': 'green', 'duration': '02:30:57', - 'queryStartTime': '2016-02-16T16:41:13-08:00', - 'formattedQueryStartTime': '2016-02-16T16:41:13-08:00', - 'clientAddress': 'local' + 'queryStartTime': '2016-02-16 16:41:13', + 'clientAddress': 'local', + 'formattedDuration': '00:00:00' }, { 'id': 2, 'databaseName': 'DB2', @@ -86,9 +107,9 @@ export function makeQueryObjects() { 'waitingResource': false, 'statusClass': 'orange', 'duration': '01:20:12', - 'queryStartTime': '2016-02-16T16:41:13-08:00', - 'formattedQueryStartTime': '2016-02-16T16:41:13-08:00', - 'clientAddress': 'local' + 'queryStartTime': '2016-02-16 16:41:13', + 'clientAddress': 'local', + 'formattedDuration': '01:00:00' }, { 'id': 3, 'databaseName': 'FoxPro', @@ -101,9 +122,9 @@ export function makeQueryObjects() { 'waitingResource': true, 'statusClass': '', 'duration': '00:20:12', - 'queryStartTime': '2016-02-16T16:41:13-08:00', - 'formattedQueryStartTime': '2016-02-16T16:41:13-08:00', - 'clientAddress': 'local' + 'queryStartTime': '2016-02-16 16:41:13', + 'clientAddress': 'local', + 'formattedDuration': '00:00:01' }]; var queries = []; @@ -115,4 +136,4 @@ export function makeQueryObjects() { return queries; } -export { getMockPayload, makeQueryObjects }; \ No newline at end of file +export {getMockPayload, makeQueryObjects}; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/tests/integration/components/query-table-test.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/tests/integration/components/query-table-test.js b/contrib/views/hawq/src/main/resources/ui/tests/integration/components/query-table-test.js index e02f8fd..9124d80 100644 --- a/contrib/views/hawq/src/main/resources/ui/tests/integration/components/query-table-test.js +++ b/contrib/views/hawq/src/main/resources/ui/tests/integration/components/query-table-test.js @@ -16,10 +16,9 @@ * limitations under the License. */ -import Ember from 'ember'; -import { moduleForComponent, test } from 'ember-qunit'; +import {moduleForComponent, test} from 'ember-qunit'; import hbs from 'htmlbars-inline-precompile'; -import { makeQueryObjects } from 'hawq-view/tests/helpers/test-helper'; +import {makeQueryObjects} from 'hawq-view/tests/helpers/test-helper'; /*jshint node:true*/ @@ -54,8 +53,8 @@ test('it renders a table with mock data', function (assert) { assert.ok(statusDOM.attr('class').match(model[0].get('statusClass'))); assert.equal(this.$('td#query-table-row0-username').text(), model[0].get('userName')); assert.equal(this.$('td#query-table-row0-databasename').text(), model[0].get('databaseName')); - assert.equal(this.$('td#query-table-row0-submittime').text(), model[0].get('formattedQueryStartTime')); - assert.equal(this.$('td#query-table-row0-duration').text(), model[0].get('duration')); + assert.equal(this.$('td#query-table-row0-submittime').text(), model[0].get('queryStartTime')); + assert.equal(this.$('td#query-table-row0-duration').text(), model[0].get('formattedDuration')); assert.equal(this.$('td#query-table-row0-clientaddress').text(), model[0].get('clientAddress')); assert.equal(this.$('tr#query-table-row1').length, 1); @@ -67,46 +66,50 @@ test('it renders a table with mock data', function (assert) { assert.equal(this.$('tr#query-table-row3').length, 0); }); -function testColumnSort(_this, assert, columnHeaderSelector, expectedRows) { - let model = makeQueryObjects(); - _this.set('model', model); +test('Display text if there are no queries', function (assert) { + this.render(hbs`{{query-table}}`); - _this.render(hbs`{{query-table queries=model}}`); - Ember.$.bootstrapSortable(false); + assert.equal(this.$('tr#no-queries').length, 1); +}); - // Ascending order - _this.$(columnHeaderSelector).click(); - let ascendingRows = this.$('tbody').find('tr'); +/* + TESTS DISABLED UNTIL SORTING FEATURE IS IMPLEMENTED - assert.equal(expectedRows.length, ascendingRows.length); + function testColumnSort(_this, assert, columnHeaderSelector, expectedRows) { + let model = makeQueryObjects(); + _this.set('model', model); - for (let i = 0, ii = expectedRows.length; i < ii; ++i) { - assert.equal(ascendingRows[i].getAttribute('id'), expectedRows[i]); - } + _this.render(hbs`{{query-table queries=model}}`); + Ember.$.bootstrapSortable(false); - // Descending order - _this.$(columnHeaderSelector).click(); - let descendingRows = this.$('tbody').find('tr'); + // Ascending order + _this.$(columnHeaderSelector).click(); + let ascendingRows = this.$('tbody').find('tr'); - assert.equal(expectedRows.length, ascendingRows.length); + assert.equal(expectedRows.length, ascendingRows.length); - for (let i = 0, ii = expectedRows.length; i < ii; ++i) { - assert.equal(descendingRows[i].getAttribute('id'), expectedRows[ii - i - 1]); - } -} + for (let i = 0, ii = expectedRows.length; i < ii; ++i) { + assert.equal(ascendingRows[i].getAttribute('id'), expectedRows[i]); + } -test('Clicking on the "Duration" column header toggles the sorting order of the elements of the column', function (assert) { - let expectedRows = [ - 'query-table-row2', - 'query-table-row1', - 'query-table-row0' - ]; + // Descending order + _this.$(columnHeaderSelector).click(); + let descendingRows = this.$('tbody').find('tr'); - testColumnSort(this, assert, 'th#query-table-header-duration', expectedRows); -}); + assert.equal(expectedRows.length, ascendingRows.length); -test('Display text if there are no queries', function (assert) { - this.render(hbs`{{query-table}}`); + for (let i = 0, ii = expectedRows.length; i < ii; ++i) { + assert.equal(descendingRows[i].getAttribute('id'), expectedRows[ii - i - 1]); + } + } - assert.equal(this.$('tr#no-queries').length, 1); -}); + test('Clicking on the "Duration" column header toggles the sorting order of the elements of the column', function (assert) { + let expectedRows = [ + 'query-table-row2', + 'query-table-row1', + 'query-table-row0' + ]; + + testColumnSort(this, assert, 'th#query-table-header-duration', expectedRows); + }); + */ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/8ac9fa10/contrib/views/hawq/src/main/resources/ui/tests/test-helper.js ---------------------------------------------------------------------- diff --git a/contrib/views/hawq/src/main/resources/ui/tests/test-helper.js b/contrib/views/hawq/src/main/resources/ui/tests/test-helper.js index 96975ee..d8588c5 100644 --- a/contrib/views/hawq/src/main/resources/ui/tests/test-helper.js +++ b/contrib/views/hawq/src/main/resources/ui/tests/test-helper.js @@ -17,8 +17,6 @@ */ import resolver from './helpers/resolver'; -import { - setResolver -} from 'ember-qunit'; +import {setResolver} from 'ember-qunit'; -setResolver(resolver); +setResolver(resolver); \ No newline at end of file