You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tez.apache.org by je...@apache.org on 2014/12/10 04:33:32 UTC
[09/53] tez git commit: TEZ-1615. Skeleton framework for Tez UI
(Prakash Ramachandran via jeagles)
TEZ-1615. Skeleton framework for Tez UI (Prakash Ramachandran via jeagles)
Project: http://git-wip-us.apache.org/repos/asf/tez/repo
Commit: http://git-wip-us.apache.org/repos/asf/tez/commit/2f2739dd
Tree: http://git-wip-us.apache.org/repos/asf/tez/tree/2f2739dd
Diff: http://git-wip-us.apache.org/repos/asf/tez/diff/2f2739dd
Branch: refs/heads/master
Commit: 2f2739ddb4819c57871ca52b963b5a03fe2872ee
Parents: fb365f4
Author: Jonathan Eagles <je...@gmail.com>
Authored: Fri Oct 3 21:10:56 2014 -0500
Committer: Jonathan Eagles <je...@gmail.com>
Committed: Fri Oct 3 21:10:56 2014 -0500
----------------------------------------------------------------------
CHANGES.txt | 1 +
tez-ui/.bowerrc | 3 +
tez-ui/.gitattributes | 1 +
tez-ui/.gitignore | 9 +
tez-ui/.jshintrc | 24 +
tez-ui/Gruntfile.js | 450 +++++++++++++++++++
tez-ui/README.TXT | 17 +
tez-ui/app/img/apache_tez_logo_lowres.png | Bin 0 -> 70978 bytes
tez-ui/app/img/glyphicons-halflings.png | Bin 0 -> 13826 bytes
tez-ui/app/index.html | 69 +++
tez-ui/app/scripts/app.js | 53 +++
tez-ui/app/scripts/components/page-nav.js | 31 ++
.../app/scripts/controllers/dag_controller.js | 40 ++
.../scripts/controllers/dag_index_controller.js | 63 +++
.../app/scripts/controllers/dags_controller.js | 190 ++++++++
tez-ui/app/scripts/helpers/ajax.js | 175 ++++++++
tez-ui/app/scripts/helpers/date.js | 216 +++++++++
tez-ui/app/scripts/helpers/handlebarHelpers.js | 48 ++
tez-ui/app/scripts/helpers/number.js | 101 +++++
tez-ui/app/scripts/helpers/translation.js | 119 +++++
tez-ui/app/scripts/mappers/dag_mapper.js | 56 +++
tez-ui/app/scripts/mixins/display_helpers.js | 39 ++
tez-ui/app/scripts/mixins/run_periodically.js | 78 ++++
.../app/scripts/models/TimelineRestAdapter.js | 124 +++++
tez-ui/app/scripts/models/abstract_entity.js | 27 ++
tez-ui/app/scripts/models/dag.js | 231 ++++++++++
tez-ui/app/scripts/router.js | 88 ++++
tez-ui/app/scripts/store.js | 21 +
tez-ui/app/styles/main.css | 70 +++
tez-ui/app/templates/application.hbs | 21 +
tez-ui/app/templates/components/page-nav.hbs | 22 +
tez-ui/app/templates/dag.hbs | 36 ++
tez-ui/app/templates/dag/index.hbs | 41 ++
tez-ui/app/templates/dag/swimlane.hbs | 1 +
tez-ui/app/templates/dag/vertex.hbs | 1 +
tez-ui/app/templates/dags.hbs | 58 +++
tez-ui/app/templates/error_.hbs | 6 +
tez-ui/app/templates/utils/_loadingSpinner.hbs | 3 +
tez-ui/app/templates/utils/_pageHeader.hbs | 7 +
tez-ui/bower.json | 32 ++
tez-ui/package.json | 36 ++
41 files changed, 2608 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index ced524a..a0c0ae8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -7,6 +7,7 @@ INCOMPATIBLE CHANGES
ALL CHANGES:
TEZ-1634. Fix compressed IFile shuffle errors
+ TEZ-1615. Skeleton framework for Tez UI
Release 0.5.2: Unreleased
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/.bowerrc
----------------------------------------------------------------------
diff --git a/tez-ui/.bowerrc b/tez-ui/.bowerrc
new file mode 100644
index 0000000..ba0accc
--- /dev/null
+++ b/tez-ui/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "app/bower_components"
+}
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/.gitattributes
----------------------------------------------------------------------
diff --git a/tez-ui/.gitattributes b/tez-ui/.gitattributes
new file mode 100644
index 0000000..2125666
--- /dev/null
+++ b/tez-ui/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/.gitignore
----------------------------------------------------------------------
diff --git a/tez-ui/.gitignore b/tez-ui/.gitignore
new file mode 100644
index 0000000..ee72141
--- /dev/null
+++ b/tez-ui/.gitignore
@@ -0,0 +1,9 @@
+node
+node_modules
+temp
+dist
+.sass-cache
+.tmp
+app/bower_components
+test/bower_components
+.editorconfig
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/.jshintrc
----------------------------------------------------------------------
diff --git a/tez-ui/.jshintrc b/tez-ui/.jshintrc
new file mode 100644
index 0000000..780790d
--- /dev/null
+++ b/tez-ui/.jshintrc
@@ -0,0 +1,24 @@
+{
+ "node": true,
+ "browser": true,
+ "esnext": true,
+ "bitwise": false,
+ "curly": false,
+ "eqeqeq": true,
+ "eqnull": true,
+ "immed": true,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "undef": true,
+ "strict": false,
+ "trailing": false,
+ "smarttabs": true,
+ "globals": {
+ "App": true,
+ "jQuery": true,
+ "Ember": true,
+ "Handlebars": true,
+ "DS": true
+ }
+}
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/Gruntfile.js
----------------------------------------------------------------------
diff --git a/tez-ui/Gruntfile.js b/tez-ui/Gruntfile.js
new file mode 100644
index 0000000..4e7fbde
--- /dev/null
+++ b/tez-ui/Gruntfile.js
@@ -0,0 +1,450 @@
+/**
+ * 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.
+ */
+// Generated on 2014-06-24 using generator-ember 0.8.4
+'use strict';
+var LIVERELOAD_PORT = 35729;
+var lrSnippet = require('connect-livereload')({port: LIVERELOAD_PORT});
+var mountFolder = function (connect, dir) {
+ return connect.static(require('path').resolve(dir));
+};
+
+// # Globbing
+// for performance reasons we're only matching one level down:
+// 'test/spec/{,*/}*.js'
+// use this if you want to match all subfolders:
+// 'test/spec/**/*.js'
+
+module.exports = function (grunt) {
+ // show elapsed time at the end
+ require('time-grunt')(grunt);
+ // load all grunt tasks
+ require('load-grunt-tasks')(grunt);
+
+ // configurable paths
+ var yeomanConfig = {
+ app: 'app',
+ dist: 'dist'
+ };
+
+ grunt.initConfig({
+ yeoman: yeomanConfig,
+ watch: {
+ emberTemplates: {
+ files: '<%= yeoman.app %>/templates/**/*.hbs',
+ tasks: ['emberTemplates']
+ },
+ neuter: {
+ files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
+ tasks: ['neuter']
+ },
+ less: {
+ files: '<%= yeoman.app %>/styles/**/*.less',
+ tasks: ['less:development']
+ },
+ livereload: {
+ options: {
+ livereload: LIVERELOAD_PORT
+ },
+ files: [
+ '.tmp/scripts/**/*.js',
+ '<%= yeoman.app %>/*.html',
+ '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
+ '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
+ ]
+ },
+ css: {
+ files: '<%= yeoman.app %>/styles/*.css',
+ tasks: ['copy:development']
+ }
+ },
+ connect: {
+ options: {
+ port: 9000,
+ // change this to '0.0.0.0' to access the server from outside
+ hostname: 'localhost'
+ },
+ livereload: {
+ options: {
+ middleware: function (connect) {
+ return [
+ lrSnippet,
+ mountFolder(connect, '.tmp'),
+ mountFolder(connect, yeomanConfig.app)
+ ];
+ }
+ }
+ },
+ test: {
+ options: {
+ middleware: function (connect) {
+ return [
+ mountFolder(connect, 'test'),
+ mountFolder(connect, '.tmp')
+ ];
+ }
+ }
+ },
+ dist: {
+ options: {
+ middleware: function (connect) {
+ return [
+ mountFolder(connect, yeomanConfig.dist)
+ ];
+ }
+ }
+ }
+ },
+ open: {
+ server: {
+ path: 'http://localhost:<%= connect.options.port %>'
+ }
+ },
+ clean: {
+ dist: {
+ files: [
+ {
+ dot: true,
+ src: [
+ '.tmp',
+ '<%= yeoman.dist %>/*',
+ '!<%= yeoman.dist %>/.git*'
+ ]
+ }
+ ]
+ },
+ server: '.tmp'
+ },
+ jshint: {
+ options: {
+ jshintrc: '.jshintrc',
+ reporter: require('jshint-stylish')
+ },
+ all: [
+ 'Gruntfile.js',
+ '<%= yeoman.app %>/scripts/{,*/}*.js',
+ '!<%= yeoman.app %>/scripts/vendor/*',
+ 'test/spec/{,*/}*.js'
+ ]
+ },
+ mocha: {
+ all: {
+ options: {
+ run: true,
+ urls: ['http://localhost:<%= connect.options.port %>/index.html']
+ }
+ }
+ },
+ // not used since Uglify task does concat,
+ // but still available if needed
+ /*concat: {
+ dist: {}
+ },*/
+ // not enabled since usemin task does concat and uglify
+ // check index.html to edit your build targets
+ // enable this task if you prefer defining your build targets here
+ /*uglify: {
+ dist: {}
+ },*/
+ rev: {
+ dist: {
+ files: {
+ src: [
+ '<%= yeoman.dist %>/scripts/{,*/}*.js',
+ '<%= yeoman.dist %>/styles/{,*/}*.css',
+ '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}',
+ '<%= yeoman.dist %>/styles/fonts/*'
+ ]
+ }
+ }
+ },
+ useminPrepare: {
+ html: '.tmp/index.html',
+ options: {
+ dest: '<%= yeoman.dist %>'
+ }
+ },
+ usemin: {
+ html: ['<%= yeoman.dist %>/{,*/}*.html'],
+ css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
+ options: {
+ dirs: ['<%= yeoman.dist %>']
+ }
+ },
+ svgmin: {
+ dist: {
+ files: [
+ {
+ expand: true,
+ cwd: '<%= yeoman.app %>/images',
+ src: '{,*/}*.svg',
+ dest: '<%= yeoman.dist %>/images'
+ }
+ ]
+ }
+ },
+ cssmin: {
+ dist: {
+ files: {
+ '<%= yeoman.dist %>/styles/main.css': [
+ '.tmp/styles/{,*/}*.css',
+ '<%= yeoman.app %>/styles/{,*/}*.css',
+ '<%= yeoman.app %>/bower_components/bootstrap/dist/css/bootstrap.css',
+ '<%= yeoman.app %>/bower_components/ember-table/dist/ember-table.css'
+ ]
+ }
+ }
+ },
+ htmlmin: {
+ dist: {
+ options: {
+ /*removeCommentsFromCDATA: true,
+ // https://github.com/yeoman/grunt-usemin/issues/44
+ //collapseWhitespace: true,
+ collapseBooleanAttributes: true,
+ removeAttributeQuotes: true,
+ removeRedundantAttributes: true,
+ useShortDoctype: true,
+ removeEmptyAttributes: true,
+ removeOptionalTags: true*/
+ },
+ files: [
+ {
+ expand: true,
+ cwd: '<%= yeoman.app %>',
+ src: '*.html',
+ dest: '<%= yeoman.dist %>'
+ }
+ ]
+ }
+ },
+ replace: {
+ app: {
+ options: {
+ variables: {
+ ember: 'bower_components/ember/ember.js',
+ ember_data: 'bower_components/ember-data/ember-data.js'
+ }
+ },
+ files: [
+ {src: '<%= yeoman.app %>/index.html', dest: '.tmp/index.html'}
+ ]
+ },
+ dist: {
+ options: {
+ variables: {
+ ember: 'bower_components/ember/ember.prod.js',
+ ember_data: 'bower_components/ember-data/ember-data.prod.js'
+ }
+ },
+ files: [
+ {src: '<%= yeoman.app %>/index.html', dest: '.tmp/index.html'}
+ ]
+ }
+ },
+ // Put files not handled in other tasks here
+ copy: {
+ dist: {
+ files: [
+ {
+ expand: true,
+ dot: true,
+ cwd: '<%= yeoman.app %>',
+ dest: '<%= yeoman.dist %>',
+ src: [
+ '*.{ico,txt}',
+ '.htaccess',
+ 'img/*',
+ 'styles/fonts/*',
+ 'scripts/assets/**/*'
+ ]
+ },
+ {
+ expand: true,
+ flatten: true,
+ src: '<%= yeoman.app %>/bower_components/jquery-ui/themes/base/images/*',
+ dest: '<%= yeoman.dist %>/styles/images/'
+ },
+ {
+ expand: true,
+ flatten: true,
+ src: '<%= yeoman.app %>/bower_components/font-awesome/font/*',
+ dest: '<%= yeoman.dist %>/font/'
+ }
+ ]
+ },
+ development: {
+ files: [
+ {
+ expand: true,
+ dot: true,
+ cwd: '<%= yeoman.app %>',
+ dest: '.tmp',
+ src: [
+ '*.{ico,txt}',
+ '.htaccess',
+ 'img/*',
+ 'styles/*.css',
+ 'styles/fonts/*',
+ 'scripts/assets/**/*'
+ ]
+ },
+ {
+ expand: true,
+ flatten: true,
+ src: '<%= yeoman.app %>/bower_components/jquery-ui/themes/base/images/*',
+ dest: '.tmp/styles/images/'
+ },
+ {
+ expand: true,
+ flatten: true,
+ src: '<%= yeoman.app %>/bower_components/font-awesome/font/*',
+ dest: '.tmp/font/'
+ },
+ {
+ expand: true,
+ flatten: false,
+ cwd: '<%= yeoman.app %>',
+ src: 'bower_components/**',
+ dest: '.tmp/'
+ }
+ ]
+ }
+ },
+ concurrent: {
+ server: [
+ 'emberTemplates'
+ ],
+ test: [
+ 'emberTemplates'
+ ],
+ dist: [
+ 'emberTemplates',
+ 'svgmin',
+ 'htmlmin'
+ ]
+ },
+ emberTemplates: {
+ options: {
+ templateName: function (sourceFile) {
+ var templatePath = yeomanConfig.app + '/templates/';
+ return sourceFile.replace(templatePath, '');
+ }
+ },
+ dist: {
+ files: {
+ '.tmp/scripts/compiled-templates.js': '<%= yeoman.app %>/templates/**/*.hbs'
+ }
+ },
+ development: {
+ files: {
+ '.tmp/scripts/compiled-templates.js': '<%= yeoman.app %>/templates/**/*.hbs'
+ }
+ }
+ },
+
+ less: {
+ development: {
+ options: {
+ paths: ["<%= yeoman.app %>/styles"],
+ sourceMap: true,
+ },
+ files: [{
+ expand: true,
+ cwd: "<%= yeoman.app %>/styles",
+ src: ['styles/*.less', '**/*.less'],
+ dest: ".tmp/styles",
+ ext: ".css"
+ }]
+ },
+ production: {
+ options: {
+ paths: ["<%= yeoman.app %>/styles"],
+ cleancss: true
+ },
+ files: {
+ ".tmp/styles/styles.css": "<%= yeoman.app %>/styles/**/*.less"
+ }
+ }
+ },
+
+ neuter: {
+ app: {
+ options: {
+ filepathTransform: function (filepath) {
+ return yeomanConfig.app + '/' + filepath;
+ }
+ },
+ src: '<%= yeoman.app %>/scripts/**/*.js',
+ dest: '.tmp/scripts/combined-scripts.js'
+ }
+ }
+ });
+
+ grunt.registerTask('server', function (target) {
+ grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
+ grunt.task.run(['serve:' + target]);
+ });
+
+ grunt.registerTask('serve', function (target) {
+ if (target === 'dist') {
+ return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
+ }
+
+ grunt.task.run([
+ 'clean:server',
+ 'replace:app',
+ 'concurrent:server',
+ 'neuter:app',
+ 'less:development',
+ 'connect:livereload',
+ 'open',
+ 'copy:development',
+ 'watch'
+ ]);
+ });
+
+ grunt.registerTask('test', [
+ 'clean:server',
+ 'replace:app',
+ 'concurrent:test',
+ 'connect:test',
+ 'neuter:app',
+ 'mocha'
+ ]);
+
+ grunt.registerTask('build', [
+ 'clean:dist',
+ 'replace:app',
+ 'useminPrepare',
+ 'concurrent:dist',
+ 'neuter:app',
+ 'less:production',
+ 'concat',
+ 'cssmin',
+ //'uglify',
+ 'copy:dist',
+ //'rev',
+ 'usemin'
+ ]);
+
+ grunt.registerTask('default', [
+ 'jshint',
+ 'test',
+ 'build'
+ ]);
+};
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/README.TXT
----------------------------------------------------------------------
diff --git a/tez-ui/README.TXT b/tez-ui/README.TXT
new file mode 100644
index 0000000..4e00df0
--- /dev/null
+++ b/tez-ui/README.TXT
@@ -0,0 +1,17 @@
+Dev setup instructions
+----------------------
+install the development dependencies nodejs, npm, grunt and bower
+from the top level directory run the following commands
+ * npm install
+ * bower install
+
+The timeline server (ATS) url by default points to http://localhost:8188. This can be
+changed to point to another host by editing the App.AtsBaseUrl entry in the
+app/scripts/app.js file.
+
+For development run 'grunt serve'. This runs a dev server on port 9000.
+navigate to http://localhost:9000 if a browser does not open automatically.
+Any changes made will be live-reloaded on the browser.
+
+For creating a distribution version, run 'grunt build'. The distributable
+version will be placed under the dist directory.
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/img/apache_tez_logo_lowres.png
----------------------------------------------------------------------
diff --git a/tez-ui/app/img/apache_tez_logo_lowres.png b/tez-ui/app/img/apache_tez_logo_lowres.png
new file mode 100644
index 0000000..45fd701
Binary files /dev/null and b/tez-ui/app/img/apache_tez_logo_lowres.png differ
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/img/glyphicons-halflings.png
----------------------------------------------------------------------
diff --git a/tez-ui/app/img/glyphicons-halflings.png b/tez-ui/app/img/glyphicons-halflings.png
new file mode 100644
index 0000000..79bc568
Binary files /dev/null and b/tez-ui/app/img/glyphicons-halflings.png differ
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/index.html
----------------------------------------------------------------------
diff --git a/tez-ui/app/index.html b/tez-ui/app/index.html
new file mode 100644
index 0000000..75ddf6d
--- /dev/null
+++ b/tez-ui/app/index.html
@@ -0,0 +1,69 @@
+<!--
+* 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.
+-->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Tez UI</title>
+
+ <!-- build:css styles/main.css -->
+ <link rel="stylesheet" href="styles/main.css">
+ <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
+ <link rel="stylesheet" href="bower_components/ember-table/dist/ember-table.css">
+ <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css">
+ <!-- endbuild -->
+
+ </head>
+
+ <body>
+ <!-- build:js(app) scripts/components.js -->
+ <script src="bower_components/cldr/plurals.js"></script>
+ <script src="bower_components/jquery/jquery.js"></script>
+ <script src="bower_components/jquery-ui/jquery-ui.js"></script>
+ <script src="bower_components/bootstrap/js/dropdown.js"></script>
+ <script src="bower_components/bootstrap/js/button.js"></script>
+ <script src="bower_components/bootstrap/js/tooltip.js"></script>
+ <script src="bower_components/jquery-ui/ui/datepicker.js"></script>
+ <script src="bower_components/moment/moment.js"></script>
+ <script src="bower_components/handlebars/handlebars.js"></script>
+ <script src="@@ember"></script>
+ <script src="@@ember_data"></script>
+ <script src="bower_components/ember-json-mapper/ember-json-mapper.js"></script>
+ <script src="bower_components/ember-i18n/lib/i18n.js"></script>
+ <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-core.min.js"></script>
+ <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-button.min.js"></script>
+ <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-modal.min.js"></script>
+ <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-nav.min.js"></script>
+ <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-items-action-bar.min.js"></script>
+ <script src="bower_components/antiscroll/antiscroll.js"></script>
+ <script src="bower_components/jquery-mousewheel/jquery.mousewheel.js"></script>
+ <script src="bower_components/ember-table/dist/ember-table.js"></script>
+ <script src="bower_components/ember-addons.bs_for_ember/dist/js/bs-nav.min.js"></script>
+ <script src="bower_components/d3/d3.v2.js"></script>
+ <!-- endbuild -->
+
+ <!-- build:js(.tmp) scripts/templates.js -->
+ <script src="scripts/compiled-templates.js"></script>
+ <!-- endbuild -->
+
+ <!-- build:js(.tmp) scripts/main.js -->
+ <script src="scripts/combined-scripts.js"></script>
+ <!-- endbuild -->
+ </body>
+</html>
+
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/app.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/app.js b/tez-ui/app/scripts/app.js
new file mode 100644
index 0000000..a3d9d5c
--- /dev/null
+++ b/tez-ui/app/scripts/app.js
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = window.App = Em.Application.createWithMixins(Bootstrap, {
+ // Basic logging, e.g. "Transitioned into 'post'"
+ LOG_TRANSITIONS: true,
+
+ // Extremely detailed logging, highlighting every internal
+ // step made while transitioning into a route, including
+ // `beforeModel`, `model`, and `afterModel` hooks, and
+ // information about redirects and aborted transitions
+ LOG_TRANSITIONS_INTERNAL: true
+});
+
+App.AtsBaseUrl = "http://localhost:8188";
+
+require('scripts/router');
+require('scripts/store');
+
+App.Helpers = Em.Namespace.create();
+App.Mappers = Em.Namespace.create();
+
+//TODO: initializer.
+
+/* Order and include */
+/* TODO: cleanup */
+require('scripts/translations');
+require('scripts/mixins/*');
+require('scripts/helpers/*');
+require('scripts/models/**/*');
+require('scripts/mappers/server_data_mapper.js');
+require('scripts/mappers/**/*');
+require('scripts/controllers/**/*');
+require('scripts/components/*');
+require('scripts/adapters/*');
+
+App.ApplicationAdapter = App.TimelineRESTAdapter.extend();
+App.ApplicationSerializer = App.TimelineSerializer.extend();
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/components/page-nav.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/components/page-nav.js b/tez-ui/app/scripts/components/page-nav.js
new file mode 100644
index 0000000..b7c21a6
--- /dev/null
+++ b/tez-ui/app/scripts/components/page-nav.js
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.PageNavComponent = Em.Component.extend({
+ actions: {
+ gotoNext: function() {
+ this.sendAction('navNext');
+ },
+ gotoPrev: function() {
+ this.sendAction('navPrev');
+ },
+ gotoFirst: function() {
+ this.sendAction('navFirst');
+ }
+ }
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/controllers/dag_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/controllers/dag_controller.js b/tez-ui/app/scripts/controllers/dag_controller.js
new file mode 100644
index 0000000..6d601a2
--- /dev/null
+++ b/tez-ui/app/scripts/controllers/dag_controller.js
@@ -0,0 +1,40 @@
+/**
+ * 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.
+ */
+
+App.DagController = Em.ObjectController.extend(App.Helpers.DisplayHelper, {
+ controllerName: "DagsController",
+
+ pageTitle: "Dag",
+
+ loading: true,
+
+ updateLoading: function() {
+ this.set('loading', false);
+ }.observes('content'),
+
+ pageSubTitle: function() {
+ return this.get('name');
+ }.property(),
+
+ childDisplayViews: [
+ Ember.Object.create({title: 'Details', linkTo: 'dag.index'}),
+ Ember.Object.create({title: 'Vertex', linkTo: 'dag.vertex'}),
+ Ember.Object.create({title: 'Dag View', linkTo: 'dag.swimlane'})
+ ],
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/controllers/dag_index_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/controllers/dag_index_controller.js b/tez-ui/app/scripts/controllers/dag_index_controller.js
new file mode 100644
index 0000000..4ecae7d
--- /dev/null
+++ b/tez-ui/app/scripts/controllers/dag_index_controller.js
@@ -0,0 +1,63 @@
+/**
+ * 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.
+ */
+
+App.DagIndexController = Em.ObjectController.extend({
+ controllerName: "DagsController",
+
+ counterTableColumns: function() {
+ var groupColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Group',
+ getCellContent: function(row) {
+ return row.get('counterGroup');
+ }
+ });
+
+ var nameColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Counter Name',
+ getCellContent: function(row) {
+ return row.get('name');
+ }
+ });
+
+ var valueColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Counter Name',
+ getCellContent: function(row) {
+ return row.get('value');
+ }
+ });
+
+ return [groupColumn, nameColumn, valueColumn];
+ }.property(),
+
+ counterContent: function() {
+ var allCounters = [];
+ this.get('content').get('counterGroups').forEach(function(cg){
+ cg.get('counters').forEach(function(counter){
+ allCounters.push({
+ counterGroup: cg.get('displayName'),
+ name: counter.get('displayName'),
+ value: counter.get('value')
+ });
+ });
+ });
+ return allCounters;
+ }.property(),
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/controllers/dags_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/controllers/dags_controller.js b/tez-ui/app/scripts/controllers/dags_controller.js
new file mode 100644
index 0000000..70ef7f3
--- /dev/null
+++ b/tez-ui/app/scripts/controllers/dags_controller.js
@@ -0,0 +1,190 @@
+/**
+ * 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.
+ */
+
+App.DagsController = Em.ArrayController.extend({
+ controllerName: "DagsController",
+
+ pageTitle: "Dags",
+
+ pageSubTitle: "All Dags",
+
+ /* filtering and sorting related */
+ queryParams: {
+ count: true,
+ fromID: true
+ },
+
+ count: 10,
+
+ fromID: '',
+
+ fromTS: '',
+
+ fields: 'events,primaryfilters,otherinfo',
+ /* end sort & filter related */
+
+ // content is being loaded.
+ loading: true,
+
+ /* There is currently no efficient way in ATS to get pagination data, so we fake one.
+ * store the first dag id on a page so that we can navigate back and store the last one
+ * (not shown on page to get the id where next page starts)
+ */
+ navIDs: {
+ prevIDs: [],
+ currentID: undefined,
+ nextID: undefined
+ },
+
+ updateLoading: function() {
+ this.set('loading', false);
+ }.observes('content'),
+
+ sortedContent: function() {
+ var sorted = Em.ArrayController.create({
+ model: this.get('content'),
+ sortProperties: ['startTime'],
+ sortAscending: false
+ });
+ this.updatePagination(sorted.toArray());
+ return sorted.slice(0, this.count);
+ }.property('content.isUpdating', 'content.isLoading'),
+
+ updatePagination: function(currentPageDagIDs) {
+ if (!!currentPageDagIDs && currentPageDagIDs.length > 0) {
+ this.set('navIDs.currentID', currentPageDagIDs[0].id);
+ var nextID = undefined;
+ if (currentPageDagIDs.length > this.count) {
+ // save the last id, so that we can use that as firt id on next page.
+ nextID = currentPageDagIDs[this.count].id;
+ }
+ this.set('navIDs.nextID', nextID);
+ }
+ },
+
+ hasPrev: function() {
+ return this.navIDs.prevIDs.length > 0;
+ }.property('navIDs.prevIDs.[]'),
+
+ hasNext: function() {
+ return !!this.navIDs.nextID;
+ }.property('navIDs.nextID'),
+
+ actions:{
+ // go to previous page
+ navigatePrev: function () {
+ var prevPageId = this.navIDs.prevIDs.popObject();
+ this.set('fromID', prevPageId);
+ this.set('loading', true);
+ this.transitionToRoute('dags');
+ },
+
+ // goto first page.
+ navigateFirst: function() {
+ var firstPageId = this.navIDs.prevIDs[0];
+ this.set('navIDs.prevIDs', []);
+ this.set('fromID', firstPageId);
+ this.set('loading', true);
+ this.transitionToRoute('dags');
+ },
+
+ // go to next page
+ navigateNext: function () {
+ this.navIDs.prevIDs.pushObject(this.navIDs.currentID);
+ this.set('fromID', this.get('navIDs.nextID'));
+ this.set('loading', true);
+ this.transitionToRoute('dags');
+ },
+ },
+
+ getFilterParams: function(params) {
+ var filterParams = {
+ limit: (parseInt(params.count) || this.get('count')) + 1,
+ fields: this.get('fields')
+ };
+ var fromID = params.fromID || this.get('fromID'),
+ fromTS = params.fromTS || this.get('fromTS'),
+ user = params.user;
+
+ if (fromID) {
+ filterParams['fromId'] = fromID;
+ }
+
+ if (fromTS) {
+ filterParams['fromTs'] = fromTS;
+ }
+
+ if (user) {
+ filterParams['primaryFilter'] = 'user:' + user;
+ }
+
+ return filterParams;
+ },
+
+ /* table view for dags */
+ columns: function() {
+ var idColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Dag Id',
+ tableCellViewClass: Em.Table.TableCell.extend({
+ template: Em.Handlebars.compile("{{#link-to 'dag' view.cellContent class='ember-table-content'}}{{view.cellContent}}{{/link-to}}")
+ }),
+ getCellContent: function(row) {
+ return row.get('id');
+ }
+ });
+ var nameColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Name',
+ getCellContent: function(row) {
+ return row.get('name');
+ }
+ });
+ var userColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Submitter',
+ getCellContent: function(row) {
+ return row.get('user');
+ }
+ });
+ var statusColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Status',
+ getCellContent: function(row) {
+ return row.get('status');
+ }
+ });
+ var startTimeColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'SubmissionTime',
+ getCellContent: function(row) {
+ return App.Helpers.date.dateFormat(row.get('startTime'));
+ }
+ });
+ var appIdColumn = Em.Table.ColumnDefinition.create({
+ textAlign: 'text-align-left',
+ headerCellName: 'Application Id',
+ getCellContent: function(row) {
+ return row.get('applicationId');
+ }
+ });
+ return [idColumn, nameColumn, userColumn, statusColumn, startTimeColumn, appIdColumn];
+ }.property(),
+
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/ajax.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/ajax.js b/tez-ui/app/scripts/helpers/ajax.js
new file mode 100644
index 0000000..7b56135
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/ajax.js
@@ -0,0 +1,175 @@
+/**
+ * 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.
+ */
+
+/**
+ * Config for each ajax-request
+ *
+ * Fields example:
+ * mock - testMode url
+ * real - real url (without API prefix)
+ * type - request type (also may be defined in the format method)
+ * format - function for processing ajax params after default formatRequest. May be called with one or two parameters (data, opt). Return ajax-params object
+ * testInProduction - can this request be executed on production tests (used only in tests)
+ *
+ * @type {Object}
+ */
+var urls = {
+ load_dags: {
+ real: App.AtsBaseUrl + '/ws/v1/timeline/TEZ_DAG_ID/{queryParams}',
+ mock: '',
+ apiPrefix: ''
+ },
+
+ load_dag_details: {
+ real: App.AtsBaseUrl + '/ws/v1/timeline/TEZ_DAG_ID/{dag_id}?fields=events,otherinfo,primaryfilters',
+ mock: '',
+ apiPrefix: ''
+ },
+
+ load_vertex_details: {
+ real: App.AtsBaseUrl + '/ws/v1/timeline/TEZ_VERTEX_ID/?primaryFilter=TEZ_DAG_ID:{dag_id}'
+ }
+};
+
+/**
+ * Replace data-placeholders to its values
+ *
+ * @param {String} url
+ * @param {Object} data
+ * @return {String}
+ */
+var formatUrl = function (url, data) {
+ if (!url) return null;
+ var keys = url.match(/\{\w+\}/g);
+ keys = (keys === null) ? [] : keys;
+ if (keys) {
+ keys.forEach(function (key) {
+ var raw_key = key.substr(1, key.length - 2);
+ var replace;
+ if (!data || !data[raw_key]) {
+ replace = '';
+ }
+ else {
+ replace = data[raw_key];
+ }
+ url = url.replace(new RegExp(key, 'g'), replace);
+ });
+ }
+ return url;
+};
+
+/**
+ * this = object from config
+ * @return {Object}
+ */
+var formatRequest = function (data) {
+ var opt = {
+ type: this.type || 'GET',
+ dataType: 'json',
+ async: true,
+ headers: this.headers
+ };
+ if (App.get('testMode')) {
+ opt.url = formatUrl(this.mock ? this.mock : '', data);
+ opt.type = 'GET';
+ }
+ else {
+ var prefix = this.apiPrefix != null ? this.apiPrefix : App.get('urlPrefix');
+ opt.url = prefix + formatUrl(this.real, data);
+ }
+
+ if (this.format) {
+ jQuery.extend(opt, this.format(data, opt));
+ }
+ return opt;
+};
+
+/**
+ * Wrapper for all ajax requests
+ *
+ * @type {Object}
+ */
+var ajax = Em.Object.extend({
+ /**
+ * Send ajax request
+ *
+ * @param {Object} config
+ * @return {$.ajax} jquery ajax object
+ *
+ * config fields:
+ * name - url-key in the urls-object *required*
+ * sender - object that send request (need for proper callback initialization) *required*
+ * data - object with data for url-format
+ * beforeSend - method-name for ajax beforeSend response callback
+ * success - method-name for ajax success response callback
+ * error - method-name for ajax error response callback
+ */
+ send: function (config) {
+
+ Ember.assert('Ajax sender should be defined!', config.sender);
+ Ember.assert('Invalid config.name provided - ' + config.name, urls[config.name]);
+
+ var opt = {},
+ params = {clusterName: App.get('clusterName')};
+
+ if (config.data) {
+ jQuery.extend(params, config.data);
+ }
+
+ opt = formatRequest.call(urls[config.name], params);
+ opt.context = this;
+
+ // object sender should be provided for processing beforeSend, success, error and complete responses
+ opt.beforeSend = function (xhr) {
+ if (config.beforeSend) {
+ config.sender[config.beforeSend](opt, xhr, params);
+ }
+ };
+
+ opt.success = function (data) {
+ console.log("TRACE: The url is: " + opt.url);
+ if (config.success) {
+ config.sender[config.success](data, opt, params);
+ }
+ };
+
+ opt.error = function (request, ajaxOptions, error) {
+ if (config.error) {
+ config.sender[config.error](request, ajaxOptions, error, opt, params);
+ }
+ };
+
+ opt.complete = function (xhr, status) {
+ if (config.complete) {
+ config.sender[config.complete](xhr, status);
+ }
+ };
+
+ return $.ajax(opt);
+ },
+
+ formatUrlFor: function(endpoint, data) {
+ Ember.assert('Ajax sender should be defined!', !!endpoint);
+ Ember.assert('Invalid config.name provided - ' + endpoint, urls[endpoint]);
+
+ return formatUrl(urls[endpoint].real, data);
+ }
+
+});
+
+App.ajax = ajax.create({});
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/date.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/date.js b/tez-ui/app/scripts/helpers/date.js
new file mode 100644
index 0000000..9bf49ce
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/date.js
@@ -0,0 +1,216 @@
+/**
+ * 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.
+ */
+
+App.Helpers.date = {
+
+ /**
+ * List of monthes short names
+ * @type {string[]}
+ */
+ dateMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
+
+ /**
+ * List of days short names
+ * @type {string[]}
+ */
+ dateDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+
+ /**
+ * Add leading zero
+ *
+ * @param {string} time
+ * @returns {string}
+ * @method dateFormatZeroFirst
+ */
+ dateFormatZeroFirst: function (time) {
+ if (time < 10) return '0' + time;
+ return "" + time;
+ },
+
+ /**
+ * Convert timestamp to date-string 'DAY_OF_THE_WEEK, MONTH DAY, YEAR HOURS:MINUTES'
+ *
+ * @param {number} timestamp
+ * @param {bool} showSeconds should seconds be added to result string
+ * @param {bool} showMilliseconds should miliseconds be added to result string (if <code>showSeconds</code> is false, milliseconds wouldn't be added)
+ * @return {*} date
+ * @method dateFormat
+ */
+ dateFormat: function (timestamp, showSeconds, showMilliseconds) {
+ if (!App.Helpers.number.isValidInt(timestamp)) {
+ return timestamp;
+ }
+ var format = 'ddd, MMM DD, YYYY HH:mm';
+ if (showSeconds) {
+ format += ':ss';
+ if (showMilliseconds) {
+ format += ':SSS';
+ }
+ }
+ return moment((new Date(timestamp)).toISOString().replace('Z', '')).format(format);
+ },
+
+ /**
+ * Convert timestamp to date-string 'DAY_OF_THE_WEEK MONTH DAY YEAR'
+ *
+ * @param {string} timestamp
+ * @return {string}
+ * @method dateFormatShort
+ */
+ dateFormatShort: function (timestamp) {
+ if (!App.Helpers.number.isValidInt(timestamp)) {
+ return timestamp;
+ }
+ var format = 'ddd MMM DD YYYY';
+ var date = moment((new Date(timestamp)).toISOString().replace('Z', '')).format(format);
+ var today = moment((new Date()).toISOString().replace('Z', '')).format(format);
+ if (date === today) {
+ return 'Today ' + (new Date(timestamp)).toLocaleTimeString();
+ }
+ return date;
+ },
+
+ /**
+ * Convert starTimestamp to 'DAY_OF_THE_WEEK, MONTH DAY, YEAR HOURS:MINUTES', except for the case: year equals 1969
+ *
+ * @param {string} startTimestamp
+ * @return {string} startTimeSummary
+ * @method startTime
+ */
+ startTime: function (startTimestamp) {
+ if (!App.Helpers.number.isValidInt(startTimestamp)) {
+ return '';
+ }
+ var startDate = new Date(startTimestamp);
+ var months = this.dateMonths;
+ var days = this.dateDays;
+ // generate start time
+ if (startDate.getFullYear() == 1969 || startTimestamp < 1) {
+ return 'Not started';
+ }
+ var startTimeSummary = '';
+ if (new Date(startTimestamp).setHours(0, 0, 0, 0) == new Date().setHours(0, 0, 0, 0)) { //today
+ startTimeSummary = 'Today ' + this.dateFormatZeroFirst(startDate.getHours()) + ':' + this.dateFormatZeroFirst(startDate.getMinutes());
+ } else {
+ startTimeSummary = days[startDate.getDay()] + ' ' + months[startDate.getMonth()] + ' ' +
+ this.dateFormatZeroFirst(startDate.getDate()) + ' ' + startDate.getFullYear() + ' '
+ + this.dateFormatZeroFirst(startDate.getHours()) + ':' + this.dateFormatZeroFirst(startDate.getMinutes());
+ }
+ return startTimeSummary;
+ },
+
+ /**
+ * Provides the duration between the given start and end timestamp. If start time
+ * not valid, duration will be ''. If end time is not valid, duration will
+ * be till now, showing 'Lasted for xxx secs'.
+ *
+ * @param {string} startTimestamp
+ * @param {string} endTimestamp
+ * @return {string} durationSummary
+ * @method durationSummary
+ */
+ durationSummary: function (startTimestamp, endTimestamp) {
+ // generate duration
+ var durationSummary = '';
+ var startDate = new Date(startTimestamp);
+ var endDate = new Date(endTimestamp);
+ if (startDate.getFullYear() == 1969 || startTimestamp < 1) {
+ // not started
+ return Em.I18n.t('common.na');
+ }
+ if (endDate.getFullYear() != 1969 && endTimestamp > 0) {
+ return '' + this.timingFormat(endTimestamp - startTimestamp, 1); //lasted for xx secs
+ } else {
+ // still running, duration till now
+ var t = new Date().getTime(),
+ time = (t - startTimestamp) < 0 ? 0 : (t - startTimestamp);
+ durationSummary = '' + this.timingFormat(time, 1);
+ }
+ return durationSummary;
+ },
+
+ /**
+ * Convert time in mseconds to
+ * 30 ms = 30 ms
+ * 300 ms = 300 ms
+ * 999 ms = 999 ms
+ * 1000 ms = 1.00 secs
+ * 3000 ms = 3.00 secs
+ * 35000 ms = 35.00 secs
+ * 350000 ms = 350.00 secs
+ * 999999 ms = 999.99 secs
+ * 1000000 ms = 16.66 mins
+ * 3500000 secs = 58.33 mins
+ *
+ * @param {number} time
+ * @param {bool} zeroValid for the case to show 0 when time is 0, not null
+ * @return {string|null} formatted date
+ * @method timingFormat
+ */
+ timingFormat: function (time, /* optional */ zeroValid) {
+ var intTime = parseInt(time);
+ if (zeroValid && intTime == 0) {
+ return 0 + ' secs';
+ }
+ if (!intTime) {
+ return null;
+ }
+ var timeStr = intTime.toString();
+ var lengthOfNumber = timeStr.length;
+ var oneMinMs = 60000;
+ var oneHourMs = 3600000;
+ var oneDayMs = 86400000;
+
+ if (lengthOfNumber < 4) {
+ return time + ' ms';
+ } else if (lengthOfNumber < 7) {
+ time = (time / 1000).toFixed(2);
+ return time + ' secs';
+ } else if (time < oneHourMs) {
+ time = (time / oneMinMs).toFixed(2);
+ return time + ' mins';
+ } else if (time < oneDayMs) {
+ time = (time / oneHourMs).toFixed(2);
+ return time + ' hours';
+ } else {
+ time = (time / oneDayMs).toFixed(2);
+ return time + ' days';
+ }
+ },
+
+ /**
+ * Provides the duration between the given start and end time. If start time
+ * is not given, duration will be 0. If end time is not given, duration will
+ * be till now.
+ *
+ * @param {Number} startTime Start time from epoch
+ * @param {Number} endTime End time from epoch
+ * @return {Number} duration
+ * @method duration
+ */
+ duration: function (startTime, endTime) {
+ var duration = 0;
+ if (startTime && startTime > 0) {
+ if (!endTime || endTime < 1) {
+ endTime = new Date().getTime();
+ }
+ duration = endTime - startTime;
+ }
+ return duration;
+ }
+};
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/handlebarHelpers.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/handlebarHelpers.js b/tez-ui/app/scripts/helpers/handlebarHelpers.js
new file mode 100644
index 0000000..5ed5e8e
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/handlebarHelpers.js
@@ -0,0 +1,48 @@
+/**
+ * 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.
+ */
+
+/**
+ * formats the given unix timestamp. returns NaN if its not a number.
+ *
+ * @param {number} unixtimestamp
+ * @returns {string}
+ * @method formatUnixTimestamp
+ */
+Em.Handlebars.helper('formatUnixTimestamp', function(timestamp) {
+ if (!App.Helpers.number.isValidInt(timestamp)) return 'NaN';
+ if (timestamp > 0) {
+ return App.Helpers.date.dateFormat(timestamp);
+ }
+ return '';
+});
+
+/*
+ * formats the duration.
+ *
+ * @param {duration} duration in milliseconds
+ * @return {Number}
+ * @method formatDuration
+ */
+Em.Handlebars.helper('formatDuration', function(startTime, endTime) {
+ // unixtimestamp is in seconds. javascript expects milliseconds.
+ if (endTime < startTime || !!endTime) {
+ end = new Date().getTime();
+ }
+
+ return App.Helpers.date.durationSummary(startTime, endTime);
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/number.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/number.js b/tez-ui/app/scripts/helpers/number.js
new file mode 100644
index 0000000..66b3e8e
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/number.js
@@ -0,0 +1,101 @@
+/**
+ * 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.
+ */
+
+App.Helpers.number = {
+
+ /**
+ * Convert byte size to other metrics.
+ *
+ * @param {Number} bytes to convert to string
+ * @param {Number} precision Number to adjust precision of return value. Default is 0.
+ * @param {String} parseType
+ * JS method name for parse string to number. Default is "parseInt".
+ * @param {Number} multiplyBy bytes by this number if given. This is needed
+ * as <code>null * 1024 = 0</null>
+ * @remarks The parseType argument can be "parseInt" or "parseFloat".
+ * @return {String} Returns converted value with abbreviation.
+ */
+ bytesToSize: function (bytes, precision, parseType, multiplyBy) {
+ if (isNaN(bytes)) bytes = 0;
+ if (Em.isNone(bytes)) {
+ return 'n/a';
+ } else {
+ if (arguments[2] === undefined) {
+ parseType = 'parseInt';
+ }
+ if (arguments[3] === undefined) {
+ multiplyBy = 1;
+ }
+ var value = bytes * multiplyBy;
+ var sizes = [ 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB' ];
+ var posttxt = 0;
+ while (value >= 1024) {
+ posttxt++;
+ value = value / 1024;
+ }
+ if (value === 0) {
+ precision = 0;
+ }
+ var parsedValue = window[parseType](value);
+ return parsedValue.toFixed(precision) + " " + sizes[posttxt];
+ }
+ },
+
+ /**
+ * Validates if the given string or number is an integer between the
+ * values of min and max (inclusive). The minimum and maximum
+ * checks are ignored if their valid is NaN.
+ *
+ * @method validateInteger
+ * @param {string|number} str - input string
+ * @param {string|number} [min]
+ * @param {string|number} [max]
+ */
+ validateInteger : function(str, min, max) {
+ if (Em.isNone(str) || (str + "").trim().length < 1) {
+ return Em.I18n.t('number.validate.empty');
+ }
+ str = (str + "").trim();
+ var number = parseInt(str);
+ if (isNaN(number)) {
+ return Em.I18n.t('number.validate.notValidNumber');
+ }
+ if (str.length != (number + "").length) {
+ // parseInt("1abc") returns 1 as integer
+ return Em.I18n.t('number.validate.notValidNumber');
+ }
+ if (!isNaN(min) && number < min) {
+ return Em.I18n.t('number.validate.lessThanMinumum').fmt(min);
+ }
+ if (!isNaN(max) && number > max) {
+ return Em.I18n.t('number.validate.moreThanMaximum').fmt(max);
+ }
+ return null;
+ },
+
+ /**
+ * Checks if the value is an integer or can be converted to an integer.
+ * a value of NaN returns false.
+ * @method: isValidInt
+ * @param {string|number} value to check
+ * @return {boolean}
+ */
+ isValidInt: function(value) {
+ return value % 1 == 0;
+ }
+
+};
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/helpers/translation.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/helpers/translation.js b/tez-ui/app/scripts/helpers/translation.js
new file mode 100644
index 0000000..ed32a39
--- /dev/null
+++ b/tez-ui/app/scripts/helpers/translation.js
@@ -0,0 +1,119 @@
+/**
+ * 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.
+ */
+
+Ember.I18n.translations = {
+
+ 'any': 'Any',
+ 'apply': 'Apply',
+ 'ok': 'Ok',
+ 'cancel': 'Cancel',
+
+ 'common.id': 'Entity Id',
+ 'common.applicationId': 'Application Id',
+ 'common.status':'Status',
+ 'common.time.start': 'Start Time',
+ 'common.time.end': 'End Time',
+ 'common.name': 'Name',
+ 'common.tasks':'Tasks',
+ 'common.na': 'n/a',
+ 'common.value': 'Value',
+ 'common.user': 'User',
+ 'common.time.duration': 'Duration',
+
+ 'number.validate.empty': 'cannot be empty',
+ 'number.validate.notValidNumber': 'not a valid number',
+ 'number.validate.lessThanMinumum': 'value less than %@1',
+ 'number.validate.moreThanMaximum': 'value greater than %@1',
+
+ 'common.loading': 'Loading...',
+ 'http.error.400': 'Unable to load data.',
+ 'dags.nothingToShow': 'No Dags to display',
+
+ 'jobs.type':'Jobs Type',
+ 'jobs.type.hive':'Hive',
+ 'jobs.show.up.to':'Show up to',
+ 'jobs.filtered.jobs':'%@ jobs showing',
+ 'jobs.filtered.clear':'clear filters',
+ 'jobs.column.id':'Id',
+ 'jobs.column.user':'User',
+ 'jobs.column.start.time':'Start Time',
+ 'jobs.column.end.time':'End Time',
+ 'jobs.column.duration':'Duration',
+ 'jobs.new_jobs.info':'New jobs available on server.',
+ 'jobs.loadingTasks': 'Loading...',
+
+ 'jobs.nothingToShow': 'No jobs to display',
+ 'jobs.error.ats.down': 'Jobs data cannot be shown since YARN App Timeline Server is not running.',
+ 'jobs.error.400': 'Unable to load data.',
+ 'jobs.table.custom.date.am':'AM',
+ 'jobs.table.custom.date.pm':'PM',
+ 'jobs.table.custom.date.header':'Select Custom Dates',
+ 'jobs.table.job.fail':'Job failed to run',
+ 'jobs.customDateFilter.error.required':'This field is required',
+ 'jobs.customDateFilter.error.date.order':'End Date must be after Start Date',
+ 'jobs.customDateFilter.startTime':'Start Time',
+ 'jobs.customDateFilter.endTime':'End Time',
+ 'jobs.hive.failed':'JOB FAILED',
+ 'jobs.hive.more':'show more',
+ 'jobs.hive.less':'show less',
+ 'jobs.hive.query':'Hive Query',
+ 'jobs.hive.stages':'Stages',
+ 'jobs.hive.yarnApplication':'YARN Application',
+ 'jobs.hive.tez.tasks':'Tez Tasks',
+ 'jobs.hive.tez.hdfs':'HDFS',
+ 'jobs.hive.tez.localFiles':'Local Files',
+ 'jobs.hive.tez.spilledRecords':'Spilled Records',
+ 'jobs.hive.tez.records':'Records',
+ 'jobs.hive.tez.reads':'%@1 reads',
+ 'jobs.hive.tez.writes':'%@1 writes',
+ 'jobs.hive.tez.records.count':'%@1 Records',
+ 'jobs.hive.tez.operatorPlan':'Operator Plan',
+ 'jobs.hive.tez.dag.summary.metric':'Summary Metric',
+ 'jobs.hive.tez.dag.error.noDag.title':'No Tez Information',
+ 'jobs.hive.tez.dag.error.noDag.message':'This job does not identify any Tez information.',
+ 'jobs.hive.tez.dag.error.noDagId.title':'No Tez Information',
+ 'jobs.hive.tez.dag.error.noDagId.message':'No Tez information was found for this job. Either it is waiting to be run, or has exited unexpectedly.',
+ 'jobs.hive.tez.dag.error.noDagForId.title':'No Tez Information',
+ 'jobs.hive.tez.dag.error.noDagForId.message':'No details were found for the Tez ID given to this job.',
+ 'jobs.hive.tez.metric.input':'Input',
+ 'jobs.hive.tez.metric.output':'Output',
+ 'jobs.hive.tez.metric.recordsRead':'Records Read',
+ 'jobs.hive.tez.metric.recordsWrite':'Records Written',
+ 'jobs.hive.tez.metric.tezTasks':'Tez Tasks',
+ 'jobs.hive.tez.metric.spilledRecords':'Spilled Records',
+ 'jobs.hive.tez.edge.':'Unknown',
+ 'jobs.hive.tez.edge.contains':'Contains',
+ 'jobs.hive.tez.edge.broadcast':'Broadcast',
+ 'jobs.hive.tez.edge.scatter_gather':'Shuffle',
+
+ 'app.loadingPlaceholder': 'Loading...',
+ 'apps.item.dag.job': 'Job',
+ 'apps.item.dag.jobId': 'Job Id',
+ 'apps.item.dag.type': 'Job Type',
+ 'apps.item.dag.status': 'Status',
+ 'apps.item.dag.num_stages': 'Total Stages',
+ 'apps.item.dag.stages': 'Tasks per Stage',
+ 'apps.item.dag.maps': 'Maps',
+ 'apps.item.dag.reduces': 'Reduces',
+ 'apps.item.dag.input': 'Input',
+ 'apps.item.dag.output': 'Output',
+ 'apps.item.dag.duration': 'Duration',
+
+ 'menu.item.jobs':'Jobs'
+
+};
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/mappers/dag_mapper.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/mappers/dag_mapper.js b/tez-ui/app/scripts/mappers/dag_mapper.js
new file mode 100644
index 0000000..aa862f9
--- /dev/null
+++ b/tez-ui/app/scripts/mappers/dag_mapper.js
@@ -0,0 +1,56 @@
+/**
+ * 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 jsonToDagMap = {
+ id: 'entity',
+ startTime: 'otherinfo.startTime', //TODO: what is the difference between this and the one in the otherinfo
+ endTime: 'otherinfo.endTime',
+ name: 'primaryfilters.dagName',
+ user: 'primaryfilters.user',
+ applicationId: 'otherinfo.applicationId',
+ status: 'otherinfo.status',
+ diagnostics: 'otherinfo.diagnostics'
+};
+
+App.Mappers.Dag = {
+ mapSingle : function(json) {
+ return Em.JsonMapper.map(json, jsonToDagMap);
+ },
+
+ mapMany : function(json) {
+ if (Array.isArray(json.entities)) {
+ return json.entities.map(this.mapSingle);
+ }
+
+ return [];
+ }
+}
+
+App.Mappers.Vertex = {
+ mapSingle: function(json) {
+ return Em.JsonMapper.map(json, jsonToVertexMap);
+ },
+
+ mapMany: function(json) {
+ if (Array.isArray(json.entities)) {
+ return json.entities.map(this.mapSingle);
+ }
+
+ return [];
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/mixins/display_helpers.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/mixins/display_helpers.js b/tez-ui/app/scripts/mixins/display_helpers.js
new file mode 100644
index 0000000..a89b597
--- /dev/null
+++ b/tez-ui/app/scripts/mixins/display_helpers.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.
+ */
+
+// TODO : document
+App.Helpers.DisplayHelper = Em.Mixin.create({
+ startTimeDisplay: function() {
+ var startTime = this.get('startTime');
+ return startTime > 0 ? App.Helpers.date.dateFormat(startTime) : '';
+ }.property('startTime'),
+
+ endtimeDisplay: function() {
+ var endTime = this.get('endTime');
+ return endTime > 0 ? App.Helpers.date.dateFormat(endTime) : '';
+ }.property('endTime'),
+
+ duration: function() {
+ var startTime = this.get('startTime');
+ var endTime = this.get('endTime');
+ if(endTime < startTime || endTime == undefined) {
+ endTime = new Date().getTime();
+ }
+ return App.Helpers.date.duration(startTime, endTime);
+ }.property('startTime', 'endTime')
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/mixins/run_periodically.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/mixins/run_periodically.js b/tez-ui/app/scripts/mixins/run_periodically.js
new file mode 100644
index 0000000..6a534a9
--- /dev/null
+++ b/tez-ui/app/scripts/mixins/run_periodically.js
@@ -0,0 +1,78 @@
+/**
+ * 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.
+ */
+
+/**
+ * Allow to run object method periodically and stop it
+ * Example:
+ * <code>
+ * var obj = Ember.Object.createWithMixins(App.RunPeriodically, {
+ * method: Ember.K
+ * });
+ * obj.set('interval', 10000); // override default value
+ * obj.loop('method'); // run periodically
+ * obj.stop(); // stop running
+ * </code>
+ * @type {Ember.Mixin}
+ */
+App.RunPeriodically = Ember.Mixin.create({
+
+ /**
+ * Interval for loop
+ * @type {number}
+ */
+ interval: 10000,
+
+ /**
+ * setTimeout's return value
+ * @type {number}
+ */
+ timer: null,
+
+ /**
+ * Run <code>methodName</code> periodically with <code>interval</code>
+ * @param {string} methodName method name to run periodically
+ * @param {bool} initRun should methodName be run before setInterval call (default - true)
+ * @method run
+ */
+ loop: function(methodName, initRun) {
+ initRun = Em.isNone(initRun) ? true : initRun;
+ var self = this,
+ interval = this.get('interval');
+ Ember.assert('Interval should be numeric and greated than 0', $.isNumeric(interval) && interval > 0);
+ if (initRun) {
+ this[methodName]();
+ }
+ this.set('timer',
+ setInterval(function () {
+ self[methodName]();
+ }, interval)
+ );
+ },
+
+ /**
+ * Stop running <code>timer</code>
+ * @method stop
+ */
+ stop: function() {
+ var timer = this.get('timer');
+ if (!Em.isNone(timer)) {
+ clearTimeout(timer);
+ }
+ }
+
+});
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/models/TimelineRestAdapter.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/models/TimelineRestAdapter.js b/tez-ui/app/scripts/models/TimelineRestAdapter.js
new file mode 100644
index 0000000..2f82371
--- /dev/null
+++ b/tez-ui/app/scripts/models/TimelineRestAdapter.js
@@ -0,0 +1,124 @@
+var typeToPathMap = {
+ dag: 'TEZ_DAG_ID',
+ vertex: 'TEZ_VERTEX_ID'
+};
+
+App.TimelineRESTAdapter = DS.RESTAdapter.extend({
+ host: App.AtsBaseUrl,
+ namespace: 'ws/v1/timeline',
+
+ pathForType: function(type) {
+ return typeToPathMap[type];
+ },
+
+});
+
+App.TimelineSerializer = DS.RESTSerializer.extend({
+ primaryKey: 'entity',
+
+ attrs: {
+ key: 'entity',
+ },
+
+ extractSingle: function(store, primaryType, rawPayload, recordId) {
+ // rest serializer expects singular form of model as the root key.
+ var payload = {};
+ payload[primaryType.typeKey] = rawPayload;
+ return this._super(store, primaryType, payload, recordId);
+ },
+
+ extractArray: function(store, primaryType, rawPayload) {
+ // restserializer expects a plural of the model but TimelineServer returns
+ // it in entities.
+ var payload = {};
+ payload[primaryType.typeKey.pluralize()] = rawPayload.entities;
+ return this._super(store, primaryType, payload);
+ },
+
+});
+
+
+var timelineJsonToDagMap = {
+ id: 'entity',
+ startTime: 'otherinfo.startTime',
+ endTime: 'otherinfo.endTime',
+ name: 'primaryfilters.dagName.0',
+ user: 'primaryfilters.user.0',
+ applicationId: 'otherinfo.applicationId',
+ status: 'otherinfo.status',
+ diagnostics: 'otherinfo.diagnostics',
+ counterGroups: 'counterGroups'
+};
+
+
+App.DagSerializer = App.TimelineSerializer.extend({
+ normalizePayload: function(rawPayload){
+ // no normalization required for multiple case (findAll)
+ if (!!rawPayload.dags) {
+ return rawPayload;
+ }
+
+ var dag = rawPayload.dag;
+ var counterGroups = dag.otherinfo.counters.counterGroups;
+ delete dag.otherinfo.counters;
+
+ // todo move counter parsing outside
+ // flatten the dag -> countergroup -> counter structure
+ var dagID = dag.entity;
+ var allCounters = [];
+
+ // put counter group id in the list
+ dag['counterGroups'] = counterGroups.map(function(cg) {
+ cg.entity = dagID + '/' + cg.counterGroupName;
+ cg.parentID = {
+ type: 'dag',
+ id: dagID
+ };
+
+ // replace the counters with thier id.
+ cg.counters = cg.counters.map(function(counter){
+ counter.entity = cg.entity + '/' + counter.counterName;
+ counter.cgID = cg.entity;
+ allCounters.push(counter);
+ return counter.entity;
+ });
+
+ return cg.entity;
+ });
+
+ var normalizedPayload = {
+ 'dag': dag,
+ 'counterGroups': counterGroups,
+ 'counters': allCounters
+ };
+ return normalizedPayload;
+ },
+
+ normalize: function(type, hash, prop) {
+ return Em.JsonMapper.map(hash, timelineJsonToDagMap);
+ },
+});
+
+App.CounterGroupSerializer = DS.JSONSerializer.extend({
+ normalize: function(type, hash, prop) {
+ return {
+ 'id': hash.entity,
+ 'name': hash.counterGroupName,
+ 'displayName': hash.counterGroupDisplayName,
+ 'counters': hash.counters,
+ 'parent': hash.parentID
+ };
+ }
+});
+
+App.CounterSerializer = DS.JSONSerializer.extend({
+ normalize: function(type, hash, prop) {
+ return {
+ 'id': hash.entity,
+ 'name': hash.counterName,
+ 'displayName': hash.counterDisplayName,
+ 'value': hash.counterValue,
+ 'parent': hash.cgID
+ };
+ }
+});
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/models/abstract_entity.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/models/abstract_entity.js b/tez-ui/app/scripts/models/abstract_entity.js
new file mode 100644
index 0000000..a3357ff
--- /dev/null
+++ b/tez-ui/app/scripts/models/abstract_entity.js
@@ -0,0 +1,27 @@
+/**
+ * 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.
+ */
+
+App.AbstractEntity = DS.Model.extend({
+ // type of the entity. should be one of App.EntityType
+ entityType: DS.attr('string')
+});
+
+App.EntityType = {
+ DAG: 'dag',
+ VERTEX: 'vertex',
+ TASK: 'task'
+};
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/models/dag.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/models/dag.js b/tez-ui/app/scripts/models/dag.js
new file mode 100644
index 0000000..7acf984
--- /dev/null
+++ b/tez-ui/app/scripts/models/dag.js
@@ -0,0 +1,231 @@
+/**
+ * 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.
+ */
+
+App.Dag = App.AbstractEntity.extend({
+
+ // start time of the entity
+ startTime: DS.attr('number'),
+
+ // end time of the entity
+ endTime: DS.attr('number'),
+
+ // set type to DAG
+ entityType: App.EntityType.DAG,
+
+ // Name of the dag.
+ name: DS.attr('string'),
+
+ // user name who ran this dag.
+ user: DS.attr('string'),
+
+ // application ID of this dag.
+ applicationId: DS.attr('string'),
+
+ // status
+ status: DS.attr('string'),
+
+ // diagnostics info if any.
+ diagnostics: DS.attr('string'),
+
+ //vertices: DS.hasMany('dagVertex'),
+
+ //edges: DS.hasMany('dagEdge'),
+
+ counterGroups: DS.hasMany('counterGroup', { inverse: 'parent' })
+});
+
+App.CounterGroup = DS.Model.extend({
+ name: DS.attr('string'),
+
+ displayName: DS.attr('string'),
+
+ counters: DS.hasMany('counter', { inverse: 'parent' }),
+
+ parent: DS.belongsTo('abstractEntity', { polymorphic: true })
+});
+
+App.Counter = DS.Model.extend({
+ name: DS.attr('string'),
+
+ displayName: DS.attr('string'),
+
+ value: DS.attr('number'),
+
+ parent: DS.belongsTo('counterGroup')
+});
+
+App.DagEdge = DS.Model.extend({
+
+ fromVertex: DS.belongsTo('dagVertex'),
+
+ toVertex: DS.belongsTo('dagVertex'),
+
+ /**
+ * Type of this edge connecting vertices. Should be one of constants defined
+ * in 'App.DagEdgeType'.
+ */
+ edgeType: DS.attr('string'),
+
+ dag: DS.belongsTo('dag')
+});
+
+App.DagVertex = DS.Model.extend({
+ name: DS.attr('string'),
+
+ dag: DS.belongsTo('dag'),
+
+ /**
+ * State of this vertex. Should be one of constants defined in
+ * App.DagVertexState.
+ */
+ state: DS.attr('string'),
+
+ /**
+ * Vertex type has to be one of the types defined in 'App.DagVertexType'
+ * @return {string}
+ */
+ type: DS.attr('string'),
+
+ /**
+ * A vertex can have multiple incoming edges.
+ */
+ incomingEdges: DS.hasMany('dagEdge'),
+
+ /**
+ * This vertex can have multiple outgoing edges.
+ */
+ outgoingEdges: DS.hasMany('dagEdge'),
+
+ startTime: DS.attr('number'),
+
+ endTime: DS.attr('number'),
+
+ /**
+ * Provides the duration of this job. If the job has not started, duration
+ * will be given as 0. If the job has not ended, duration will be till now.
+ *
+ * @return {Number} Duration in milliseconds.
+ */
+ duration: function () {
+ return App.Helpers.date.duration(this.get('startTime'), this.get('endTime'))
+ }.property('startTime', 'endTime'),
+
+ /**
+ * Each Tez vertex can perform arbitrary application specific computations
+ * inside. The application can provide a list of operations it has provided in
+ * this vertex.
+ *
+ * Array of strings. [{string}]
+ */
+ operations: DS.attr('array'),
+
+ /**
+ * Provides additional information about the 'operations' performed in this
+ * vertex. This is shown directly to the user.
+ */
+ operationPlan: DS.attr('string'),
+
+ /**
+ * Number of actual Map/Reduce tasks in this vertex
+ */
+ tasksCount: DS.attr('number'),
+
+ tasksNumber: function () {
+ return this.getWithDefault('tasksCount', 0);
+ }.property('tasksCount'),
+
+ /**
+ * Local filesystem usage metrics for this vertex
+ */
+ fileReadBytes: DS.attr('number'),
+
+ fileWriteBytes: DS.attr('number'),
+
+ fileReadOps: DS.attr('number'),
+
+ fileWriteOps: DS.attr('number'),
+
+ /**
+ * Spilled records
+ */
+ spilledRecords: DS.attr('number'),
+
+ /**
+ * HDFS usage metrics for this vertex
+ */
+ hdfsReadBytes: DS.attr('number'),
+
+ hdfsWriteBytes: DS.attr('number'),
+
+ hdfsReadOps: DS.attr('number'),
+
+ hdfsWriteOps: DS.attr('number'),
+
+ /**
+ * Record metrics for this vertex
+ */
+ recordReadCount: DS.attr('number'),
+
+ recordWriteCount: DS.attr('number'),
+
+ totalReadBytes: function () {
+ return this.get('fileReadBytes') + this.get('hdfsReadBytes');
+ }.property('fileReadBytes', 'hdfsReadBytes'),
+
+ totalWriteBytes: function () {
+ return this.get('fileWriteBytes') + this.get('hdfsWriteBytes');
+ }.property('fileWriteBytes', 'hdfsWriteBytes'),
+
+ totalReadBytesDisplay: function () {
+ return App.Helpers.number.bytesToSize(this.get('totalReadBytes'));
+ }.property('totalReadBytes'),
+
+ totalWriteBytesDisplay: function () {
+ return App.Helpers.number.bytesToSize(this.get('totalWriteBytes'));
+ }.property('totalWriteBytes'),
+
+ durationDisplay: function () {
+ return App.Helpers.date.timingFormat(this.get('duration'), true);
+ }.property('duration')
+});
+
+
+
+App.DagVertexState = {
+ NEW: "NEW",
+ INITIALIZING: "INITIALIZING",
+ INITED: "INITED",
+ RUNNING: "RUNNING",
+ SUCCEEDED: "SUCCEEDED",
+ FAILED: "FAILED",
+ KILLED: "KILLED",
+ ERROR: "ERROR",
+ TERMINATING: "TERMINATING",
+ JOBFAILED: "JOB FAILED"
+};
+
+App.DagVertexType = {
+ MAP: 'MAP',
+ REDUCE: 'REDUCE',
+ UNION: 'UNION'
+};
+
+App.DagEdgeType = {
+ SCATTER_GATHER: "SCATTER_GATHER",
+ BROADCAST: "BROADCAST",
+ CONTAINS: "CONTAINS"
+};
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/router.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/router.js b/tez-ui/app/scripts/router.js
new file mode 100644
index 0000000..28e279d
--- /dev/null
+++ b/tez-ui/app/scripts/router.js
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.Router.map(function() {
+ this.resource('dags', { path: '/' });
+ this.resource('dag', { path: '/dag/:dag_id'}, function() {
+ this.route('vertex'),
+ this.route('swimlane')
+ });
+ //this.resource('error', {path: '/error'});
+});
+
+/*
+App.ApplicationRoute = Em.Route.extend({
+ actions: {
+ //TODO: handle error and show proper error message.
+ error: function(error, transition) {
+ return;
+ this.transitionTo('error').then(function(newRoute) {
+ newRoute.controller.set('err', {
+ target: transition.targetName,
+ statusText: error.message,
+ responseText: error.stack
+ });
+ newRoute.controller.set('pageTitle', "Error");
+ });
+ }
+ }
+});*/
+
+App.DagsRoute = Em.Route.extend({
+ queryParams: {
+ count: {
+ refreshModel: true,
+ replace: true
+ },
+ fromID: {
+ refreshModel: true,
+ replace: true
+ },
+ fromTS: {
+ refreshModel: true,
+ replace: true,
+ },
+ user: {
+ refreshModel: true,
+ replace: true
+ }
+ },
+
+ model: function(params) {
+ var controllerClass = this.controllerFor('dags');
+ var queryParams = controllerClass.getFilterParams(params);
+
+ //TODO remove this
+ this.store.unloadAll('dag');
+ return this.store.findQuery('dag', queryParams);
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ },
+});
+
+App.DagRoute = Em.Route.extend({
+ model: function(params) {
+ return this.store.find('dag', params.dag_id);
+ },
+
+ setupController: function(controller, model) {
+ this._super(controller, model);
+ }
+});
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/scripts/store.js
----------------------------------------------------------------------
diff --git a/tez-ui/app/scripts/store.js b/tez-ui/app/scripts/store.js
new file mode 100644
index 0000000..21cae3e
--- /dev/null
+++ b/tez-ui/app/scripts/store.js
@@ -0,0 +1,21 @@
+/**
+ * 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.
+ */
+
+//App.Store = DS.Store.extend();
+//App.ApplicationAdapter = DS.FixtureAdapter.extend();
+//App.ApplicationAdapter = App.TimelineRestAdapter.extend();
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/styles/main.css
----------------------------------------------------------------------
diff --git a/tez-ui/app/styles/main.css b/tez-ui/app/styles/main.css
new file mode 100644
index 0000000..67082e9
--- /dev/null
+++ b/tez-ui/app/styles/main.css
@@ -0,0 +1,70 @@
+
+/* misc helpers */
+.align-right {
+ float: right;
+}
+
+.align-left {
+ float: left;
+}
+
+.align-clear {
+ clear: both;
+}
+
+.margin-small {
+ margin: 15px;
+}
+
+.margin-small-horizontal {
+ margin: 15px 0px;
+}
+
+.margin-medium {
+ margin: 30px;
+}
+
+.type-table {
+ display: table;
+}
+
+.fill-full {
+ width: 100%;
+ height: 100%;
+}
+
+/* Navigation */
+.page-nav-link {
+ cursor: pointer;
+ color: black;
+}
+
+.page-nav-link.disabled {
+ pointer-events: none;
+ color:lightgray;
+}
+
+.pill-container {
+
+}
+
+.pill-container {
+ display: table-cell;
+ margin-right: 30px;
+}
+
+.pill-container a {
+ height: 30px !important;
+ padding: 5px 15px !important;
+}
+
+/* dag page */
+.detail-list {
+ table-layout: fixed;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.detail-list td:first-child {
+ width:120px;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/templates/application.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/app/templates/application.hbs b/tez-ui/app/templates/application.hbs
new file mode 100644
index 0000000..7b35f42
--- /dev/null
+++ b/tez-ui/app/templates/application.hbs
@@ -0,0 +1,21 @@
+{{!
+ * 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 style="width: 95%; margin: 0 auto;">
+ {{outlet}}
+</div>
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/templates/components/page-nav.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/app/templates/components/page-nav.hbs b/tez-ui/app/templates/components/page-nav.hbs
new file mode 100644
index 0000000..04cad94
--- /dev/null
+++ b/tez-ui/app/templates/components/page-nav.hbs
@@ -0,0 +1,22 @@
+{{!
+* 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.
+}}
+
+{{!TODO: change to classes. and disable rather than hiding}}
+ <i {{bind-attr class=':fa-arrow-circle-o-left :fa :fa-3x :page-nav-link hasPrev:enabled:disabled'}} {{action 'gotoFirst'}}></i>
+ <i {{bind-attr class=':fa-arrow-circle-left :fa :fa-3x :page-nav-link hasPrev:enabled:disabled'}} {{action 'gotoPrev'}}></i>
+ <i {{bind-attr class=':fa-arrow-circle-right :fa :fa-3x :page-nav-link hasNext:enabled:disabled'}} {{action 'gotoNext'}}></i>
http://git-wip-us.apache.org/repos/asf/tez/blob/2f2739dd/tez-ui/app/templates/dag.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/app/templates/dag.hbs b/tez-ui/app/templates/dag.hbs
new file mode 100644
index 0000000..e0ccd05
--- /dev/null
+++ b/tez-ui/app/templates/dag.hbs
@@ -0,0 +1,36 @@
+{{partial "utils/pageHeader"}}
+{{#unless loading}}
+ <div class='margin-small-horizontal'>
+ {{#link-to 'dags'}}<i class='fa-arrow-circle-left fa fa-2x'> Dags</i>{{/link-to}}
+ </div>
+
+ <div class='type-table fill-full margin-small'>
+ <div class='align-left'>
+ {{!simple-kv-list data=this columns=commonDisplayProperties}}
+
+ <table class='detail-list'>
+ <tbody>
+ <tr>
+ <td>{{t common.id}}</td>
+ <td>{{id}}</td>
+ </tr>
+ <tr>
+ <td>{{t common.applicationId}}</td>
+ <td>{{applicationId}}</td>
+ </tr>
+ </tbody>
+ </table>
+
+ </div>
+
+ <div class='pill-container align-right'>
+ {{bs-pills contentBinding='childDisplayViews' selectedBinding='childDisplayViewSelected' size='lg'}}
+ </div>
+
+ </div>
+ <div class='margin-small'>
+ {{outlet}}
+ </div>
+{{else}}
+ {{partial 'utils/loadingSpinner'}}
+{{/unless}}
\ No newline at end of file