From cd367395f49760fc84cf3d3f8fc87783b167423d Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Tue, 20 Jan 2015 12:30:51 -0500 Subject: [PATCH 01/43] Fix incorrectly-cased file in gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5994375544..a0e2987a52 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ tar-build *.py[c,o] # JavaScript -/GruntFile.js +/Gruntfile.js /bower.json /package.json node_modules/** From 1697aff3093abb7e25805e34ecd5174831e20211 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Tue, 20 Jan 2015 12:45:28 -0500 Subject: [PATCH 02/43] Setup karma for testing with mocha & sinon for doubles --- awx/ui/tests/karma-shared.conf | 21 +- awx/ui/tests/karma-unit.conf | 3 +- awx/ui/tests/unit/CheckLicense.js | 183 ------------------ .../services/job-status-graph-data-test.js | 3 + 4 files changed, 18 insertions(+), 192 deletions(-) delete mode 100644 awx/ui/tests/unit/CheckLicense.js create mode 100644 awx/ui/tests/unit/services/job-status-graph-data-test.js diff --git a/awx/ui/tests/karma-shared.conf b/awx/ui/tests/karma-shared.conf index 36971fd92d..376ef52e47 100644 --- a/awx/ui/tests/karma-shared.conf +++ b/awx/ui/tests/karma-shared.conf @@ -9,15 +9,15 @@ module.exports = function() { // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine'], + frameworks: ['mocha', 'chai', 'sinon-chai', 'chai-as-promised'], // list of files / patterns to load in the browser files: [ '../static/lib/jquery/dist/jquery.min.js', - '../static/lib/angular/angular.min.js', - '../static/lib/angular-route/angular-route.min.js', - '../static/lib/angular-resource/angular-resource.min.js', - '../static/lib/angular-cookies/angular-cookies.min.js', + '../static/lib/angular/angular.js', + '../static/lib/angular-route/angular-route.js', + '../static/lib/angular-resource/angular-resource.js', + '../static/lib/angular-cookies/angular-cookies.js', '../static/lib/angular-sanitize/angular-sanitize.min.js', '../static/lib/angular-md5/angular-md5.min.js', '../static/lib/angular-codemirror/lib/AngularCodeMirror.js', @@ -30,9 +30,8 @@ module.exports = function() { '../static/lib/angular-scheduler/lib/angular-scheduler.min.js', '../static/lib/jqueryui/ui/minified/jquery-ui.min.js', '../static/lib/bootstrap/dist/js/bootstrap.min.js', - '../static/lib/js-yaml/js-yaml.min.js', + '../static/lib/js-yaml/dist/js-yaml.min.js', '../static/lib/select2/select2.min.js', - '../static/lib/js-yaml/js-yaml.min.js', '../static/lib/jsonlint/lib/jsonlint.js', '../static/lib/codemirror/lib/codemirror.js', '../static/lib/codemirror/mode/javascript/javascript.js', @@ -74,6 +73,12 @@ module.exports = function() { // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ['progress'], + client: { + mocha: { + ui: 'bdd' + } + }, + // web server port port: 9876, @@ -84,7 +89,7 @@ module.exports = function() { // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, + autoWatch: true, // start these browsers diff --git a/awx/ui/tests/karma-unit.conf b/awx/ui/tests/karma-unit.conf index 05239f9cb6..eae72dd9e0 100644 --- a/awx/ui/tests/karma-unit.conf +++ b/awx/ui/tests/karma-unit.conf @@ -10,7 +10,8 @@ module.exports = function(config) { conf.files = conf.files.concat([ '../static/lib/angular-mocks/angular-mocks.js', '../../../node_modules/ng-midway-tester/src/ngMidwayTester.js', - './unit/*' + './unit/*', + './unit/**/*' ]); // level of logging diff --git a/awx/ui/tests/unit/CheckLicense.js b/awx/ui/tests/unit/CheckLicense.js deleted file mode 100644 index c591e3fcaf..0000000000 --- a/awx/ui/tests/unit/CheckLicense.js +++ /dev/null @@ -1,183 +0,0 @@ -/********************************** - * Copyright (c) 2014 AnsibleWorks, Inc. - * - * CheckLicense.js - * - * Tests the CheckLicense service- helpers/CheckLicense.js - * - */ - - /* global describe, it, beforeEach, expect, module, inject */ - - var licenses = [{ - desc: 'expired license with < 1 day grace period', - valid_key: true, - time_remaining: 0, - grace_period_remaining: 85000, - free_instances: 10, - expects: 'grace period has been exceeded' - }, { - desc: 'expired license with > 1 day grace period', - valid_key: true, - time_remaining: 0, - grace_period_remaining: (86400 * 2), - free_instances: 10, - expects: '2 grace days' - }, { - desc: 'valid license with time remaining = 15 days', - valid_key: true, - time_remaining: (86400 * 15), - grace_period_remaining: 0, - free_instances: 10, - expects: 'license is valid' - }, { - desc: 'valid license with time remaining < 15 days', - valid_key: true, - time_remaining: (86400 * 10) , - grace_period_remaining: 0, - free_instances: 10, - expects: 'license has 10 days remaining' - }, { - desc: 'valid license with time remaining > 15 days and remaining hosts > 0', - valid_key: true, - time_remaining: (86400 * 20), - free_instances: 10, - grace_period_remaining: 0, - expects: 'license is valid' - }, { - desc: 'valid license with time remaining > 15 days and remaining hosts = 0', - valid_key: true, - time_remaining: (86400 * 20) , - grace_period_remaining: 0, - free_instances: 0, - expects: 'license has reached capacity' - }, { - desc: 'expired trial license with > 1 day grace period', - valid_key: true, - trial: true, - time_remaining: 0, - grace_period_remaining: (86400 * 2), - free_instances: 10, - notExpects: 'grace days' - } , { - desc: 'expired trial license with < 1 day grace period', - valid_key: true, - trial: true, - time_remaining: 0, - grace_period_remaining: 0, - free_instances: 10, - notExpects: '30 day grace period' - }, { - desc: 'trial license with time remaining = 15 days', - trial: true, - valid_key: true, - time_remaining: (86400 * 15), - grace_period_remaining: 0, - free_instances: 10, - notExpects: 'grace period' - }, { - desc: 'trial license with time remaining < 15 days', - valid_key: true, - trial: true, - time_remaining: (86400 * 10) , - grace_period_remaining: 0, - free_instances: 10, - notExpects: 'grace period' - }]; - -var should_notify = [{ - desc: 'should notify when license expired', - valid_key: true, - time_remaining: 0, - grace_period_remaining: 85000, - free_instances: 10 - }, { - desc: 'should notify when license time remaining < 15 days', - valid_key: true, - time_remaining: (86400 * 10) , - grace_period_remaining: 0, - free_instances: 10 - }, { - desc: 'should notify when host count <= 0', - valid_key: true, - time_remaining: (86400 * 200) , - grace_period_remaining: 0, - free_instances: 0 - }, { - desc: 'should notify when license is invalid', - valid_key: false - },{ - desc: 'should notify when license is empty', - }]; - -describe('Unit:CheckLicense', function() { - - beforeEach(module('Tower')); - - /*beforeEach(inject(function($rootScope) { - scope = $rootScope.$new(); - }));*/ - - it('should contain CheckLicense service', inject(function(CheckLicense) { - expect(CheckLicense).not.toBe(null); - })); - - it('should have a getRemainingDays method', inject(function(CheckLicense) { - expect(CheckLicense.getRemainingDays).not.toBe(null); - })); - - it('should have a getHTML method', inject(function(CheckLicense) { - expect(CheckLicense.getHTML).not.toBe(null); - })); - - it('should have a getAdmin method', inject(function(CheckLicense) { - expect(CheckLicense.getAdmin).not.toBe(null); - })); - - it('should have a shouldNotify method', inject(function(CheckLicense) { - expect(CheckLicense.shouldNotify).not.toBe(null); - })); - - it('should not notify when license valid, time remaining > 15 days and host count > 0', inject(function(CheckLicense) { - expect(CheckLicense.shouldNotify({ - valid_key: true, - time_remaining: (86400 * 20), - grace_period_remaining: 0, - free_instances: 10 })).toBe(false); - })); - - should_notify.forEach(function(lic) { - it(lic.desc, inject(function(CheckLicense) { - expect(CheckLicense.shouldNotify(lic)).toBe(true); - })); - }); - - licenses.forEach(function(lic) { - it(lic.desc, inject(function(CheckLicense) { - var r; - if (lic.expects) { - r = new RegExp(lic.expects); - expect(CheckLicense.getHTML(lic).body).toMatch(r); - } else { - r = new RegExp(lic.notExpects); - expect(CheckLicense.getHTML(lic).body).not.toMatch(r); - } - })); - }); - - it('should recognize empty license as invalid', inject(function(CheckLicense) { - expect(CheckLicense.getHTML({}).title).toMatch(/license required/i); - })); - - it('should show license update form to admin users when license is invalid', inject(function(CheckLicense, $rootScope) { - $rootScope.current_user = {}; - $rootScope.current_user.is_superuser = true; - expect(CheckLicense.getHTML({}).body).toMatch(/license\_license\_json/); - })); - - it('should not show license update form to non-admin users when license is invalid', inject(function(CheckLicense, $rootScope) { - $rootScope.current_user = {}; - $rootScope.current_user.is_superuser = false; - expect(CheckLicense.getHTML({}).body).not.toMatch(/license\_license\_json/); - })); -}); diff --git a/awx/ui/tests/unit/services/job-status-graph-data-test.js b/awx/ui/tests/unit/services/job-status-graph-data-test.js new file mode 100644 index 0000000000..692104c9db --- /dev/null +++ b/awx/ui/tests/unit/services/job-status-graph-data-test.js @@ -0,0 +1,3 @@ +describe("JobStatusGraphDataFactory", function() { + it('should fail', function() { expect(true).to.be.true; }); +}); From d7a150befece1d2e86a5b05c8fa35f14f4752766 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 21 Jan 2015 11:31:36 -0500 Subject: [PATCH 03/43] Extract graph data retrieval to a service --- awx/ui/static/js/app.js | 3 +- .../js/services/job-status-graph-data.js | 36 +++++++ awx/ui/templates/ui/index.html | 1 + .../services/job-status-graph-data-test.js | 95 ++++++++++++++++++- 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 awx/ui/static/js/services/job-status-graph-data.js diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index d551d6af11..cab1d896e0 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -18,11 +18,13 @@ if ($basePath) { urlPrefix = $basePath; } + angular.module('Tower', [ 'ngRoute', 'ngSanitize', 'ngCookies', 'RestServices', + 'DataServices', 'AuthService', 'Utilities', 'LicenseHelper', @@ -131,7 +133,6 @@ angular.module('Tower', [ .constant('AngularScheduler.useTimezone', true) .constant('AngularScheduler.showUTCField', true) .constant('$timezones.definitions.location', urlPrefix + 'lib/angular-tz-extensions/tz/data') - .config(['$routeProvider', function ($routeProvider) { diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js new file mode 100644 index 0000000000..638774177b --- /dev/null +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -0,0 +1,36 @@ +angular.module('DataServices', []) + .service('jobStatusGraphData', + ["RestServices", + "GetBasePath", + "ProcessErrors", + "$rootScope", + "$q", + JobStatusGraphData]); + +function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { + var callbacks = {}; + var currentCallbackId = 0; + + function getData() { + return Rest.get(); + } + + return { + setupWatcher: function() { + $rootScope.$on('JobStatusChange', function() { + getData().then(function(result) { + $rootScope. + $broadcast('DataReceived:JobStatusGraph', + result); + }); + }); + }, + get: function(period, jobType) { + + this.setupWatcher(); + + return getData(); + + } + }; +} diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 59572635ff..0a4310d75f 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -81,6 +81,7 @@ + diff --git a/awx/ui/tests/unit/services/job-status-graph-data-test.js b/awx/ui/tests/unit/services/job-status-graph-data-test.js index 692104c9db..e36014b500 100644 --- a/awx/ui/tests/unit/services/job-status-graph-data-test.js +++ b/awx/ui/tests/unit/services/job-status-graph-data-test.js @@ -1,3 +1,94 @@ -describe("JobStatusGraphDataFactory", function() { - it('should fail', function() { expect(true).to.be.true; }); +describe('Job Status Graph Data Service', function() { + + var q; + + var jobStatusGraphData, httpBackend, rootScope, timeout; + + var jobStatusChange = { + $on: sinon.spy(), + }; + + function flushPromises() { + window.setTimeout(function() { + inject(function($rootScope) { + $rootScope.$apply(); + }); + }, 100); + } + + var restStub = { + setUrl: angular.noop, + reset: function() { + delete restStub.deferred; + }, + get: function() { + if (angular.isUndefined(restStub.deferred)) { + restStub.deferred = q.defer(); + } + + return restStub.deferred.promise; + }, + succeed: function(value) { + restStub.deferred.resolve(value); + }, + fail: function(value) { + restStub.deferred.reject(value); + } + }; + + beforeEach(module("Tower")); + + beforeEach(module(function($provide) { + + $provide.value("$cookieStore", { get: angular.noop }); + + $provide.value('RestServices', restStub); + })); + + afterEach(function() { + restStub.reset(); + }); + + beforeEach(inject(function(_jobStatusGraphData_, $httpBackend, $q, $rootScope, $timeout) { + jobStatusGraphData = _jobStatusGraphData_; + httpBackend = $httpBackend; + rootScope = $rootScope; + timeout = $timeout; + $httpBackend.expectGET('/static/js/local_config.js').respond({ + }); + q = $q; + })); + + it('returns a promise to be fulfilled when data comes in', function() { + var firstResult = "result"; + + var result = jobStatusGraphData.get('', ''); + + restStub.succeed(firstResult); + + flushPromises(); + + return expect(result).to.eventually.equal(firstResult);; + }); + + it('broadcasts event when data is received', function() { + var expected = "value"; + var result = q.defer(); + jobStatusGraphData.setupWatcher(); + + inject(function($rootScope) { + $rootScope.$on('DataReceived:JobStatusGraph', function(e, data) { + result.resolve(data); + }); + $rootScope.$emit('JobStatusChange'); + restStub.succeed(expected); + flushPromises(); + }); + + return expect(result.promise).to.eventually.equal(expected); + }); + + xit('processes errors through error handler', function() { + }); + }); From e0efc11ef56955ab623c0baccb5826b335c803a9 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 21 Jan 2015 12:49:37 -0500 Subject: [PATCH 04/43] Add error handling to retrieval service --- awx/ui/static/js/services/job-status-graph-data.js | 12 ++++++++++++ .../unit/services/job-status-graph-data-test.js | 14 +++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index 638774177b..037747c496 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -22,6 +22,18 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { $rootScope. $broadcast('DataReceived:JobStatusGraph', result); + return result; + }).catch(function(response) { + var errorMessage = 'Failed to get: ' + url + ' GET returned: ' + status; + + ProcessErrors(null, + response.data, + response.status, + null, { + hdr: 'Error!', + msg: errorMessage + }); + return response; }); }); }, diff --git a/awx/ui/tests/unit/services/job-status-graph-data-test.js b/awx/ui/tests/unit/services/job-status-graph-data-test.js index e36014b500..4cb80df2a4 100644 --- a/awx/ui/tests/unit/services/job-status-graph-data-test.js +++ b/awx/ui/tests/unit/services/job-status-graph-data-test.js @@ -71,6 +71,17 @@ describe('Job Status Graph Data Service', function() { return expect(result).to.eventually.equal(firstResult);; }); + it('processes errors through error handler', function() { + var expected = { data: "error", status: "bad" }; + var actual = jobStatusGraphData.get(); + + restStub.fail(expected); + + flushPromises(); + + return expect(actual).to.be.rejectedWith(expected); + }); + it('broadcasts event when data is received', function() { var expected = "value"; var result = q.defer(); @@ -88,7 +99,4 @@ describe('Job Status Graph Data Service', function() { return expect(result.promise).to.eventually.equal(expected); }); - xit('processes errors through error handler', function() { - }); - }); From 95ad326c2973327452a6048a69b6f08bb9b01ffa Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 21 Jan 2015 14:46:36 -0500 Subject: [PATCH 05/43] Use new service in job status graph widget --- awx/ui/static/js/app.js | 12 ++++- awx/ui/static/js/controllers/Home.js | 9 ++-- .../js/services/job-status-graph-data.js | 45 ++++++++++--------- awx/ui/static/js/widgets/JobStatusGraph.js | 40 +++++++---------- .../services/job-status-graph-data-test.js | 17 ++++++- 5 files changed, 71 insertions(+), 52 deletions(-) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index cab1d896e0..08bec05b44 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -400,7 +400,17 @@ angular.module('Tower', [ when('/home', { templateUrl: urlPrefix + 'partials/home.html', - controller: 'Home' + controller: 'Home', + resolve: { + graphData: function($q, jobStatusGraphData) { + return $q.all({ + jobStatus: jobStatusGraphData.get("month", "all").then(function(data) { + console.log('got data: ', data); + return data.data; + }) + }); + } + } }). when('/home/groups', { diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index 820ef907c9..b753e9d442 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -26,8 +26,9 @@ * */ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, HostGraph, JobStatusGraph, HostPieChart, DashboardJobs, - ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button){ + ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, graphData){ + console.log('graphData:', graphData); ClearScope('home'); var buttons, html, e, waitCount, loadedCount,borderStyles, jobs_scope, schedule_scope; @@ -120,7 +121,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, JobStatusGraph({ scope: $scope, target: 'dash-job-status-graph', - dashboard: data + data: graphData.jobStatus }); if ($rootScope.user_is_superuser === true) { @@ -188,7 +189,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, } Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'HostGraph','JobStatusGraph', 'HostPieChart', 'DashboardJobs', - 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button' + 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button', 'graphData' ]; @@ -757,4 +758,4 @@ function HomeHosts($scope, $location, $routeParams, HomeHostList, GenerateList, HomeHosts.$inject = ['$scope', '$location', '$routeParams', 'HomeHostList', 'GenerateList', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'SetStatus', 'ToggleHostEnabled', 'HostsEdit', 'Stream', 'Find', 'ShowJobSummary', 'ViewJob' -]; \ No newline at end of file +]; diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index 037747c496..9bcdd33a25 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -1,6 +1,6 @@ angular.module('DataServices', []) .service('jobStatusGraphData', - ["RestServices", + ["Rest", "GetBasePath", "ProcessErrors", "$rootScope", @@ -11,37 +11,42 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { var callbacks = {}; var currentCallbackId = 0; - function getData() { + function getData(period, jobType) { + var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; + Rest.setUrl(url); return Rest.get(); } return { + destroyWatcher: angular.noop, setupWatcher: function() { - $rootScope.$on('JobStatusChange', function() { - getData().then(function(result) { - $rootScope. - $broadcast('DataReceived:JobStatusGraph', - result); - return result; - }).catch(function(response) { - var errorMessage = 'Failed to get: ' + url + ' GET returned: ' + status; + this.destroyWatcher = + $rootScope.$on('JobStatusChange', function() { + getData().then(function(result) { + $rootScope. + $broadcast('DataReceived:JobStatusGraph', + result); + return result; + }).catch(function(response) { + var errorMessage = 'Failed to get: ' + url + ' GET returned: ' + status; - ProcessErrors(null, - response.data, - response.status, - null, { - hdr: 'Error!', - msg: errorMessage - }); - return response; - }); + ProcessErrors(null, + response.data, + response.status, + null, { + hdr: 'Error!', + msg: errorMessage + }); + return response; + }); }); }, get: function(period, jobType) { + this.destroyWatcher(); this.setupWatcher(); - return getData(); + return getData(period, jobType); } }; diff --git a/awx/ui/static/js/widgets/JobStatusGraph.js b/awx/ui/static/js/widgets/JobStatusGraph.js index 6477ca7b6f..f5bbbbddd6 100644 --- a/awx/ui/static/js/widgets/JobStatusGraph.js +++ b/awx/ui/static/js/widgets/JobStatusGraph.js @@ -11,13 +11,13 @@ 'use strict'; angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities']) - .factory('JobStatusGraph', ['$rootScope', '$compile', '$location' , 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', - function ($rootScope, $compile , $location, Rest, GetBasePath, ProcessErrors) { + .factory('JobStatusGraph', ['$rootScope', '$compile', '$location' , 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', 'jobStatusGraphData', + function ($rootScope, $compile , $location, Rest, GetBasePath, ProcessErrors, jobStatusGraphData) { return function (params) { var scope = params.scope, target = params.target, - // dashboard = params.dashboard, + data = params.data, html, element, url, job_status_chart, period="month", job_type="all"; @@ -65,29 +65,16 @@ angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities']) // html += "\n"; - function createGraph(){ - - url = GetBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+job_type; - Rest.setUrl(url); - Rest.get() - .success(function (data){ + scope.$on('DataReceived:JobStatusGraph', + function(data) { scope.$emit('graphDataReady', data); - return job_type, period; + }); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get: ' + url + ' GET returned: ' + status }); - }); + function createGraph(period, jobtype){ + // console.log(jobStatusGraphData); + // jobStatusGraphData.get(period, jobtype); } - if ($rootScope.removeReloadJobStatusGraph) { - $rootScope.removeReloadJobStatusGraph(); - } - $rootScope.removeReloadJobStatusGraph = $rootScope.$on('ReloadJobStatusGraph', function() { - createGraph(); - }); - element = angular.element(document.getElementById(target)); element.html(html); $compile(element)(scope); @@ -192,7 +179,7 @@ angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities']) period = this.getAttribute("id"); $('#period-dropdown').replaceWith(""+this.text+"\n"); - createGraph(); + createGraph(period, job_type); }); //On click, update with new data @@ -201,7 +188,8 @@ angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities']) job_type = this.getAttribute("id"); $('#type-dropdown').replaceWith(""+this.text+"\n"); - createGraph(); + data + createGraph(period, job_type); }); scope.$emit('WidgetLoaded'); @@ -214,6 +202,8 @@ angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities']) }); + scope.$emit('graphDataReady', data); + }; } - ]); \ No newline at end of file + ]); diff --git a/awx/ui/tests/unit/services/job-status-graph-data-test.js b/awx/ui/tests/unit/services/job-status-graph-data-test.js index 4cb80df2a4..56dec232ba 100644 --- a/awx/ui/tests/unit/services/job-status-graph-data-test.js +++ b/awx/ui/tests/unit/services/job-status-graph-data-test.js @@ -8,12 +8,16 @@ describe('Job Status Graph Data Service', function() { $on: sinon.spy(), }; + var getBasePath = function(path) { + return '/' + path + '/'; + } + function flushPromises() { window.setTimeout(function() { inject(function($rootScope) { $rootScope.$apply(); }); - }, 100); + }); } var restStub = { @@ -42,7 +46,8 @@ describe('Job Status Graph Data Service', function() { $provide.value("$cookieStore", { get: angular.noop }); - $provide.value('RestServices', restStub); + $provide.value('Rest', restStub); + $provide.value('GetBasePath', getBasePath); })); afterEach(function() { @@ -99,4 +104,12 @@ describe('Job Status Graph Data Service', function() { return expect(result.promise).to.eventually.equal(expected); }); + it('requests data with given period and jobType', function() { + restStub.setUrl = sinon.spy(); + + jobStatusGraphData.get('1', '2'); + + expect(restStub.setUrl).to.have.been.calledWith('/dashboard/graphs/jobs/?period=1&job_type=2'); + }); + }); From b0dcafca8cb9723332bec91b6e61b9522e6eb738 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 21 Jan 2015 16:08:49 -0500 Subject: [PATCH 06/43] Display job status graph in directive instead of widget --- awx/ui/static/js/app.js | 5 +- awx/ui/static/js/controllers/Home.js | 19 ++-- .../job-status-graph.js} | 90 +++++++++---------- .../js/services/job-status-graph-data.js | 16 ++-- awx/ui/static/partials/home.html | 2 +- awx/ui/templates/ui/index.html | 1 + .../services/job-status-graph-data-test.js | 4 +- 7 files changed, 65 insertions(+), 72 deletions(-) rename awx/ui/static/js/{widgets/JobStatusGraph.js => directives/job-status-graph.js} (80%) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 08bec05b44..73675b9be2 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -25,6 +25,7 @@ angular.module('Tower', [ 'ngCookies', 'RestServices', 'DataServices', + 'GraphDirectives', 'AuthService', 'Utilities', 'LicenseHelper', @@ -86,7 +87,6 @@ angular.module('Tower', [ 'SelectionHelper', 'HostGroupsFormDefinition', 'DashboardCountsWidget', - 'JobStatusGraphWidget', 'HostPieChartWidget', 'HostGraphWidget', 'DashboardJobsWidget', @@ -405,8 +405,7 @@ angular.module('Tower', [ graphData: function($q, jobStatusGraphData) { return $q.all({ jobStatus: jobStatusGraphData.get("month", "all").then(function(data) { - console.log('got data: ', data); - return data.data; + return data; }) }); } diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index b753e9d442..5a248c0a86 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -25,10 +25,9 @@ * Host count graph should only be loaded if the user is a super user * */ -function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, HostGraph, JobStatusGraph, HostPieChart, DashboardJobs, +function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, HostGraph, HostPieChart, DashboardJobs, ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, graphData){ - console.log('graphData:', graphData); ClearScope('home'); var buttons, html, e, waitCount, loadedCount,borderStyles, jobs_scope, schedule_scope; @@ -118,11 +117,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, dashboard: data }); - JobStatusGraph({ - scope: $scope, - target: 'dash-job-status-graph', - data: graphData.jobStatus - }); + $scope.graphData = graphData; if ($rootScope.user_is_superuser === true) { waitCount = 5; @@ -151,11 +146,11 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, if ($rootScope.removeJobStatusChange) { $rootScope.removeJobStatusChange(); } - $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function() { - jobs_scope.refreshJobs(); - $scope.$emit('ReloadJobStatusGraph'); + // $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function() { + // jobs_scope.refreshJobs(); + // $scope.$emit('ReloadJobStatusGraph'); - }); + // }); if ($rootScope.removeScheduleChange) { $rootScope.removeScheduleChange(); @@ -188,7 +183,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, } -Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'HostGraph','JobStatusGraph', 'HostPieChart', 'DashboardJobs', +Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'HostGraph', 'HostPieChart', 'DashboardJobs', 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button', 'graphData' ]; diff --git a/awx/ui/static/js/widgets/JobStatusGraph.js b/awx/ui/static/js/directives/job-status-graph.js similarity index 80% rename from awx/ui/static/js/widgets/JobStatusGraph.js rename to awx/ui/static/js/directives/job-status-graph.js index f5bbbbddd6..a24df54c9b 100644 --- a/awx/ui/static/js/widgets/JobStatusGraph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -1,28 +1,22 @@ -/********************************************* - * Copyright (c) 2014 AnsibleWorks, Inc. - */ - /** - * @ngdoc function - * @name widgets.function:JobStatusGraph - * @description - */ +angular.module('GraphDirectives', []) + .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', 'jobStatusGraphData', + function ($rootScope, $compile , $location, $window, Rest, GetBasePath, ProcessErrors, Wait, jobStatusGraphData) { + return function (scope, element, attr) { - -'use strict'; - -angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities']) - .factory('JobStatusGraph', ['$rootScope', '$compile', '$location' , 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', 'jobStatusGraphData', - function ($rootScope, $compile , $location, Rest, GetBasePath, ProcessErrors, jobStatusGraphData) { - return function (params) { - - var scope = params.scope, - target = params.target, - data = params.data, - html, element, url, job_status_chart, + var html, url, job_status_chart, period="month", job_type="all"; - // html = "
\n"; + var cleanup = angular.noop; + + var data; + scope.$watch(attr.data, function(value) { + if (value) { + scope.$emit('graphDataReady', value); + } + }); + + scope.$on('$destroy', cleanup); html = "
\n"; html += "
Job Status
\n"; // for All Jobs, Past Month @@ -65,41 +59,41 @@ angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities']) // html += "
\n"; - scope.$on('DataReceived:JobStatusGraph', - function(data) { + cleanup = _.compose( + [ cleanup, + scope.$on('DataReceived:JobStatusGraph', + function(e, data) { scope.$emit('graphDataReady', data); - }); + }) + ]); function createGraph(period, jobtype){ - // console.log(jobStatusGraphData); - // jobStatusGraphData.get(period, jobtype); + jobStatusGraphData.get(period, jobtype).then(function(data) { + scope.$emit('graphDataReady', data); + }); } - element = angular.element(document.getElementById(target)); - element.html(html); - $compile(element)(scope); + element.html($compile(html)(scope)); - createGraph(); - - if (scope.removeResizeJobGraph) { - scope.removeResizeJobGraph(); - } - scope.removeResizeJobGraph= scope.$on('ResizeJobGraph', function () { - if($(window).width()<500){ - $('.graph-container').height(300); - } - else{ - var winHeight = $(window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container').height(available_height/2); - job_status_chart.update(); - } - }); + cleanup = _.compose( + [ cleanup, + angular.element($window).bind('resize', function() { + if($(window).width()<500){ + $('.graph-container').height(300); + } + else{ + var winHeight = $(window).height(), + available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; + $('.graph-container').height(available_height/2); + job_status_chart.update(); + } + }) + ]); if (scope.removeGraphDataReady) { scope.removeGraphDataReady(); } - scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data) { + scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data, third) { var timeFormat, graphData = [ @@ -202,8 +196,6 @@ angular.module('JobStatusGraphWidget', ['RestServices', 'Utilities']) }); - scope.$emit('graphDataReady', data); }; - } - ]); + }]); diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index 9bcdd33a25..4bf681102a 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -11,24 +11,30 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { var callbacks = {}; var currentCallbackId = 0; + function pluck(property, promise) { + return promise.then(function(value) { + return value[property]; + }); + } + function getData(period, jobType) { var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; Rest.setUrl(url); - return Rest.get(); + return pluck('data', Rest.get()); } return { destroyWatcher: angular.noop, - setupWatcher: function() { + setupWatcher: function(period, jobType) { this.destroyWatcher = $rootScope.$on('JobStatusChange', function() { - getData().then(function(result) { + getData(period, jobType).then(function(result) { $rootScope. $broadcast('DataReceived:JobStatusGraph', result); return result; }).catch(function(response) { - var errorMessage = 'Failed to get: ' + url + ' GET returned: ' + status; + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; ProcessErrors(null, response.data, @@ -44,7 +50,7 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { get: function(period, jobType) { this.destroyWatcher(); - this.setupWatcher(); + this.setupWatcher(period, jobType); return getData(period, jobType); diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index 844d6b15fd..5ea2cc5fe4 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -12,7 +12,7 @@
-
+
diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 0a4310d75f..d9314e9e48 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -82,6 +82,7 @@ + diff --git a/awx/ui/tests/unit/services/job-status-graph-data-test.js b/awx/ui/tests/unit/services/job-status-graph-data-test.js index 56dec232ba..e80102af97 100644 --- a/awx/ui/tests/unit/services/job-status-graph-data-test.js +++ b/awx/ui/tests/unit/services/job-status-graph-data-test.js @@ -69,7 +69,7 @@ describe('Job Status Graph Data Service', function() { var result = jobStatusGraphData.get('', ''); - restStub.succeed(firstResult); + restStub.succeed({ data: firstResult }); flushPromises(); @@ -97,7 +97,7 @@ describe('Job Status Graph Data Service', function() { result.resolve(data); }); $rootScope.$emit('JobStatusChange'); - restStub.succeed(expected); + restStub.succeed({ data: expected }); flushPromises(); }); From 8d29b170bcc19982bd96f64e742d35922acf4363 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 22 Jan 2015 11:17:51 -0500 Subject: [PATCH 07/43] Extract html string to template --- .../static/js/directives/job-status-graph.js | 318 ++++++++---------- awx/ui/static/partials/job_status_graph.html | 39 +++ .../unit/directives/job-status-graph-test.js | 76 +++++ 3 files changed, 252 insertions(+), 181 deletions(-) create mode 100644 awx/ui/static/partials/job_status_graph.html create mode 100644 awx/ui/tests/unit/directives/job-status-graph-test.js diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index a24df54c9b..529fa01ad7 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -1,201 +1,157 @@ angular.module('GraphDirectives', []) - .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', 'jobStatusGraphData', - function ($rootScope, $compile , $location, $window, Rest, GetBasePath, ProcessErrors, Wait, jobStatusGraphData) { - return function (scope, element, attr) { + .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'jobStatusGraphData', + function ($rootScope, $compile , $location, $window, Rest, Wait, jobStatusGraphData) { + return { + restrict: 'A', + templateUrl: '/static/partials/job_status_graph.html', + link: link + }; - var html, url, job_status_chart, - period="month", - job_type="all"; + function link(scope, element, attr) { - var cleanup = angular.noop; + var html, url, job_status_chart, + period="month", + job_type="all"; - var data; - scope.$watch(attr.data, function(value) { - if (value) { - scope.$emit('graphDataReady', value); - } - }); + var cleanup = angular.noop; - scope.$on('$destroy', cleanup); + var data; + scope.$watch(attr.data, function(value) { + if (value) { + scope.$emit('graphDataReady', value); + } + }); - html = "
\n"; - html += "
Job Status
\n"; // for All Jobs, Past Month + scope.$on('$destroy', cleanup); - html += "
\n"; - html += "
\n"; - html += "Job Type: \n"; - html += "All\n"; - html += " \n"; - - html += "\n"; - html += "
\n"; - - html += "
\n"; //end of filter div - - html += "
\n"; - html += "
\n"; - html += "Period: \n"; - html += "Past Month\n"; - html += " \n"; - - html += "\n"; - html += "
\n"; - html += "
\n"; //end of filter div - - html += "
\n"; // end of row - - html +="
\n"; - html += "
\n"; - html += "
\n"; - - // html += "
\n"; - - cleanup = _.compose( - [ cleanup, - scope.$on('DataReceived:JobStatusGraph', + cleanup = _.compose( + [ cleanup, + scope.$on('DataReceived:JobStatusGraph', function(e, data) { scope.$emit('graphDataReady', data); }) - ]); + ]); - function createGraph(period, jobtype){ - jobStatusGraphData.get(period, jobtype).then(function(data) { - scope.$emit('graphDataReady', data); - }); - } + function createGraph(period, jobtype){ + console.log('createGraph'); + // jobStatusGraphData.get(period, jobtype).then(function(data) { + // scope.$emit('graphDataReady', data); + // }); + } - element.html($compile(html)(scope)); + cleanup = _.compose( + [ cleanup, + angular.element($window).bind('resize', function() { + if($(window).width()<500){ + $('.graph-container').height(300); + } + else{ + var winHeight = $(window).height(), + available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; + $('.graph-container').height(available_height/2); + job_status_chart.update(); + } + }) + ]); - cleanup = _.compose( - [ cleanup, - angular.element($window).bind('resize', function() { - if($(window).width()<500){ - $('.graph-container').height(300); - } - else{ - var winHeight = $(window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container').height(available_height/2); - job_status_chart.update(); - } - }) - ]); + if (scope.removeGraphDataReady) { + scope.removeGraphDataReady(); + } + scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data) { - if (scope.removeGraphDataReady) { - scope.removeGraphDataReady(); - } - scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data, third) { + console.log("building graph", data); + var timeFormat, graphData = [ + { + "color": "#00aa00", + "key": "Successful", + "values": data.jobs.successful + }, + { + "key" : "Failed" , + "color" : "#aa0000", + "values": data.jobs.failed + } + ]; - var timeFormat, graphData = [ - { - "color": "#00aa00", - "key": "Successful", - "values": data.jobs.successful - }, - { - "key" : "Failed" , - "color" : "#aa0000", - "values": data.jobs.failed - } - ]; - - if(period==="day"){ - timeFormat="%H:%M"; - } - else { - timeFormat = '%m/%d'; - } - graphData.map(function(series) { - series.values = series.values.map(function(d) { - return { - x: d[0], - y: d[1] - }; - }); - return series; - }); - - nv.addGraph({ - generate: function() { - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, - job_status_chart = nv.models.lineChart() - .margin({top: 5, right: 75, bottom: 80, left: 85}) //Adjust chart margins to give the x-axis some breathing room. - .x(function(d,i) { return i; }) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .transitionDuration(350) //how fast do you want the lines to transition? - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true) //Show the x-axis - // .width(width) - // .height(height) - ; - - job_status_chart.xAxis - .axisLabel("Time")//.showMaxMin(true) - .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; - }); - - job_status_chart.yAxis //Chart y-axis settings - .axisLabel('Jobs') - .tickFormat(d3.format('.f')); - - d3.select('.job-status-graph svg') - .datum(graphData).transition() - .attr('width', width) - .attr('height', height) - .duration(1000) - .call(job_status_chart) - .style({ - // 'width': width, - // 'height': height, - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - - // when the Period drop down filter is used, create a new graph based on the - d3.selectAll(".n") - .on("click", function() { - period = this.getAttribute("id"); - $('#period-dropdown').replaceWith(""+this.text+"\n"); - - createGraph(period, job_type); - }); - - //On click, update with new data - d3.selectAll(".m") - .on("click", function() { - job_type = this.getAttribute("id"); - $('#type-dropdown').replaceWith(""+this.text+"\n"); - - data - createGraph(period, job_type); - }); - - scope.$emit('WidgetLoaded'); - return job_status_chart; - - }, - - - }); - + if(period==="day"){ + timeFormat="%H:%M"; + } + else { + timeFormat = '%m/%d'; + } + graphData.map(function(series) { + series.values = series.values.map(function(d) { + return { + x: d[0], + y: d[1] + }; }); + return series; + }); + + var job_status_chart = nv.models.lineChart() + .margin({top: 5, right: 75, bottom: 80, left: 85}) //Adjust chart margins to give the x-axis some breathing room. + .x(function(d,i) { return i; }) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .transitionDuration(350) //how fast do you want the lines to transition? + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true) //Show the x-axis + // .width(width) + // .height(height) + ; - }; + var width = $('.graph-container').width(); // nv.utils.windowSize().width/3, + var height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, + + job_status_chart.xAxis + .axisLabel("Time")//.showMaxMin(true) + .tickFormat(function(d) { + var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; + }); + + job_status_chart.yAxis //Chart y-axis settings + .axisLabel('Jobs') + .tickFormat(d3.format('.f')); + + d3.select(element.find('svg')[0]) + .datum(graphData).transition() + .attr('width', width) + .attr('height', height) + .duration(1000) + .call(job_status_chart) + .style({ + // 'width': width, + // 'height': height, + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + // when the Period drop down filter is used, create a new graph based on the + d3.selectAll(element.find(".n")[0]) + .on("click", function() { + period = this.getAttribute("id"); + $('#period-dropdown').replaceWith(""+this.text+"\n"); + + createGraph(period, job_type); + }); + + //On click, update with new data + d3.selectAll(element.find(".m")[0]) + .on("click", function() { + job_type = this.getAttribute("id"); + $('#type-dropdown').replaceWith(""+this.text+"\n"); + + createGraph(period, job_type); + }); + + return job_status_chart; + + }); + } }]); diff --git a/awx/ui/static/partials/job_status_graph.html b/awx/ui/static/partials/job_status_graph.html new file mode 100644 index 0000000000..a463d63f13 --- /dev/null +++ b/awx/ui/static/partials/job_status_graph.html @@ -0,0 +1,39 @@ + + +
+
+
+ diff --git a/awx/ui/tests/unit/directives/job-status-graph-test.js b/awx/ui/tests/unit/directives/job-status-graph-test.js new file mode 100644 index 0000000000..49246063d1 --- /dev/null +++ b/awx/ui/tests/unit/directives/job-status-graph-test.js @@ -0,0 +1,76 @@ +describe('Job Status Graph Directive', function() { + var element, scope, httpBackend; + + beforeEach(module('Tower')); + + beforeEach(module(function($provide) { + $provide.value('LoadBasePaths', angular.noop); + })); + + beforeEach(inject(function($rootScope, $compile, $httpBackend) { + httpBackend = $httpBackend; + $httpBackend.expectGET('/static/js/local_config.js').respond({ + }); + + $httpBackend.whenGET('/static/partials/job_status_graph.html') + .respond("
"); + + // $httpBackend.whenGET('/api/').respond(200, + // {"available_versions": {"v1": "/api/v1/"}, "description": "Ansible Tower REST API", "current_version": "/api/v1/"}); + + scope = $rootScope.$new(); + + element = '
'; + + // Takes jobs grouped by result (successful or failure + // Then looks at each array of arrays, where index 0 is the timestamp & index 1 is the count of jobs with that status + scope.data = + { jobs: + { successful: [[1, 0], [2, 0], [3,0], [4,0], [5,0]], + failed: [[1,0],[2,0],[3,0],[4,0],[5,0]] + } + }; + + element = $compile(element)(scope); + scope.$digest(); + + $httpBackend.flush(); + + })); + + afterEach(function() { + httpBackend.verifyNoOutstandingExpectation(); + httpBackend.verifyNoOutstandingRequest(); + }); + + function filterDataSeries(key, data) { + return data.map(function(datum) { + return datum.values; + })[key]; + } + + it('uses successes & failures from scope', function() { + var chartContainer = d3.select(element.find('svg')[0]); + var lineData = chartContainer.datum(); + + var successfulSeries = filterDataSeries(0, lineData); + var failedSeries = filterDataSeries(1, lineData); + + console.log("test done"); + expect(successfulSeries).to.eql( + [ {x: 1, y: 0, series: 0}, + {x: 2, y: 0, series: 0}, + {x: 3, y: 0, series: 0}, + {x: 4, y: 0, series: 0}, + {x: 5, y: 0, series: 0}]); + + expect(failedSeries).to.eql( + [ {x: 1, y: 0, series: 1}, + {x: 2, y: 0, series: 1}, + {x: 3, y: 0, series: 1}, + {x: 4, y: 0, series: 1}, + {x: 5, y: 0, series: 1}]); + }); + +}); + From 7f291c91b515a9fcaa8cd1a2ec0e27157d08d3ab Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 22 Jan 2015 17:45:56 -0500 Subject: [PATCH 08/43] Fix click handlers for filters --- awx/ui/static/js/directives/job-status-graph.js | 6 ++---- awx/ui/tests/unit/directives/job-status-graph-test.js | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 529fa01ad7..64ecc502d7 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -59,8 +59,6 @@ angular.module('GraphDirectives', []) } scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data) { - console.log("building graph", data); - var timeFormat, graphData = [ { "color": "#00aa00", @@ -133,7 +131,7 @@ angular.module('GraphDirectives', []) }); // when the Period drop down filter is used, create a new graph based on the - d3.selectAll(element.find(".n")[0]) + d3.selectAll(element.find(".n")) .on("click", function() { period = this.getAttribute("id"); $('#period-dropdown').replaceWith(""+this.text+"\n"); @@ -142,7 +140,7 @@ angular.module('GraphDirectives', []) }); //On click, update with new data - d3.selectAll(element.find(".m")[0]) + d3.selectAll(element.find(".m")) .on("click", function() { job_type = this.getAttribute("id"); $('#type-dropdown').replaceWith(""+this.text+"\n"); diff --git a/awx/ui/tests/unit/directives/job-status-graph-test.js b/awx/ui/tests/unit/directives/job-status-graph-test.js index 49246063d1..f890358e00 100644 --- a/awx/ui/tests/unit/directives/job-status-graph-test.js +++ b/awx/ui/tests/unit/directives/job-status-graph-test.js @@ -56,7 +56,6 @@ describe('Job Status Graph Directive', function() { var successfulSeries = filterDataSeries(0, lineData); var failedSeries = filterDataSeries(1, lineData); - console.log("test done"); expect(successfulSeries).to.eql( [ {x: 1, y: 0, series: 0}, {x: 2, y: 0, series: 0}, From 8e092714fbe5bc0bf4ed5f324872039b6e006292 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 22 Jan 2015 17:46:21 -0500 Subject: [PATCH 09/43] Switch to using element instead of attribute --- awx/ui/static/js/directives/job-status-graph.js | 15 +++++---------- awx/ui/static/partials/home.html | 2 +- .../unit/directives/job-status-graph-test.js | 5 +---- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 64ecc502d7..fdf5fe9381 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -1,8 +1,8 @@ angular.module('GraphDirectives', []) .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'jobStatusGraphData', - function ($rootScope, $compile , $location, $window, Rest, Wait, jobStatusGraphData) { + function ($rootScope, $compile , $location, $window, Wait, jobStatusGraphData) { return { - restrict: 'A', + restrict: 'E', templateUrl: '/static/partials/job_status_graph.html', link: link }; @@ -26,17 +26,12 @@ angular.module('GraphDirectives', []) cleanup = _.compose( [ cleanup, - scope.$on('DataReceived:JobStatusGraph', - function(e, data) { - scope.$emit('graphDataReady', data); - }) ]); function createGraph(period, jobtype){ - console.log('createGraph'); - // jobStatusGraphData.get(period, jobtype).then(function(data) { - // scope.$emit('graphDataReady', data); - // }); + jobStatusGraphData.get(period, jobtype).then(function(data) { + scope.$emit('graphDataReady', data); + }); } cleanup = _.compose( diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index 5ea2cc5fe4..e3f218465c 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -12,7 +12,7 @@
-
+
diff --git a/awx/ui/tests/unit/directives/job-status-graph-test.js b/awx/ui/tests/unit/directives/job-status-graph-test.js index f890358e00..09628f8e86 100644 --- a/awx/ui/tests/unit/directives/job-status-graph-test.js +++ b/awx/ui/tests/unit/directives/job-status-graph-test.js @@ -15,12 +15,9 @@ describe('Job Status Graph Directive', function() { $httpBackend.whenGET('/static/partials/job_status_graph.html') .respond("
"); - // $httpBackend.whenGET('/api/').respond(200, - // {"available_versions": {"v1": "/api/v1/"}, "description": "Ansible Tower REST API", "current_version": "/api/v1/"}); - scope = $rootScope.$new(); - element = '
'; + element = ''; // Takes jobs grouped by result (successful or failure // Then looks at each array of arrays, where index 0 is the timestamp & index 1 is the count of jobs with that status From 3db561141e7900b6f48711c77d74c1370cf13a6e Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 22 Jan 2015 17:47:29 -0500 Subject: [PATCH 10/43] Add Debug.log helper for easy logging --- awx/ui/static/js/app.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 73675b9be2..3c6af0934c 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -10,6 +10,14 @@ var urlPrefix, $AnsibleConfig; +window.Debug = {}; +window.Debug.log = function(label, obj) { + if (Debug.enabled) { + console.log(label + ":", obj); + } + return obj; +}; + if ($basePath) { urlPrefix = $basePath; } else { From 42fbcf34543778d069f8b51c9d920c28f656d4b6 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 10:13:24 -0500 Subject: [PATCH 11/43] Implement responsiveness in graph --- .../static/js/directives/job-status-graph.js | 68 ++++++++++++------ awx/ui/static/less/new-dashboard.less | 15 +++- awx/ui/static/partials/job_status_graph.html | 70 +++++++++---------- 3 files changed, 96 insertions(+), 57 deletions(-) diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index fdf5fe9381..18fa71a07e 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -34,24 +34,49 @@ angular.module('GraphDirectives', []) }); } - cleanup = _.compose( - [ cleanup, - angular.element($window).bind('resize', function() { - if($(window).width()<500){ - $('.graph-container').height(300); - } - else{ - var winHeight = $(window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container').height(available_height/2); - job_status_chart.update(); - } - }) - ]); + var w = angular.element($window); + + function adjustGraphSize() { + var parentHeight = element.parent().height(); + var toolbarHeight = element.find('.toolbar').height(); + var container = element.find('svg').parent(); + var margins = job_status_chart.margin(); + + $(container).height(parentHeight - toolbarHeight - margins.bottom); + + var graph = d3.select(element.find('svg')[0]); + var width = parseInt(graph.style('width')) - margins.left - margins.right; + var height = parseInt(graph.style('height')) - margins.top - margins.bottom; + + + job_status_chart.xRange([0, width]); + job_status_chart.yRange([height, 0]); + + job_status_chart.xAxis.ticks(Math.max(width / 75, 2)); + job_status_chart.yAxis.ticks(Math.max(height / 50, 2)); + + if (height < 160) { + graph.select('.y.axis').select('.domain').style('display', 'none'); + graph.select('.y.axis').select('.domain').style('display', 'initial'); + } + + graph.select('.x.axis') + .attr('transform', 'translate(0, ' + height + ')') + .call(job_status_chart.xAxis); + + graph.selectAll('.line') + .attr('d', job_status_chart.lines) + + job_status_chart.update(); + } + + $window.addEventListener('resize', adjustGraphSize); if (scope.removeGraphDataReady) { scope.removeGraphDataReady(); } + + var job_status_chart; scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data) { var timeFormat, graphData = [ @@ -83,11 +108,10 @@ angular.module('GraphDirectives', []) return series; }); - var job_status_chart = nv.models.lineChart() - .margin({top: 5, right: 75, bottom: 80, left: 85}) //Adjust chart margins to give the x-axis some breathing room. + job_status_chart = nv.models.lineChart() + .margin({top: 5, right: 75, bottom: 50, left: 85}) //Adjust chart margins to give the x-axis some breathing room. .x(function(d,i) { return i; }) .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .transitionDuration(350) //how fast do you want the lines to transition? .showLegend(true) //Show the legend, allowing users to turn on/off line series. .showYAxis(true) //Show the y-axis .showXAxis(true) //Show the x-axis @@ -111,10 +135,10 @@ angular.module('GraphDirectives', []) .tickFormat(d3.format('.f')); d3.select(element.find('svg')[0]) - .datum(graphData).transition() - .attr('width', width) - .attr('height', height) - .duration(1000) + .datum(graphData) + // .attr('width', width) + // .attr('height', height) + // .transition().duration(100) .call(job_status_chart) .style({ // 'width': width, @@ -143,6 +167,8 @@ angular.module('GraphDirectives', []) createGraph(period, job_type); }); + adjustGraphSize(); + return job_status_chart; }); diff --git a/awx/ui/static/less/new-dashboard.less b/awx/ui/static/less/new-dashboard.less index 2560cca8c1..4a6e2c58cc 100644 --- a/awx/ui/static/less/new-dashboard.less +++ b/awx/ui/static/less/new-dashboard.less @@ -8,6 +8,19 @@ */ +.graph-wrapper { + width: 100%; +} + +.graph { + background-color: white; + // @include transition(width 2s ease-in-out, height 2s ease-in-out); + position: relative; + text-align: center; + width: 100%; + height: 100%; + margin: 0 auto; +} .job-status-graph, .host-count-graph{ font: 10px sans-serif; @@ -99,4 +112,4 @@ due to the login screen showing on top of the dashboard, we're hiding the border .m, .n{ cursor:pointer; -} \ No newline at end of file +} diff --git a/awx/ui/static/partials/job_status_graph.html b/awx/ui/static/partials/job_status_graph.html index a463d63f13..ec70847709 100644 --- a/awx/ui/static/partials/job_status_graph.html +++ b/awx/ui/static/partials/job_status_graph.html @@ -1,39 +1,39 @@ -
-
Job Status
+
From 1c4b8c06766bab16ee108da9ffd9f369790ae61f Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 10:57:20 -0500 Subject: [PATCH 12/43] Move HostPieChart into directive --- awx/ui/static/js/app.js | 3 +- awx/ui/static/js/controllers/Home.js | 15 +- .../static/js/directives/dashboard-graphs.js | 1 + .../static/js/directives/host-status-graph.js | 104 ++++++++++++++ .../static/js/directives/job-status-graph.js | 3 +- awx/ui/static/js/widgets/HostPieChart.js | 128 ------------------ awx/ui/static/partials/home.html | 12 +- awx/ui/static/partials/host_status_graph.html | 8 ++ awx/ui/templates/ui/index.html | 2 + 9 files changed, 132 insertions(+), 144 deletions(-) create mode 100644 awx/ui/static/js/directives/dashboard-graphs.js create mode 100644 awx/ui/static/js/directives/host-status-graph.js delete mode 100644 awx/ui/static/js/widgets/HostPieChart.js create mode 100644 awx/ui/static/partials/host_status_graph.html diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 3c6af0934c..a0e071a93a 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -33,7 +33,7 @@ angular.module('Tower', [ 'ngCookies', 'RestServices', 'DataServices', - 'GraphDirectives', + 'DashboardGraphs', 'AuthService', 'Utilities', 'LicenseHelper', @@ -95,7 +95,6 @@ angular.module('Tower', [ 'SelectionHelper', 'HostGroupsFormDefinition', 'DashboardCountsWidget', - 'HostPieChartWidget', 'HostGraphWidget', 'DashboardJobsWidget', 'PortalJobsWidget', diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index 5a248c0a86..f46a59cadd 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -25,7 +25,7 @@ * Host count graph should only be loaded if the user is a super user * */ -function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, HostGraph, HostPieChart, DashboardJobs, +function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, HostGraph, DashboardJobs, ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, graphData){ ClearScope('home'); @@ -84,9 +84,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, loadedCount++; if (loadedCount === waitCount) { $(window).resize(_.debounce(function() { - $scope.$emit('ResizeJobGraph'); $scope.$emit('ResizeHostGraph'); - $scope.$emit('ResizeHostPieGraph'); Wait('stop'); }, 500)); $(window).resize(); @@ -97,6 +95,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, $scope.removeDashboardReady(); } $scope.removeDashboardReady = $scope.$on('dashboardReady', function (e, data) { + nv.dev=false; @@ -135,11 +134,6 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, target: 'dash-jobs-list', dashboard: data }); - HostPieChart({ - scope: $scope, - target: 'dash-host-status-graph', - dashboard: data - }); }); @@ -172,7 +166,8 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, Rest.setUrl(GetBasePath('dashboard')); Rest.get() .success(function (data) { - $scope.$emit('dashboardReady', data); + $scope.dashboardData = data; + $scope.$emit('dashboardReady', data); }) .error(function (data, status) { ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); @@ -183,7 +178,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, } -Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'HostGraph', 'HostPieChart', 'DashboardJobs', +Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'HostGraph', 'DashboardJobs', 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button', 'graphData' ]; diff --git a/awx/ui/static/js/directives/dashboard-graphs.js b/awx/ui/static/js/directives/dashboard-graphs.js new file mode 100644 index 0000000000..ed86866e80 --- /dev/null +++ b/awx/ui/static/js/directives/dashboard-graphs.js @@ -0,0 +1 @@ +angular.module('DashboardGraphs', []); diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js new file mode 100644 index 0000000000..b8c4385617 --- /dev/null +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -0,0 +1,104 @@ +angular.module('DashboardGraphs') + .directive('hostStatusGraph', ['$compile', '$window', + function ($compile, $window) { + return { + restrict: 'E', + link: link, + templateUrl: '/static/partials/host_status_graph.html' + }; + + function link(scope, element, attr) { + var html, canvas, context, winHeight, available_height, host_pie_chart; + + scope.$watch(attr.data, function(data) { + if (data && data.hosts) { + buildGraph(data); + } + }); + + function adjustGraphSize() { + if($($window).width()<500){ + $('.graph-container').height(300); + } + else{ + var winHeight = $($window).height(), + available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; + $('.graph-container').height(available_height/2); + if(host_pie_chart){ + host_pie_chart.update(); + } + } + } + + $window.addEventListener('resize', adjustGraphSize); + + element.on('$destroy', function() { + $window.removeEventListener('resize', adjustGraphSize); + }); + + function buildGraph(data) { + if(data.hosts.total+data.hosts.failed>0){ + data = [ + { + "label": "Successful", + "color": "#00aa00", + "value" : data.hosts.total + } , + { + "label": "Failed", + "color" : "#aa0000", + "value" : data.hosts.failed + } + ]; + + nv.addGraph(function() { + var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, + height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, + host_pie_chart = nv.models.pieChart() + .margin({top: 5, right: 75, bottom: 40, left: 85}) + .x(function(d) { return d.label; }) + .y(function(d) { return d.value; }) + .showLabels(true) + .labelThreshold(0.01) + .tooltipContent(function(x, y) { + return ''+x+''+ '

' + Math.floor(y.replace(',','')) + ' Hosts ' + '

'; + }) + .color(['#00aa00', '#aa0000']); + + host_pie_chart.pie.pieLabelsOutside(true).labelType("percent"); + + d3.select(".host-pie-chart svg") + .datum(data) + .attr('width', width) + .attr('height', height) + .transition().duration(350) + .call(host_pie_chart) + .style({ + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + // nv.utils.windowResize(host_pie_chart.update); + scope.$emit('WidgetLoaded'); + return host_pie_chart; + }); + } + else{ + winHeight = $($window).height(); + available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; + $('.graph-container:eq(1)').height(available_height/2); + $('.host-pie-chart svg').replaceWith(''); + + canvas = document.getElementById("circlecanvas"); + context = canvas.getContext("2d"); + context.arc(55, 55, 50, 0, Math.PI * 2, false); + context.lineWidth = 1; + context.strokeStyle = '#1778c3'; + context.stroke(); + context.font = "12px Open Sans"; + context.fillText("No Host data",18,55); + } + } + } + }]); diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 18fa71a07e..a2c963544e 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -1,4 +1,4 @@ -angular.module('GraphDirectives', []) +angular.module('DashboardGraphs') .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'jobStatusGraphData', function ($rootScope, $compile , $location, $window, Wait, jobStatusGraphData) { return { @@ -8,7 +8,6 @@ angular.module('GraphDirectives', []) }; function link(scope, element, attr) { - var html, url, job_status_chart, period="month", job_type="all"; diff --git a/awx/ui/static/js/widgets/HostPieChart.js b/awx/ui/static/js/widgets/HostPieChart.js deleted file mode 100644 index 98bcf8ef21..0000000000 --- a/awx/ui/static/js/widgets/HostPieChart.js +++ /dev/null @@ -1,128 +0,0 @@ -/********************************************* - * Copyright (c) 2014 AnsibleWorks, Inc. - */ - /** - * @ngdoc function - * @name widgets.function:HostPieChart - * @description - * HostPieChart.js - * - * file for the host status pie chart - * - */ - -'use strict'; - -angular.module('HostPieChartWidget', ['RestServices', 'Utilities']) - .factory('HostPieChart', ['$rootScope', '$compile', - //'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', - function ($rootScope, $compile){ - //, Rest, GetBasePath, ProcessErrors) { - return function (params) { - - var scope = params.scope, - target = params.target, - dashboard = params.dashboard, - html, element, data, - canvas, context, winHeight, available_height, host_pie_chart; - - // html = "
\n"; - - html ="
\n"; - html += "
Host Status
\n"; - html += "
\n"; - - html +="
\n"; - html += "
\n"; - html += "
\n"; - - // html += "
\n"; - - element = angular.element(document.getElementById(target)); - element.html(html); - $compile(element)(scope); - - if (scope.removeResizeHostPieGraph) { - scope.removeResizeHostPieGraph(); - } - scope.removeResizeHostPieGraph= scope.$on('ResizeHostPieGraph', function () { - if($(window).width()<500){ - $('.graph-container').height(300); - } - else{ - var winHeight = $(window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container').height(available_height/2); - if(host_pie_chart){ - host_pie_chart.update(); - } - } - }); - - if(dashboard.hosts.total+dashboard.hosts.failed>0){ - data = [ - { - "label": "Successful", - "color": "#00aa00", - "value" : dashboard.hosts.total - } , - { - "label": "Failed", - "color" : "#aa0000", - "value" : dashboard.hosts.failed - } - ]; - - nv.addGraph(function() { - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, - host_pie_chart = nv.models.pieChart() - .margin({top: 5, right: 75, bottom: 40, left: 85}) - .x(function(d) { return d.label; }) - .y(function(d) { return d.value; }) - .showLabels(true) - .labelThreshold(0.01) - .tooltipContent(function(x, y) { - return ''+x+''+ '

' + Math.floor(y.replace(',','')) + ' Hosts ' + '

'; - }) - .color(['#00aa00', '#aa0000']); - - host_pie_chart.pie.pieLabelsOutside(true).labelType("percent"); - - d3.select(".host-pie-chart svg") - .datum(data) - .attr('width', width) - .attr('height', height) - .transition().duration(350) - .call(host_pie_chart) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - // nv.utils.windowResize(host_pie_chart.update); - scope.$emit('WidgetLoaded'); - return host_pie_chart; - }); - } - else{ - winHeight = $(window).height(); - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container:eq(1)').height(available_height/2); - $('.host-pie-chart svg').replaceWith(''); - - canvas = document.getElementById("circlecanvas"); - context = canvas.getContext("2d"); - context.arc(55, 55, 50, 0, Math.PI * 2, false); - context.lineWidth = 1; - context.strokeStyle = '#1778c3'; - context.stroke(); - context.font = "12px Open Sans"; - context.fillText("No Host data",18,55); - - scope.$emit('WidgetLoaded'); - } - }; - } - ]); diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index e3f218465c..ceb9ede175 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -12,8 +12,16 @@
-
-
+
+
+ +
+
+
+
+ +
+
diff --git a/awx/ui/static/partials/host_status_graph.html b/awx/ui/static/partials/host_status_graph.html new file mode 100644 index 0000000000..03fe929982 --- /dev/null +++ b/awx/ui/static/partials/host_status_graph.html @@ -0,0 +1,8 @@ +
+
Host Status
+
+ +
+
+
+ diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index d9314e9e48..709b5fa822 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -82,7 +82,9 @@ + + From 116e7b211828f76d37f11a493d382582cdd0bde2 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 11:11:56 -0500 Subject: [PATCH 13/43] Take toolbar height into account on job status graph --- awx/ui/static/js/directives/job-status-graph.js | 6 ++++-- awx/ui/static/partials/job_status_graph.html | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index a2c963544e..1d299c1eba 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -36,12 +36,14 @@ angular.module('DashboardGraphs') var w = angular.element($window); function adjustGraphSize() { - var parentHeight = element.parent().height(); + var parentHeight = element.parent().parent().height(); var toolbarHeight = element.find('.toolbar').height(); var container = element.find('svg').parent(); var margins = job_status_chart.margin(); - $(container).height(parentHeight - toolbarHeight - margins.bottom); + var newHeight = parentHeight - toolbarHeight - margins.bottom; + + $(container).height(newHeight); var graph = d3.select(element.find('svg')[0]); var width = parseInt(graph.style('width')) - margins.left - margins.right; diff --git a/awx/ui/static/partials/job_status_graph.html b/awx/ui/static/partials/job_status_graph.html index ec70847709..c57c242b30 100644 --- a/awx/ui/static/partials/job_status_graph.html +++ b/awx/ui/static/partials/job_status_graph.html @@ -1,5 +1,5 @@
-
+
Job Status
From f98e9ce0c7dd84e1230b3176a8678547787a408c Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 11:36:45 -0500 Subject: [PATCH 14/43] Update size of host pie chart on window resize --- .../static/js/directives/host-status-graph.js | 82 +++++++++---------- awx/ui/static/partials/host_status_graph.html | 16 ++-- 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js index b8c4385617..4f1704194a 100644 --- a/awx/ui/static/js/directives/host-status-graph.js +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -17,17 +17,16 @@ angular.module('DashboardGraphs') }); function adjustGraphSize() { - if($($window).width()<500){ - $('.graph-container').height(300); - } - else{ - var winHeight = $($window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container').height(available_height/2); - if(host_pie_chart){ - host_pie_chart.update(); - } - } + var parentHeight = element.parent().parent().height(); + var toolbarHeight = element.find('.toolbar').height(); + var container = element.find('svg').parent(); + var margins = host_pie_chart.margin(); + + var newHeight = parentHeight - toolbarHeight - margins.bottom; + + $(container).height(newHeight); + + host_pie_chart.update(); } $window.addEventListener('resize', adjustGraphSize); @@ -51,44 +50,42 @@ angular.module('DashboardGraphs') } ]; - nv.addGraph(function() { - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, - host_pie_chart = nv.models.pieChart() - .margin({top: 5, right: 75, bottom: 40, left: 85}) - .x(function(d) { return d.label; }) - .y(function(d) { return d.value; }) - .showLabels(true) - .labelThreshold(0.01) - .tooltipContent(function(x, y) { - return ''+x+''+ '

' + Math.floor(y.replace(',','')) + ' Hosts ' + '

'; - }) - .color(['#00aa00', '#aa0000']); + var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, + height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, + host_pie_chart = nv.models.pieChart() + .margin({top: 5, right: 75, bottom: 25, left: 85}) + .x(function(d) { return d.label; }) + .y(function(d) { return d.value; }) + .showLabels(true) + .labelThreshold(0.01) + .tooltipContent(function(x, y) { + return ''+x+''+ '

' + Math.floor(y.replace(',','')) + ' Hosts ' + '

'; + }) + .color(['#00aa00', '#aa0000']); - host_pie_chart.pie.pieLabelsOutside(true).labelType("percent"); + host_pie_chart.pie.pieLabelsOutside(true).labelType("percent"); - d3.select(".host-pie-chart svg") - .datum(data) - .attr('width', width) - .attr('height', height) - .transition().duration(350) - .call(host_pie_chart) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - // nv.utils.windowResize(host_pie_chart.update); - scope.$emit('WidgetLoaded'); - return host_pie_chart; + d3.select(element.find('svg')[0]) + .datum(data) + // .attr('width', width) + // .attr('height', height) + .transition().duration(350) + .call(host_pie_chart) + .style({ + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" }); + // nv.utils.windowResize(host_pie_chart.update); + adjustGraphSize(); + return host_pie_chart; } else{ winHeight = $($window).height(); available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container:eq(1)').height(available_height/2); - $('.host-pie-chart svg').replaceWith(''); + element.find('.graph-wrapper').height(available_height/2); + element.find('svg').replaceWith(''); canvas = document.getElementById("circlecanvas"); context = canvas.getContext("2d"); @@ -99,6 +96,7 @@ angular.module('DashboardGraphs') context.font = "12px Open Sans"; context.fillText("No Host data",18,55); } + } } }]); diff --git a/awx/ui/static/partials/host_status_graph.html b/awx/ui/static/partials/host_status_graph.html index 03fe929982..57193bf023 100644 --- a/awx/ui/static/partials/host_status_graph.html +++ b/awx/ui/static/partials/host_status_graph.html @@ -1,8 +1,12 @@ -
-
Host Status
-
- -
-
+
+
+
+ Host Status +
+
+ +
+ +
From 408d9bf1361695602745794b3abaff680bf2bde9 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 12:02:03 -0500 Subject: [PATCH 15/43] Move host count graph into directive --- awx/ui/static/js/app.js | 1 - awx/ui/static/js/controllers/Home.js | 15 +- .../static/js/directives/host-count-graph.js | 148 ++++++++++++++++++ .../static/js/directives/job-status-graph.js | 45 +----- .../static/js/services/adjust-graph-size.js | 38 +++++ awx/ui/static/partials/home.html | 6 +- awx/ui/static/partials/host_count_graph.html | 12 ++ awx/ui/templates/ui/index.html | 1 + 8 files changed, 213 insertions(+), 53 deletions(-) create mode 100644 awx/ui/static/js/directives/host-count-graph.js create mode 100644 awx/ui/static/js/services/adjust-graph-size.js create mode 100644 awx/ui/static/partials/host_count_graph.html diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index a0e071a93a..570ec5f70c 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -95,7 +95,6 @@ angular.module('Tower', [ 'SelectionHelper', 'HostGroupsFormDefinition', 'DashboardCountsWidget', - 'HostGraphWidget', 'DashboardJobsWidget', 'PortalJobsWidget', 'StreamWidget', diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index f46a59cadd..a931113e1c 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -25,7 +25,7 @@ * Host count graph should only be loaded if the user is a super user * */ -function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, HostGraph, DashboardJobs, +function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, DashboardJobs, ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, graphData){ ClearScope('home'); @@ -84,7 +84,6 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, loadedCount++; if (loadedCount === waitCount) { $(window).resize(_.debounce(function() { - $scope.$emit('ResizeHostGraph'); Wait('stop'); }, 500)); $(window).resize(); @@ -118,15 +117,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, $scope.graphData = graphData; - if ($rootScope.user_is_superuser === true) { - waitCount = 5; - HostGraph({ - scope: $scope, - target: 'dash-host-count-graph', - dashboard: data - }); - } - else{ + if ($rootScope.user_is_superuser !== true) { $('#dash-host-count-graph').remove(); //replaceWith("
"); } DashboardJobs({ @@ -178,7 +169,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, } -Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'HostGraph', 'DashboardJobs', +Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'DashboardJobs', 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button', 'graphData' ]; diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js new file mode 100644 index 0000000000..008b8aa6b8 --- /dev/null +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -0,0 +1,148 @@ +angular.module('DashboardGraphs'). + directive('hostCountGraph', ['GetBasePath', 'Rest', function(getBasePath, Rest) { + + return { + restrict: 'E', + templateUrl: '/static/partials/host_count_graph.html', + link: link + }; + + function link(scope, element, attrs) { + var url, license, license_graph; + + url = getBasePath('config'); + + if (scope.removeResizeHostGraph) { + scope.removeResizeHostGraph(); + } + scope.removeResizeHostGraph= scope.$on('ResizeHostGraph', function () { + if($(window).width()<500){ + $('.graph-container').height(300); + } + else{ + var winHeight = $(window).height(), + available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; + $('.graph-container').height(available_height/2); + license_graph.update(); + } + }); + + Rest.setUrl(url); + Rest.get() + .success(function (data){ + license = data.license_info.instance_count; + scope.$emit('licenseCountReady', license); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get: ' + url + ' GET returned: ' + status }); + }); + + if (scope.removeLicenseCountReady) { + scope.removeLicenseCountReady(); + } + scope.removeLicenseCountReady = scope.$on('licenseCountReady', function (e, license) { + url = getBasePath('dashboard')+'graphs/inventory/'; + Rest.setUrl(url); + Rest.get() + .success(function (data) { + scope.$emit('hostDataReady', data, license); + }) + .error(function (data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get: ' + url + ' GET returned: ' + status }); + }); + + }); + + if (scope.removeHostDataReady) { + scope.removeHostDataReady(); + } + scope.removeHostDataReady = scope.$on('hostDataReady', function (e, data, license) { + + //url = getBasePath('dashboard')+'graphs/'; + var graphData = [ + { + "key" : "Hosts" , + "color" : "#1778c3", + "values": data.hosts + }, + { + "key" : "License" , + "color" : "#171717", + "values": data.hosts + } + ]; + + graphData.map(function(series) { + if(series.key==="Hosts"){ + series.values = series.values.map(function(d) { + return { + x: d[0], + y: d[1] + }; + }); + } + if(series.key==="License"){ + series.values = series.values.map(function(d) { + return { + x: d[0], + y: license + }; + }); + + } + return series; + + }); + + nv.addGraph({ + generate: function() { + var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, + height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5, + license_graph = nv.models.lineChart() + .margin({top: 15, right: 75, bottom: 40, left: 85}) + .x(function(d,i) { return i ;}) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .transitionDuration(350) //how fast do you want the lines to transition? + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true) //Show the x-axis + ; + + license_graph.xAxis + .axisLabel("Time") + .tickFormat(function(d) { + var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : ''; + }); + + license_graph.yAxis //Chart y-axis settings + .axisLabel('Hosts') + .tickFormat(d3.format('.f')); + + d3.select(element.find('svg')[0]) + .datum(graphData).transition() + .attr('width', width) + .attr('height', height) + .duration(500) + .call(license_graph) + .style({ + // 'width': width, + // 'height': height, + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + + // nv.utils.windowResize(license_graph.update); + scope.$emit('WidgetLoaded'); + return license_graph; + + } + }); + }); + } +}]); diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 1d299c1eba..9d9f5d1849 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -1,6 +1,6 @@ angular.module('DashboardGraphs') - .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'jobStatusGraphData', - function ($rootScope, $compile , $location, $window, Wait, jobStatusGraphData) { + .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'adjustGraphSize', 'jobStatusGraphData', + function ($rootScope, $compile , $location, $window, Wait, adjustGraphSize, jobStatusGraphData) { return { restrict: 'E', templateUrl: '/static/partials/job_status_graph.html', @@ -35,43 +35,10 @@ angular.module('DashboardGraphs') var w = angular.element($window); - function adjustGraphSize() { - var parentHeight = element.parent().parent().height(); - var toolbarHeight = element.find('.toolbar').height(); - var container = element.find('svg').parent(); - var margins = job_status_chart.margin(); - var newHeight = parentHeight - toolbarHeight - margins.bottom; - - $(container).height(newHeight); - - var graph = d3.select(element.find('svg')[0]); - var width = parseInt(graph.style('width')) - margins.left - margins.right; - var height = parseInt(graph.style('height')) - margins.top - margins.bottom; - - - job_status_chart.xRange([0, width]); - job_status_chart.yRange([height, 0]); - - job_status_chart.xAxis.ticks(Math.max(width / 75, 2)); - job_status_chart.yAxis.ticks(Math.max(height / 50, 2)); - - if (height < 160) { - graph.select('.y.axis').select('.domain').style('display', 'none'); - graph.select('.y.axis').select('.domain').style('display', 'initial'); - } - - graph.select('.x.axis') - .attr('transform', 'translate(0, ' + height + ')') - .call(job_status_chart.xAxis); - - graph.selectAll('.line') - .attr('d', job_status_chart.lines) - - job_status_chart.update(); - } - - $window.addEventListener('resize', adjustGraphSize); + $window.addEventListener('resize', function() { + adjustGraphSize(job_status_chart, element); + }); if (scope.removeGraphDataReady) { scope.removeGraphDataReady(); @@ -168,7 +135,7 @@ angular.module('DashboardGraphs') createGraph(period, job_type); }); - adjustGraphSize(); + adjustGraphSize(job_status_chart, element); return job_status_chart; diff --git a/awx/ui/static/js/services/adjust-graph-size.js b/awx/ui/static/js/services/adjust-graph-size.js new file mode 100644 index 0000000000..f784cd4cda --- /dev/null +++ b/awx/ui/static/js/services/adjust-graph-size.js @@ -0,0 +1,38 @@ +angular.module('DashboardGraphs'). + factory('adjustGraphSize', function() { + return function adjustGraphSize(chartModel, element) { + var parentHeight = element.parent().parent().height(); + var toolbarHeight = element.find('.toolbar').height(); + var container = element.find('svg').parent(); + var margins = chartModel.margin(); + + var newHeight = parentHeight - toolbarHeight - margins.bottom; + + $(container).height(newHeight); + + var graph = d3.select(element.find('svg')[0]); + var width = parseInt(graph.style('width')) - margins.left - margins.right; + var height = parseInt(graph.style('height')) - margins.top - margins.bottom; + + + chartModel.xRange([0, width]); + chartModel.yRange([height, 0]); + + chartModel.xAxis.ticks(Math.max(width / 75, 2)); + chartModel.yAxis.ticks(Math.max(height / 50, 2)); + + if (height < 160) { + graph.select('.y.axis').select('.domain').style('display', 'none'); + graph.select('.y.axis').select('.domain').style('display', 'initial'); + } + + graph.select('.x.axis') + .attr('transform', 'translate(0, ' + height + ')') + .call(chartModel.xAxis); + + graph.selectAll('.line') + .attr('d', chartModel.lines) + + chartModel.update(); + } +}); diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index ceb9ede175..a9155bc49d 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -25,7 +25,11 @@
-
+
+
+ +
+
diff --git a/awx/ui/static/partials/host_count_graph.html b/awx/ui/static/partials/host_count_graph.html new file mode 100644 index 0000000000..3b154ab50a --- /dev/null +++ b/awx/ui/static/partials/host_count_graph.html @@ -0,0 +1,12 @@ +
+
+
+ Host Count +
+
+ +
+ +
+
+ diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 709b5fa822..60abfeef45 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -83,6 +83,7 @@ + From ad5c12fa49244f513cd2c33e8a8a94236a961241 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 12:08:52 -0500 Subject: [PATCH 16/43] Use responsive helper in host count graph --- .../static/js/directives/host-count-graph.js | 106 ++++++++---------- 1 file changed, 47 insertions(+), 59 deletions(-) diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index 008b8aa6b8..599890eb74 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -1,5 +1,5 @@ angular.module('DashboardGraphs'). - directive('hostCountGraph', ['GetBasePath', 'Rest', function(getBasePath, Rest) { + directive('hostCountGraph', ['GetBasePath', 'Rest', 'adjustGraphSize', '$window', function(getBasePath, Rest, adjustGraphSize, $window) { return { restrict: 'E', @@ -12,19 +12,8 @@ angular.module('DashboardGraphs'). url = getBasePath('config'); - if (scope.removeResizeHostGraph) { - scope.removeResizeHostGraph(); - } - scope.removeResizeHostGraph= scope.$on('ResizeHostGraph', function () { - if($(window).width()<500){ - $('.graph-container').height(300); - } - else{ - var winHeight = $(window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container').height(available_height/2); - license_graph.update(); - } + $window.addEventListener('resize', function() { + adjustGraphSize(license_graph, element); }); Rest.setUrl(url); @@ -96,53 +85,52 @@ angular.module('DashboardGraphs'). }); - nv.addGraph({ - generate: function() { - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5, - license_graph = nv.models.lineChart() - .margin({top: 15, right: 75, bottom: 40, left: 85}) - .x(function(d,i) { return i ;}) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .transitionDuration(350) //how fast do you want the lines to transition? - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true) //Show the x-axis - ; + var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, + height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5, + license_graph = nv.models.lineChart() + .margin({top: 15, right: 75, bottom: 40, left: 85}) + .x(function(d,i) { return i ;}) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .transitionDuration(350) //how fast do you want the lines to transition? + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true) //Show the x-axis + ; - license_graph.xAxis - .axisLabel("Time") - .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : ''; - }); - - license_graph.yAxis //Chart y-axis settings - .axisLabel('Hosts') - .tickFormat(d3.format('.f')); - - d3.select(element.find('svg')[0]) - .datum(graphData).transition() - .attr('width', width) - .attr('height', height) - .duration(500) - .call(license_graph) - .style({ - // 'width': width, - // 'height': height, - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - - - // nv.utils.windowResize(license_graph.update); - scope.$emit('WidgetLoaded'); - return license_graph; - - } + license_graph.xAxis + .axisLabel("Time") + .tickFormat(function(d) { + var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : ''; }); + + license_graph.yAxis //Chart y-axis settings + .axisLabel('Hosts') + .tickFormat(d3.format('.f')); + + d3.select(element.find('svg')[0]) + .datum(graphData).transition() + .attr('width', width) + .attr('height', height) + .duration(500) + .call(license_graph) + .style({ + // 'width': width, + // 'height': height, + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + + // nv.utils.windowResize(license_graph.update); + scope.$emit('WidgetLoaded'); + + adjustGraphSize(license_graph, element); + + return license_graph; + }); } }]); From 7ddb695afc5829043b10a2c3a73c948bd42eafef Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 13:08:44 -0500 Subject: [PATCH 17/43] Remove HostGraph widget --- awx/ui/static/js/widgets/HostGraph.js | 180 -------------------------- 1 file changed, 180 deletions(-) delete mode 100644 awx/ui/static/js/widgets/HostGraph.js diff --git a/awx/ui/static/js/widgets/HostGraph.js b/awx/ui/static/js/widgets/HostGraph.js deleted file mode 100644 index 981268d788..0000000000 --- a/awx/ui/static/js/widgets/HostGraph.js +++ /dev/null @@ -1,180 +0,0 @@ -/********************************************* - * Copyright (c) 2014 AnsibleWorks, Inc. - */ - /** - * @ngdoc function - * @name widgets.function:HostGraph - * @description - * - */ - - -'use strict'; - -angular.module('HostGraphWidget', ['RestServices', 'Utilities']) - .factory('HostGraph', ['$rootScope', '$compile', '$location', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', - function ($rootScope, $compile, $location, Rest, GetBasePath, ProcessErrors) { - return function (params) { - - var scope = params.scope, - target = params.target, - html, element, url, license, license_graph; - - - // html = "
\n"; - html ="
\n"; - html += "
Host Count
\n"; - html += "
\n"; - html +="
\n"; - html += "
\n"; - - // html += "
\n"; - - - - element = angular.element(document.getElementById(target)); - element.html(html); - $compile(element)(scope); - - url = GetBasePath('config'); - - if (scope.removeResizeHostGraph) { - scope.removeResizeHostGraph(); - } - scope.removeResizeHostGraph= scope.$on('ResizeHostGraph', function () { - if($(window).width()<500){ - $('.graph-container').height(300); - } - else{ - var winHeight = $(window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container').height(available_height/2); - license_graph.update(); - } - }); - - Rest.setUrl(url); - Rest.get() - .success(function (data){ - license = data.license_info.instance_count; - scope.$emit('licenseCountReady', license); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get: ' + url + ' GET returned: ' + status }); - }); - - if (scope.removeLicenseCountReady) { - scope.removeLicenseCountReady(); - } - scope.removeLicenseCountReady = scope.$on('licenseCountReady', function (e, license) { - url = GetBasePath('dashboard')+'graphs/inventory/'; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - scope.$emit('hostDataReady', data, license); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get: ' + url + ' GET returned: ' + status }); - }); - - }); - - if (scope.removeHostDataReady) { - scope.removeHostDataReady(); - } - scope.removeHostDataReady = scope.$on('hostDataReady', function (e, data, license) { - - //url = GetBasePath('dashboard')+'graphs/'; - var graphData = [ - { - "key" : "Hosts" , - "color" : "#1778c3", - "values": data.hosts - }, - { - "key" : "License" , - "color" : "#171717", - "values": data.hosts - } - ]; - - graphData.map(function(series) { - if(series.key==="Hosts"){ - series.values = series.values.map(function(d) { - return { - x: d[0], - y: d[1] - }; - }); - } - if(series.key==="License"){ - series.values = series.values.map(function(d) { - return { - x: d[0], - y: license - }; - }); - - } - return series; - - }); - - nv.addGraph({ - generate: function() { - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5, - license_graph = nv.models.lineChart() - .margin({top: 15, right: 75, bottom: 40, left: 85}) - .x(function(d,i) { return i ;}) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .transitionDuration(350) //how fast do you want the lines to transition? - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true) //Show the x-axis - ; - - license_graph.xAxis - .axisLabel("Time") - .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : ''; - }); - - license_graph.yAxis //Chart y-axis settings - .axisLabel('Hosts') - .tickFormat(d3.format('.f')); - - d3.select('.host-count-graph svg') - .datum(graphData).transition() - .attr('width', width) - .attr('height', height) - .duration(500) - .call(license_graph) - .style({ - // 'width': width, - // 'height': height, - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - - - // nv.utils.windowResize(license_graph.update); - scope.$emit('WidgetLoaded'); - return license_graph; - - }, - - }); - //}); - }); - - - - }; - } - ]); \ No newline at end of file From 6046419e2d6f00f951dc0748e7e15facbd43f17f Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 13:08:55 -0500 Subject: [PATCH 18/43] Kill eager unbind on window resize event This severly breaks any window resize bindings in directives. That is double plus bad. I made the executive decision to remove them because they exist for cleanup that should be done already, assuming we are following good practices. We aren't yet, so it _could_ cause some problems, but not likely. I've already pinged @jlaska to keep an eye on resizing behavior & memory usage in his testing. --- awx/ui/static/lib/ansible/Utilities.js | 3 +-- awx/ui/static/lib/ansible/form-generator.js | 4 +--- awx/ui/static/lib/ansible/list-generator.js | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/awx/ui/static/lib/ansible/Utilities.js b/awx/ui/static/lib/ansible/Utilities.js index 7af00374ba..4d8765aa35 100644 --- a/awx/ui/static/lib/ansible/Utilities.js +++ b/awx/ui/static/lib/ansible/Utilities.js @@ -49,7 +49,6 @@ angular.module('Utilities', ['RestServices', 'Utilities']) } catch (e) { // ignore } - $(window).unbind('resize'); }; }]) @@ -859,4 +858,4 @@ angular.module('Utilities', ['RestServices', 'Utilities']) }; } -]); \ No newline at end of file +]); diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js index 7089ec804f..66f164a385 100644 --- a/awx/ui/static/lib/ansible/form-generator.js +++ b/awx/ui/static/lib/ansible/form-generator.js @@ -223,8 +223,6 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator $(this).remove(); }); - $(window).unbind('resize'); - // Prepend an asterisk to required field label $('.form-control[required], input[type="radio"][required]').each(function () { var label, span; @@ -1656,4 +1654,4 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'Utilities', 'ListGenerator } }; } -]); \ No newline at end of file +]); diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index 5dafeec78e..e046d461d7 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -186,7 +186,6 @@ angular.module('ListGenerator', ['GeneratorHelpers']) // remove lingering popover
. Seems to be a bug in TB3 RC1 $(this).remove(); }); - $(window).unbind('resize'); try { $('#help-modal').empty().dialog('destroy'); @@ -582,4 +581,4 @@ angular.module('ListGenerator', ['GeneratorHelpers']) return html; } }; - }]); \ No newline at end of file + }]); From 64484c405445b2ac685468cc450023c90d640a33 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Mon, 26 Jan 2015 13:58:22 -0500 Subject: [PATCH 19/43] Use jquery for resize binding --- awx/ui/static/js/controllers/Home.js | 16 +++++++++------- awx/ui/static/js/directives/host-count-graph.js | 8 +++++++- awx/ui/static/js/directives/host-status-graph.js | 4 ++-- awx/ui/static/js/directives/job-status-graph.js | 6 +++++- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index a931113e1c..b2164f037b 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -26,7 +26,7 @@ * */ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, DashboardCounts, DashboardJobs, - ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, graphData){ + ClearScope, Stream, Rest, GetBasePath, ProcessErrors, Button, $window, graphData){ ClearScope('home'); @@ -64,7 +64,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, e.html(html); $compile(e)($scope); - waitCount = 4; + waitCount = 3; loadedCount = 0; if (!$routeParams.login) { @@ -83,10 +83,12 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, } loadedCount++; if (loadedCount === waitCount) { - $(window).resize(_.debounce(function() { - Wait('stop'); - }, 500)); - $(window).resize(); + // console.log('binding to resize'); + // angular.element($window).on('resize', _.debounce(function() { + // console.log('resize from controller'); + // Wait('stop'); + // }, 500)); + // $(window).resize(); } }); @@ -170,7 +172,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, } Home.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'DashboardCounts', 'DashboardJobs', - 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button', 'graphData' + 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors', 'Button', '$window', 'graphData' ]; diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index 599890eb74..7981c88c9a 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -12,10 +12,16 @@ angular.module('DashboardGraphs'). url = getBasePath('config'); - $window.addEventListener('resize', function() { + angular.element($window).on('resize', function(e) { + if(!license_graph) return; adjustGraphSize(license_graph, element); }); + element.on('$destroy', function() { + angular.element($window).off('resize', adjustGraphSize); + }); + + Rest.setUrl(url); Rest.get() .success(function (data){ diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js index 4f1704194a..f9b5a95d2f 100644 --- a/awx/ui/static/js/directives/host-status-graph.js +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -29,10 +29,10 @@ angular.module('DashboardGraphs') host_pie_chart.update(); } - $window.addEventListener('resize', adjustGraphSize); + angular.element($window).on('resize', adjustGraphSize); element.on('$destroy', function() { - $window.removeEventListener('resize', adjustGraphSize); + angular.element($window).off('resize', adjustGraphSize); }); function buildGraph(data) { diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 9d9f5d1849..af6271bda7 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -36,10 +36,14 @@ angular.module('DashboardGraphs') var w = angular.element($window); - $window.addEventListener('resize', function() { + angular.element($window).on('resize', function() { adjustGraphSize(job_status_chart, element); }); + element.on('$destroy', function() { + angular.element($window).off('resize', adjustGraphSize); + }); + if (scope.removeGraphDataReady) { scope.removeGraphDataReady(); } From 50d9c114195ce0a6ce7d5bd37e98f48856152e38 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Tue, 27 Jan 2015 12:28:11 -0500 Subject: [PATCH 20/43] Ensure we always catch errors on Rest calls --- .../js/services/job-status-graph-data.js | 27 ++++++++++--------- .../services/job-status-graph-data-test.js | 15 ++++++++--- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index 4bf681102a..1c2f528a3c 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -20,7 +20,21 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { function getData(period, jobType) { var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; Rest.setUrl(url); - return pluck('data', Rest.get()); + var result = Rest.get() + .catch(function(response) { + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; + + processErrors(null, + response.data, + response.status, + null, { + hdr: 'Error!', + msg: errorMessage + }); + return response; + }); + + return pluck('data', result); } return { @@ -33,17 +47,6 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { $broadcast('DataReceived:JobStatusGraph', result); return result; - }).catch(function(response) { - var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; - - ProcessErrors(null, - response.data, - response.status, - null, { - hdr: 'Error!', - msg: errorMessage - }); - return response; }); }); }, diff --git a/awx/ui/tests/unit/services/job-status-graph-data-test.js b/awx/ui/tests/unit/services/job-status-graph-data-test.js index e80102af97..fc0f4cd4bd 100644 --- a/awx/ui/tests/unit/services/job-status-graph-data-test.js +++ b/awx/ui/tests/unit/services/job-status-graph-data-test.js @@ -8,6 +8,8 @@ describe('Job Status Graph Data Service', function() { $on: sinon.spy(), }; + var processErrors = sinon.spy(); + var getBasePath = function(path) { return '/' + path + '/'; } @@ -46,6 +48,7 @@ describe('Job Status Graph Data Service', function() { $provide.value("$cookieStore", { get: angular.noop }); + $provide.value('ProcessErrors', processErrors); $provide.value('Rest', restStub); $provide.value('GetBasePath', getBasePath); })); @@ -77,14 +80,20 @@ describe('Job Status Graph Data Service', function() { }); it('processes errors through error handler', function() { - var expected = { data: "error", status: "bad" }; - var actual = jobStatusGraphData.get(); + var expected = { data: "blah", status: "bad" }; + var actual = jobStatusGraphData.get().catch(function() { + return processErrors; + }); restStub.fail(expected); flushPromises(); - return expect(actual).to.be.rejectedWith(expected); + return actual.catch(function() { + expect(processErrors).to + .have.been.calledWith(null, expected.data, expected.status); + }); + }); it('broadcasts event when data is received', function() { From d3a6fac5f9532ff64f2c9046117ed082cbe2a4e7 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Tue, 27 Jan 2015 14:05:36 -0500 Subject: [PATCH 21/43] Extract data loading for host count graph --- awx/ui/static/js/app.js | 7 +- .../static/js/directives/host-count-graph.js | 41 +----- awx/ui/static/js/services/data-services.js | 1 + .../js/services/host-count-graph-data.js | 44 ++++++ .../js/services/job-status-graph-data.js | 2 +- awx/ui/static/partials/home.html | 2 +- awx/ui/templates/ui/index.html | 3 + .../services/host-count-graph-data-test.js | 131 ++++++++++++++++++ 8 files changed, 191 insertions(+), 40 deletions(-) create mode 100644 awx/ui/static/js/services/data-services.js create mode 100644 awx/ui/static/js/services/host-count-graph-data.js create mode 100644 awx/ui/tests/unit/services/host-count-graph-data-test.js diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 570ec5f70c..9f3095896c 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -408,11 +408,10 @@ angular.module('Tower', [ templateUrl: urlPrefix + 'partials/home.html', controller: 'Home', resolve: { - graphData: function($q, jobStatusGraphData) { + graphData: function($q, jobStatusGraphData, hostCountGraphData) { return $q.all({ - jobStatus: jobStatusGraphData.get("month", "all").then(function(data) { - return data; - }) + jobStatus: jobStatusGraphData.get("month", "all"), + hostCounts: hostCountGraphData.get() }); } } diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index 7981c88c9a..24d532b53e 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -7,10 +7,13 @@ angular.module('DashboardGraphs'). link: link }; - function link(scope, element, attrs) { + function link(scope, element, attr) { var url, license, license_graph; - url = getBasePath('config'); + scope.$watch(attr.data, function(data) { + if(!data) return; + createGraph(data.hosts, data.license); + }); angular.element($window).on('resize', function(e) { if(!license_graph) return; @@ -22,38 +25,8 @@ angular.module('DashboardGraphs'). }); - Rest.setUrl(url); - Rest.get() - .success(function (data){ - license = data.license_info.instance_count; - scope.$emit('licenseCountReady', license); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get: ' + url + ' GET returned: ' + status }); - }); - if (scope.removeLicenseCountReady) { - scope.removeLicenseCountReady(); - } - scope.removeLicenseCountReady = scope.$on('licenseCountReady', function (e, license) { - url = getBasePath('dashboard')+'graphs/inventory/'; - Rest.setUrl(url); - Rest.get() - .success(function (data) { - scope.$emit('hostDataReady', data, license); - }) - .error(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get: ' + url + ' GET returned: ' + status }); - }); - - }); - - if (scope.removeHostDataReady) { - scope.removeHostDataReady(); - } - scope.removeHostDataReady = scope.$on('hostDataReady', function (e, data, license) { + function createGraph(data, license) { //url = getBasePath('dashboard')+'graphs/'; var graphData = [ @@ -137,6 +110,6 @@ angular.module('DashboardGraphs'). return license_graph; - }); + } } }]); diff --git a/awx/ui/static/js/services/data-services.js b/awx/ui/static/js/services/data-services.js new file mode 100644 index 0000000000..fb6b4dc4fd --- /dev/null +++ b/awx/ui/static/js/services/data-services.js @@ -0,0 +1 @@ +angular.module('DataServices', []); diff --git a/awx/ui/static/js/services/host-count-graph-data.js b/awx/ui/static/js/services/host-count-graph-data.js new file mode 100644 index 0000000000..db447cc640 --- /dev/null +++ b/awx/ui/static/js/services/host-count-graph-data.js @@ -0,0 +1,44 @@ +angular.module('DataServices') + .service('hostCountGraphData', + ["Rest", + "GetBasePath", + "ProcessErrors", + "$q", + HostCountGraphData]); + +function HostCountGraphData(Rest, getBasePath, processErrors, $q) { + + function pluck(property, promise) { + return promise.then(function(value) { + return value[property]; + }); + } + + function getLicenseData() { + url = getBasePath('config'); + Rest.setUrl(url); + return Rest.get() + .then(function (data){ + license = data.data.license_info.instance_count; + return license; + }) + } + + function getHostData() { + url = getBasePath('dashboard')+'graphs/inventory/'; + Rest.setUrl(url); + return pluck('data', Rest.get()); + } + + return { + get: function() { + return $q.all({ + license: getLicenseData(), + hosts: getHostData() + }).catch(function (data, status) { + processErrors(null, data, status, null, { hdr: 'Error!', + msg: 'Failed to get: ' + url + ' GET returned: ' + status }); + }); + } + }; +} diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index 1c2f528a3c..f578041d3a 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -1,4 +1,4 @@ -angular.module('DataServices', []) +angular.module('DataServices') .service('jobStatusGraphData', ["Rest", "GetBasePath", diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index a9155bc49d..c4df41acb2 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -27,7 +27,7 @@
- +
diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 60abfeef45..3893bcc93b 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -81,11 +81,14 @@ + + + diff --git a/awx/ui/tests/unit/services/host-count-graph-data-test.js b/awx/ui/tests/unit/services/host-count-graph-data-test.js new file mode 100644 index 0000000000..ed5b0699ec --- /dev/null +++ b/awx/ui/tests/unit/services/host-count-graph-data-test.js @@ -0,0 +1,131 @@ +describe('Host Count Graph Data Service', function() { + + var q; + + var hostCountGraphData, httpBackend, rootScope, timeout; + + var processErrors = sinon.spy(); + + var getBasePath = function(path) { + return '/' + path + '/'; + } + + function flushPromises() { + window.setTimeout(function() { + inject(function($rootScope) { + $rootScope.$apply(); + }); + }); + } + + function assertUrlDeferred(url, obj) { + if (angular.isUndefined(obj[url]) || + angular.isUndefined(obj[url].then) && + angular.isUndefined(obj[url].promise.then)) { + var urls = []; + + for (key in obj) { + if (/\//.test(key)) { + urls.push(key); + } + } + + var registered = urls.map(function(url) { + return "\t\"" + url + "\""; + }).join("\n"); + + throw "Could not find a thenable registered for url \"" + url + "\". Registered URLs include:\n\n" + registered + "\n\nPerhaps you typo'd the URL?\n" + } + } + + var restStub = { + setUrl: function(url) { + restStub[url] = q.defer(); + restStub.currentUrl = url; + }, + reset: function() { + delete restStub.deferred; + }, + get: function() { + // allow a single deferred on restStub in case we don't need URL + restStub.deferred = restStub[restStub.currentUrl]; + + return restStub.deferred.promise; + }, + succeedAt: function(url, value) { + assertUrlDeferred(url, restStub); + restStub[url].resolve(value); + }, + succeed: function(value) { + restStub.deferred.resolve(value); + }, + failAt: function(url, value) { + assertUrlDeferred(url, restStub); + restStub[url].reject(value); + }, + fail: function(value) { + restStub.deferred.reject(value); + } + }; + + beforeEach(module("Tower")); + + beforeEach(module(function($provide) { + + $provide.value("$cookieStore", { get: angular.noop }); + + $provide.value('Rest', restStub); + $provide.value('GetBasePath', getBasePath); + })); + + afterEach(function() { + restStub.reset(); + }); + + beforeEach(inject(function(_hostCountGraphData_, $httpBackend, $q, $rootScope, $timeout) { + hostCountGraphData = _hostCountGraphData_; + httpBackend = $httpBackend; + rootScope = $rootScope; + timeout = $timeout; + $httpBackend.expectGET('/static/js/local_config.js').respond({ + }); + q = $q; + })); + + it('returns a promise to be fulfilled when data comes in', function() { + var license = "license"; + var hostData = "hosts"; + + var result = hostCountGraphData.get(); + + restStub.succeedAt('/config/', { data: { + license_info: { + instance_count: license + } + } + }); + + restStub.succeedAt('/dashboard/graphs/inventory/', { data: hostData }); + + flushPromises(); + + return expect(result).to.eventually.eql({ license: license, hosts: hostData });; + }); + + it('processes errors through error handler', function() { + var expected = { data: "blah", status: "bad" }; + var actual = hostCountGraphData.get(); + + restStub.failAt('/config/', expected); + + flushPromises(); + + return actual.catch(function() { + expect(processErrors).to + .have.been.calledWith(null, expected.data, expected.status); + }); + + }); + +}); + From 896433300dba69b1c7eb805795a6832e14de7f6b Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Tue, 27 Jan 2015 15:42:53 -0500 Subject: [PATCH 22/43] Fix double modal issue on 401 error --- awx/ui/static/js/services/host-count-graph-data.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/awx/ui/static/js/services/host-count-graph-data.js b/awx/ui/static/js/services/host-count-graph-data.js index db447cc640..e81fd0fe14 100644 --- a/awx/ui/static/js/services/host-count-graph-data.js +++ b/awx/ui/static/js/services/host-count-graph-data.js @@ -35,9 +35,12 @@ function HostCountGraphData(Rest, getBasePath, processErrors, $q) { return $q.all({ license: getLicenseData(), hosts: getHostData() - }).catch(function (data, status) { - processErrors(null, data, status, null, { hdr: 'Error!', - msg: 'Failed to get: ' + url + ' GET returned: ' + status }); + }).catch(function (response) { + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; + processErrors(null, response.data, response.status, null, { hdr: 'Error!', + msg: errorMessage + }); + return response; }); } }; From e567386c6953bd94e0be77394c6d62c0a2d621b6 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Tue, 27 Jan 2015 16:17:11 -0500 Subject: [PATCH 23/43] Fix errors resizing job status graph --- .../static/js/directives/job-status-graph.js | 205 +++++++++--------- 1 file changed, 101 insertions(+), 104 deletions(-) diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index af6271bda7..440e1709c6 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -8,29 +8,117 @@ angular.module('DashboardGraphs') }; function link(scope, element, attr) { - var html, url, job_status_chart, - period="month", - job_type="all"; - + var html; + var url; + var job_status_chart; + var job_status_chart; var cleanup = angular.noop; + scope.period="month"; + scope.jobType="all"; + var data; scope.$watch(attr.data, function(value) { if (value) { - scope.$emit('graphDataReady', value); + createGraph(value, scope.period, scope.jobType); } }); - scope.$on('$destroy', cleanup); + function createGraph(data, period, jobtype){ - cleanup = _.compose( - [ cleanup, - ]); + scope.period = period; + scope.jobType = jobtype; + + var timeFormat, graphData = [ + { + "color": "#00aa00", + "key": "Successful", + "values": data.jobs.successful + }, + { + "key" : "Failed" , + "color" : "#aa0000", + "values": data.jobs.failed + } + ]; + + if(period==="day") { + timeFormat="%H:%M"; + } + else { + timeFormat = '%m/%d'; + } + graphData.map(function(series) { + series.values = series.values.map(function(d) { + return { + x: d[0], + y: d[1] + }; + }); + return series; + }); + + job_status_chart = nv.models.lineChart() + .margin({top: 5, right: 75, bottom: 50, left: 85}) //Adjust chart margins to give the x-axis some breathing room. + .x(function(d,i) { return i; }) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true) //Show the x-axis + // .width(width) + // .height(height) + ; + + + var width = $('.graph-container').width(); // nv.utils.windowSize().width/3, + var height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, + + job_status_chart.xAxis + .axisLabel("Time")//.showMaxMin(true) + .tickFormat(function(d) { + var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; + }); + + job_status_chart.yAxis //Chart y-axis settings + .axisLabel('Jobs') + .tickFormat(d3.format('.f')); + + d3.select(element.find('svg')[0]) + .datum(graphData) + // .attr('width', width) + // .attr('height', height) + // .transition().duration(100) + .call(job_status_chart) + .style({ + // 'width': width, + // 'height': height, + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + // when the Period drop down filter is used, create a new graph based on the + d3.selectAll(element.find(".n")) + .on("click", function() { + period = this.getAttribute("id"); + $('#period-dropdown').replaceWith(""+this.text+"\n"); + + createGraph(data, period, job_type); + }); + + //On click, update with new data + d3.selectAll(element.find(".m")) + .on("click", function() { + job_type = this.getAttribute("id"); + $('#type-dropdown').replaceWith(""+this.text+"\n"); + + createGraph(data, period, job_type); + }); + + adjustGraphSize(job_status_chart, element); - function createGraph(period, jobtype){ - jobStatusGraphData.get(period, jobtype).then(function(data) { - scope.$emit('graphDataReady', data); - }); } var w = angular.element($window); @@ -48,99 +136,8 @@ angular.module('DashboardGraphs') scope.removeGraphDataReady(); } - var job_status_chart; scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data) { - var timeFormat, graphData = [ - { - "color": "#00aa00", - "key": "Successful", - "values": data.jobs.successful - }, - { - "key" : "Failed" , - "color" : "#aa0000", - "values": data.jobs.failed - } - ]; - - if(period==="day"){ - timeFormat="%H:%M"; - } - else { - timeFormat = '%m/%d'; - } - graphData.map(function(series) { - series.values = series.values.map(function(d) { - return { - x: d[0], - y: d[1] - }; - }); - return series; - }); - - job_status_chart = nv.models.lineChart() - .margin({top: 5, right: 75, bottom: 50, left: 85}) //Adjust chart margins to give the x-axis some breathing room. - .x(function(d,i) { return i; }) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true) //Show the x-axis - // .width(width) - // .height(height) - ; - - - var width = $('.graph-container').width(); // nv.utils.windowSize().width/3, - var height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, - - job_status_chart.xAxis - .axisLabel("Time")//.showMaxMin(true) - .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; - }); - - job_status_chart.yAxis //Chart y-axis settings - .axisLabel('Jobs') - .tickFormat(d3.format('.f')); - - d3.select(element.find('svg')[0]) - .datum(graphData) - // .attr('width', width) - // .attr('height', height) - // .transition().duration(100) - .call(job_status_chart) - .style({ - // 'width': width, - // 'height': height, - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - - // when the Period drop down filter is used, create a new graph based on the - d3.selectAll(element.find(".n")) - .on("click", function() { - period = this.getAttribute("id"); - $('#period-dropdown').replaceWith(""+this.text+"\n"); - - createGraph(period, job_type); - }); - - //On click, update with new data - d3.selectAll(element.find(".m")) - .on("click", function() { - job_type = this.getAttribute("id"); - $('#type-dropdown').replaceWith(""+this.text+"\n"); - - createGraph(period, job_type); - }); - - adjustGraphSize(job_status_chart, element); - return job_status_chart; }); From 38b8ed4e9ec5a64c6db1252f4f7c7ff23de86d8c Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Tue, 27 Jan 2015 16:23:51 -0500 Subject: [PATCH 24/43] Remove extraneous unused code/comments --- awx/ui/static/js/controllers/Home.js | 37 ------------------- .../static/js/directives/host-count-graph.js | 3 -- .../static/js/directives/host-status-graph.js | 4 +- .../static/js/directives/job-status-graph.js | 19 +--------- 4 files changed, 2 insertions(+), 61 deletions(-) diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index b2164f037b..d6a2778ca2 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -72,26 +72,6 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, //Wait('start'); } - if ($scope.removeWidgetLoaded) { - $scope.removeWidgetLoaded(); - } - $scope.removeWidgetLoaded = $scope.$on('WidgetLoaded', function (e, label, jobscope, schedulescope) { - // Once all the widgets report back 'loaded', turn off Wait widget - if(label==="dashboard_jobs"){ - jobs_scope = jobscope; - schedule_scope = schedulescope; - } - loadedCount++; - if (loadedCount === waitCount) { - // console.log('binding to resize'); - // angular.element($window).on('resize', _.debounce(function() { - // console.log('resize from controller'); - // Wait('stop'); - // }, 500)); - // $(window).resize(); - } - }); - if ($scope.removeDashboardReady) { $scope.removeDashboardReady(); } @@ -130,23 +110,6 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, }); - if ($rootScope.removeJobStatusChange) { - $rootScope.removeJobStatusChange(); - } - // $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function() { - // jobs_scope.refreshJobs(); - // $scope.$emit('ReloadJobStatusGraph'); - - // }); - - if ($rootScope.removeScheduleChange) { - $rootScope.removeScheduleChange(); - } - $rootScope.removeScheduleChange = $rootScope.$on('ScheduleChange', function() { - schedule_scope.refreshSchedules(); - $scope.$emit('ReloadJobStatusGraph'); - }); - $scope.showActivity = function () { Stream({ scope: $scope diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index 24d532b53e..debf9b49e5 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -94,8 +94,6 @@ angular.module('DashboardGraphs'). .duration(500) .call(license_graph) .style({ - // 'width': width, - // 'height': height, "font-family": 'Open Sans', "font-style": "normal", "font-weight":400, @@ -103,7 +101,6 @@ angular.module('DashboardGraphs'). }); - // nv.utils.windowResize(license_graph.update); scope.$emit('WidgetLoaded'); adjustGraphSize(license_graph, element); diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js index f9b5a95d2f..a724d03724 100644 --- a/awx/ui/static/js/directives/host-status-graph.js +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -67,8 +67,6 @@ angular.module('DashboardGraphs') d3.select(element.find('svg')[0]) .datum(data) - // .attr('width', width) - // .attr('height', height) .transition().duration(350) .call(host_pie_chart) .style({ @@ -77,7 +75,7 @@ angular.module('DashboardGraphs') "font-weight":400, "src": "url(/static/fonts/OpenSans-Regular.ttf)" }); - // nv.utils.windowResize(host_pie_chart.update); + adjustGraphSize(); return host_pie_chart; } diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 440e1709c6..a9d88b4864 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -11,7 +11,6 @@ angular.module('DashboardGraphs') var html; var url; var job_status_chart; - var job_status_chart; var cleanup = angular.noop; scope.period="month"; @@ -64,10 +63,7 @@ angular.module('DashboardGraphs') .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! .showLegend(true) //Show the legend, allowing users to turn on/off line series. .showYAxis(true) //Show the y-axis - .showXAxis(true) //Show the x-axis - // .width(width) - // .height(height) - ; + .showXAxis(true); //Show the x-axis var width = $('.graph-container').width(); // nv.utils.windowSize().width/3, @@ -86,13 +82,8 @@ angular.module('DashboardGraphs') d3.select(element.find('svg')[0]) .datum(graphData) - // .attr('width', width) - // .attr('height', height) - // .transition().duration(100) .call(job_status_chart) .style({ - // 'width': width, - // 'height': height, "font-family": 'Open Sans', "font-style": "normal", "font-weight":400, @@ -121,9 +112,6 @@ angular.module('DashboardGraphs') } - var w = angular.element($window); - - angular.element($window).on('resize', function() { adjustGraphSize(job_status_chart, element); }); @@ -136,10 +124,5 @@ angular.module('DashboardGraphs') scope.removeGraphDataReady(); } - scope.removeGraphDataReady = scope.$on('graphDataReady', function (e, data) { - - return job_status_chart; - - }); } }]); From f3b1bd0a75e7cdebe9096f80dafbfe1025cac35e Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Tue, 27 Jan 2015 16:59:12 -0500 Subject: [PATCH 25/43] Rename new-dashboard less template --- awx/ui/static/less/ansible-ui.less | 2 +- awx/ui/static/less/{new-dashboard.less => dashboard.less} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename awx/ui/static/less/{new-dashboard.less => dashboard.less} (98%) diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index bc741ca16a..702191d940 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -58,7 +58,7 @@ @import "breadcrumbs.less"; @import "stdout.less"; @import "lists.less"; -@import "new-dashboard.less"; +@import "dashboard.less"; @import "jPushMenu.less"; @import "survey-maker.less"; @import "portal.less"; diff --git a/awx/ui/static/less/new-dashboard.less b/awx/ui/static/less/dashboard.less similarity index 98% rename from awx/ui/static/less/new-dashboard.less rename to awx/ui/static/less/dashboard.less index 4a6e2c58cc..6fcbe7289c 100644 --- a/awx/ui/static/less/new-dashboard.less +++ b/awx/ui/static/less/dashboard.less @@ -1,7 +1,7 @@ /********************************************* * Copyright (c) 2014 AnsibleWorks, Inc. * - * new-dashboard.css + * dashboard.css * * custom styles for the new dashboard * From 34e29a042143804d47f1d8acd1dfc7813174192e Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 10:18:19 -0500 Subject: [PATCH 26/43] Correctly select axes on graphs --- awx/ui/static/js/services/adjust-graph-size.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/static/js/services/adjust-graph-size.js b/awx/ui/static/js/services/adjust-graph-size.js index f784cd4cda..0e361b4858 100644 --- a/awx/ui/static/js/services/adjust-graph-size.js +++ b/awx/ui/static/js/services/adjust-graph-size.js @@ -22,11 +22,11 @@ angular.module('DashboardGraphs'). chartModel.yAxis.ticks(Math.max(height / 50, 2)); if (height < 160) { - graph.select('.y.axis').select('.domain').style('display', 'none'); - graph.select('.y.axis').select('.domain').style('display', 'initial'); + graph.select('.y.nv-axis').select('.domain').style('display', 'none'); + graph.select('.y.nv-axis').select('.domain').style('display', 'initial'); } - graph.select('.x.axis') + graph.select('.x.nv-axis') .attr('transform', 'translate(0, ' + height + ')') .call(chartModel.xAxis); From 5ab6ed91fa6a4ca904e3efbb43e02571ed9cdf23 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 12:05:43 -0500 Subject: [PATCH 27/43] Keep bottom margins consistent on line graphs --- awx/ui/static/js/directives/job-status-graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index a9d88b4864..5dcf977e4e 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -58,7 +58,7 @@ angular.module('DashboardGraphs') }); job_status_chart = nv.models.lineChart() - .margin({top: 5, right: 75, bottom: 50, left: 85}) //Adjust chart margins to give the x-axis some breathing room. + .margin({top: 5, right: 75, bottom: 40, left: 85}) //Adjust chart margins to give the x-axis some breathing room. .x(function(d,i) { return i; }) .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! .showLegend(true) //Show the legend, allowing users to turn on/off line series. From f2dbf7a559ac1ef75ff6e0e17ec3918aae5dbcca Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 12:06:10 -0500 Subject: [PATCH 28/43] Fix issue with graph modules causing vertical scroll --- awx/ui/static/js/controllers/Home.js | 7 ++--- .../static/js/directives/auto-size-module.js | 31 +++++++++++++++++++ .../static/js/services/adjust-graph-size.js | 1 - awx/ui/static/partials/home.html | 6 ++-- awx/ui/templates/ui/index.html | 1 + 5 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 awx/ui/static/js/directives/auto-size-module.js diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index d6a2778ca2..4fb44d07d4 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -86,17 +86,14 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, "margin-bottom": "15px"}; $('.graph-container').css(borderStyles); - var winHeight = $(window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - $('.graph-container').height(available_height/2); - // // chart.update(); - DashboardCounts({ scope: $scope, target: 'dash-counts', dashboard: data }); + // // chart.update(); + $scope.graphData = graphData; if ($rootScope.user_is_superuser !== true) { diff --git a/awx/ui/static/js/directives/auto-size-module.js b/awx/ui/static/js/directives/auto-size-module.js new file mode 100644 index 0000000000..daaec6442e --- /dev/null +++ b/awx/ui/static/js/directives/auto-size-module.js @@ -0,0 +1,31 @@ +angular.module('DashboardGraphs') + .directive('autoSizeModule', ['$window', function($window) { + + // Adjusts the size of the module so that all modules + // fit into a single a page; assumes there are 2 rows + // of modules, with the available height being offset + // by the navbar & the count summaries module + return function(scope, element, attr) { + + function adjustSize() { + var winHeight = $($window).height(), + available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; + element.height(available_height/2); + } + + $($window).resize(adjustSize); + + element.on('$destroy', function() { + $($window).off('resize', adjustSize); + }); + + // This makes sure count-container div is loaded + // by controllers/Home.js before we use it + // to determine the available window height + scope.$on('dashboardReady', function() { + adjustSize(); + }); + + }; + +}]); diff --git a/awx/ui/static/js/services/adjust-graph-size.js b/awx/ui/static/js/services/adjust-graph-size.js index 0e361b4858..497955adcb 100644 --- a/awx/ui/static/js/services/adjust-graph-size.js +++ b/awx/ui/static/js/services/adjust-graph-size.js @@ -14,7 +14,6 @@ angular.module('DashboardGraphs'). var width = parseInt(graph.style('width')) - margins.left - margins.right; var height = parseInt(graph.style('height')) - margins.top - margins.bottom; - chartModel.xRange([0, width]); chartModel.yRange([height, 0]); diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index c4df41acb2..5b0e035011 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -13,12 +13,12 @@
-
+
-
+
@@ -26,7 +26,7 @@
-
+
diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 3893bcc93b..a59c5d69a4 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -86,6 +86,7 @@ + From 7b92bf1b88d8d7217ac4fca6925ff9b11683c1f1 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 12:45:46 -0500 Subject: [PATCH 29/43] Document the graph resizing calculations --- .../static/js/services/adjust-graph-size.js | 103 +++++++++++++----- 1 file changed, 78 insertions(+), 25 deletions(-) diff --git a/awx/ui/static/js/services/adjust-graph-size.js b/awx/ui/static/js/services/adjust-graph-size.js index 497955adcb..ed973bbb23 100644 --- a/awx/ui/static/js/services/adjust-graph-size.js +++ b/awx/ui/static/js/services/adjust-graph-size.js @@ -1,37 +1,90 @@ angular.module('DashboardGraphs'). factory('adjustGraphSize', function() { - return function adjustGraphSize(chartModel, element) { - var parentHeight = element.parent().parent().height(); - var toolbarHeight = element.find('.toolbar').height(); - var container = element.find('svg').parent(); - var margins = chartModel.margin(); - var newHeight = parentHeight - toolbarHeight - margins.bottom; - $(container).height(newHeight); + // Adjusts the size of graphs based on the current height + // of the outer parent (see auto-size-module directive). + // + // Since the graph's svg element is set to width & height of 100%, + // it will automatically size itself when the size of its container + // changes. Since boxes in HTML automatically fill the width of their + // parent, we don't have to change the container's width. However, + // since the makers HTML never heard of vertical rhythm, + // we have to manually set a new height on the container. + // + // ## Calculating the container's new height + // + // newHeight is the height we assign to the graph's immediate parent. + // This is calculated as the height of the graph-container (the + // outer parent), offset by the height of the toolbar row + // (the contains the title and/or any filters) and the + // bottom margin. + // + // ## Responsive Graph Stuff + // + // Letting the svg element automatically scale only solves part of + // the responsive graph problem. d3 draws graphs as paths, with static + // positioning of all elements. Therefore, we need to tell the graph how + // to adjust itself so that it can resize properly. + // + // ### Resizing the axes + // + // First we get the width & height of the chart after it has been modified + // by setting the height on its parent (see Calculating the New Container's + // Height above). Note that we need to offset the width/height by the margins + // to make sure we keep all the spacing intact. + // + // Next, we update the range for x & y to take the new width & height into + // account. d3 uses this range to map domain values (the actual data) onto + // pixels. + // + // After that we adjust the number of ticks on the axes. This makes sure we + // will never have overlapping ticks. If that does become a problem, try + // changing the divisor in the calculations to a different number until you + // find something that helps. For example, (width / 75) should make the x + // axis only ever display 1 tick per every 75 pixels. + // + // ### Redrawing the line + // + // Since this is a line graph, now that we've changed the range & ticks, + // we need to instruct d3 to repaint (redraw) the actual lines representing + // the data. We do this by setting the "d" attribute of the path element + // that represents the line to the line function on the chart model. This + // function triggers the mapping of domain to range, and plots the chart. + // Calling chartModel.update() at the end instructs nv to process our changes. + // + return function adjustGraphSize(chartModel, element) { + var parentHeight = element.parent().parent().height(); + var toolbarHeight = element.find('.toolbar').height(); + var container = element.find('svg').parent(); + var margins = chartModel.margin(); - var graph = d3.select(element.find('svg')[0]); - var width = parseInt(graph.style('width')) - margins.left - margins.right; - var height = parseInt(graph.style('height')) - margins.top - margins.bottom; + var newHeight = parentHeight - toolbarHeight - margins.bottom; - chartModel.xRange([0, width]); - chartModel.yRange([height, 0]); + $(container).height(newHeight); - chartModel.xAxis.ticks(Math.max(width / 75, 2)); - chartModel.yAxis.ticks(Math.max(height / 50, 2)); + var graph = d3.select(element.find('svg')[0]); + var width = parseInt(graph.style('width')) - margins.left - margins.right; + var height = parseInt(graph.style('height')) - margins.top - margins.bottom; - if (height < 160) { - graph.select('.y.nv-axis').select('.domain').style('display', 'none'); - graph.select('.y.nv-axis').select('.domain').style('display', 'initial'); - } + chartModel.xRange([0, width]); + chartModel.yRange([height, 0]); - graph.select('.x.nv-axis') - .attr('transform', 'translate(0, ' + height + ')') - .call(chartModel.xAxis); + chartModel.xAxis.ticks(Math.max(width / 75, 2)); + chartModel.yAxis.ticks(Math.max(height / 50, 2)); - graph.selectAll('.line') - .attr('d', chartModel.lines) - - chartModel.update(); + if (height < 160) { + graph.select('.y.nv-axis').select('.domain').style('display', 'none'); + graph.select('.y.nv-axis').select('.domain').style('display', 'initial'); } + + graph.select('.x.nv-axis') + .attr('transform', 'translate(0, ' + height + ')') + .call(chartModel.xAxis); + + graph.selectAll('.line') + .attr('d', chartModel.lines) + + chartModel.update(); + } }); From 6e2c9f5eda69f0584c61b468237208e3cd3e0d1f Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 13:08:52 -0500 Subject: [PATCH 30/43] Remove Debug helper --- awx/ui/static/js/app.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 9f3095896c..c1cf9dd844 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -10,14 +10,6 @@ var urlPrefix, $AnsibleConfig; -window.Debug = {}; -window.Debug.log = function(label, obj) { - if (Debug.enabled) { - console.log(label + ":", obj); - } - return obj; -}; - if ($basePath) { urlPrefix = $basePath; } else { From 4fc86e30ea6d6a93bf85e72afc32458b32567583 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 14:10:31 -0500 Subject: [PATCH 31/43] Fix displaying graphs with no content --- awx/ui/static/js/controllers/Home.js | 3 -- .../static/js/directives/host-status-graph.js | 30 +++++++++++-------- awx/ui/static/partials/home.html | 2 +- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index 4fb44d07d4..314a1e4503 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -96,9 +96,6 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, $scope.graphData = graphData; - if ($rootScope.user_is_superuser !== true) { - $('#dash-host-count-graph').remove(); //replaceWith("
"); - } DashboardJobs({ scope: $scope, target: 'dash-jobs-list', diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js index a724d03724..281e6749f9 100644 --- a/awx/ui/static/js/directives/host-status-graph.js +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -17,6 +17,11 @@ angular.module('DashboardGraphs') }); function adjustGraphSize() { + + if (angular.isUndefined(host_pie_chart)) { + return; + } + var parentHeight = element.parent().parent().height(); var toolbarHeight = element.find('.toolbar').height(); var container = element.find('svg').parent(); @@ -80,19 +85,20 @@ angular.module('DashboardGraphs') return host_pie_chart; } else{ - winHeight = $($window).height(); - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - element.find('.graph-wrapper').height(available_height/2); - element.find('svg').replaceWith(''); + // This should go in a template or something + // but I'm at the end of a card and need to get this done. + // We definitely need to refactor this, I'm letting + // good enough be good enough for right now. + var notFoundContainer = $('
'); + notFoundContainer.css({ + 'text-align': 'center', + 'width': '100%', + 'padding-top': '2em' + }); - canvas = document.getElementById("circlecanvas"); - context = canvas.getContext("2d"); - context.arc(55, 55, 50, 0, Math.PI * 2, false); - context.lineWidth = 1; - context.strokeStyle = '#1778c3'; - context.stroke(); - context.font = "12px Open Sans"; - context.fillText("No Host data",18,55); + notFoundContainer.text('No host data'); + + element.find('svg').replaceWith(notFoundContainer); } } diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index 5b0e035011..62c90258d0 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -26,7 +26,7 @@
-
+
From 03e5e42dae5c51abbe452fab801314f8fd6d350f Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 14:10:58 -0500 Subject: [PATCH 32/43] Fix include order for tests --- awx/ui/tests/karma-shared.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/tests/karma-shared.conf b/awx/ui/tests/karma-shared.conf index 376ef52e47..c6b88fa008 100644 --- a/awx/ui/tests/karma-shared.conf +++ b/awx/ui/tests/karma-shared.conf @@ -51,6 +51,7 @@ module.exports = function() { '../static/lib/lrInfiniteScroll/lrInfiniteScroll.js', '../static/lib/ansible/*.js', '../static/js/config.js', + '../static/js/directives/dashboard-graphs.js', '../static/js/*/*.js', '../static/js/app.js' ], From 1113337cdc929e3f7c3015b7e965fd89ac76dfcb Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 14:17:16 -0500 Subject: [PATCH 33/43] Fix initial load resize for module --- awx/ui/static/partials/home.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index 62c90258d0..dc70714683 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -26,8 +26,8 @@
-
- +
+
From 2e87454e5aef9ec22cbab44229ff676bb59b7c72 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Wed, 28 Jan 2015 16:40:06 -0500 Subject: [PATCH 34/43] More unused code --- awx/ui/static/js/controllers/Home.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index 314a1e4503..ab7da2e3d3 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -30,7 +30,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, ClearScope('home'); - var buttons, html, e, waitCount, loadedCount,borderStyles, jobs_scope, schedule_scope; + var buttons, html, e, borderStyles, jobs_scope, schedule_scope; // Add buttons to the top of the Home page. We're using lib/ansible/generator_helpers.js-> Buttons() // to build buttons dynamically and insure all styling and icons match the rest of the application. @@ -64,9 +64,6 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, e.html(html); $compile(e)($scope); - waitCount = 3; - loadedCount = 0; - if (!$routeParams.login) { // If we're not logging in, start the Wait widget. Otherwise, it's already running. //Wait('start'); @@ -112,7 +109,6 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, $scope.refresh = function () { Wait('start'); - loadedCount = 0; Rest.setUrl(GetBasePath('dashboard')); Rest.get() .success(function (data) { From 1809fec52e4036e4979277e4bf1a0c665890c404 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 29 Jan 2015 12:06:17 -0500 Subject: [PATCH 35/43] Setup testing for ci and development --- .gitignore | 1 + Makefile | 6 +++--- awx/ui/templates/ui/index.html | 3 --- awx/ui/tests/{karma-shared.conf => karma.conf.js} | 14 +++++++++----- 4 files changed, 13 insertions(+), 11 deletions(-) rename awx/ui/tests/{karma-shared.conf => karma.conf.js} (92%) diff --git a/.gitignore b/.gitignore index a0e2987a52..0f07b6eaa4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ awx/projects awx/job_output awx/public/media awx/public/static +awx/ui/tests/test-results.xml awx/ui/static/js/awx.min.js awx/ui/static/js/local_config.js awx/ui/static/css/awx.min.css diff --git a/Makefile b/Makefile index 0bc3af431d..f98d06bd42 100644 --- a/Makefile +++ b/Makefile @@ -240,9 +240,9 @@ test_coverage: coverage_html: coverage html -# Run UI unit tests using Selenium. -test_ui: - $(PYTHON) manage.py test -v2 awx.ui.tests +# Run UI unit tests +test_ui: node_modules + $(GRUNT) karma:ci # Run API unit tests across multiple Python/Django versions with Tox. test_tox: diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index a59c5d69a4..d158fda7ee 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -179,9 +179,6 @@ - - - diff --git a/awx/ui/tests/karma-shared.conf b/awx/ui/tests/karma.conf.js similarity index 92% rename from awx/ui/tests/karma-shared.conf rename to awx/ui/tests/karma.conf.js index c6b88fa008..12a315cf88 100644 --- a/awx/ui/tests/karma-shared.conf +++ b/awx/ui/tests/karma.conf.js @@ -1,8 +1,8 @@ // Karma configuration // Generated on Mon Aug 04 2014 21:17:04 GMT-0400 (EDT) -module.exports = function() { - return { +module.exports = function(config) { + config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', @@ -53,7 +53,11 @@ module.exports = function() { '../static/js/config.js', '../static/js/directives/dashboard-graphs.js', '../static/js/*/*.js', - '../static/js/app.js' + '../static/js/app.js', + '../static/lib/angular-mocks/angular-mocks.js', + '../../../node_modules/ng-midway-tester/src/ngMidwayTester.js', + './unit/*', + './unit/**/*' ], @@ -72,7 +76,7 @@ module.exports = function() { // test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress'], + reporters: ['dots', 'progress'], client: { mocha: { @@ -102,5 +106,5 @@ module.exports = function() { // if true, Karma captures browsers, runs the tests and exits singleRun: false - }; + }); }; From 98a4f195c2c65d24ee06d302e54550e4be19935e Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 29 Jan 2015 13:31:55 -0500 Subject: [PATCH 36/43] Add grunt task for running plato reports --- .jshintrc | 4 ---- Makefile | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.jshintrc b/.jshintrc index 81365330d8..255d14024b 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,8 +1,4 @@ { - // Details: https://github.com/victorporof/Sublime-JSHint#using-your-own-jshintrc-options - // Example: https://github.com/jshint/jshint/blob/master/examples/.jshintrc - // Documentation: http://www.jshint.com/docs/ - "browser": true, "jquery": true, "esnext": true, diff --git a/Makefile b/Makefile index f98d06bd42..d1f2f03dd3 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ MOCK_CFG ?= .PHONY: clean rebase push requirements requirements_pypi requirements_jenkins \ develop refresh adduser syncdb migrate dbchange dbshell runserver celeryd \ - receiver test test_coverage coverage_html test_ui test_jenkins dev_build \ + receiver test test_coverage coverage_html ui_analysis_report test_ui test_jenkins dev_build \ release_build release_clean sdist rpmtar mock-rpm mock-srpm \ deb deb-src debian reprepro setup_tarball @@ -240,6 +240,9 @@ test_coverage: coverage_html: coverage html +ui_analysis_report: node_modules + $(GRUNT) plato:report + # Run UI unit tests test_ui: node_modules $(GRUNT) karma:ci From f1f15f1670fc10a3274fb1d9bd1db1e6995fffee Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 29 Jan 2015 14:01:20 -0500 Subject: [PATCH 37/43] Generate chart model from the start --- awx/ui/static/js/directives/job-status-graph.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 5dcf977e4e..a56ab2b2d9 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -10,7 +10,7 @@ angular.module('DashboardGraphs') function link(scope, element, attr) { var html; var url; - var job_status_chart; + var job_status_chart = nv.models.lineChart(); var cleanup = angular.noop; scope.period="month"; @@ -57,13 +57,13 @@ angular.module('DashboardGraphs') return series; }); - job_status_chart = nv.models.lineChart() - .margin({top: 5, right: 75, bottom: 40, left: 85}) //Adjust chart margins to give the x-axis some breathing room. - .x(function(d,i) { return i; }) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true); //Show the x-axis + job_status_chart + .margin({top: 5, right: 75, bottom: 40, left: 85}) //Adjust chart margins to give the x-axis some breathing room. + .x(function(d,i) { return i; }) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true); //Show the x-axis var width = $('.graph-container').width(); // nv.utils.windowSize().width/3, From e28869c63d7befa7c9192847875243584ffac5ae Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 29 Jan 2015 14:20:39 -0500 Subject: [PATCH 38/43] Fix cleanup of resize events --- .../static/js/directives/host-count-graph.js | 7 ++++++- .../static/js/directives/job-status-graph.js | 9 +++++---- .../unit/directives/job-status-graph-test.js | 20 +++++++++++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index debf9b49e5..9b0f99440f 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -15,13 +15,18 @@ angular.module('DashboardGraphs'). createGraph(data.hosts, data.license); }); + function onResize() { + if(!license_graph) return; + adjustGraphSize(license_graph, element); + } + angular.element($window).on('resize', function(e) { if(!license_graph) return; adjustGraphSize(license_graph, element); }); element.on('$destroy', function() { - angular.element($window).off('resize', adjustGraphSize); + angular.element($window).off('resize', onResize); }); diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index a56ab2b2d9..1d5f6f3827 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -109,15 +109,16 @@ angular.module('DashboardGraphs') }); adjustGraphSize(job_status_chart, element); - } - angular.element($window).on('resize', function() { + function onResize() { adjustGraphSize(job_status_chart, element); - }); + } + + angular.element($window).on('resize', onResize); element.on('$destroy', function() { - angular.element($window).off('resize', adjustGraphSize); + angular.element($window).off('resize', onResize); }); if (scope.removeGraphDataReady) { diff --git a/awx/ui/tests/unit/directives/job-status-graph-test.js b/awx/ui/tests/unit/directives/job-status-graph-test.js index 09628f8e86..18f5596916 100644 --- a/awx/ui/tests/unit/directives/job-status-graph-test.js +++ b/awx/ui/tests/unit/directives/job-status-graph-test.js @@ -1,10 +1,13 @@ describe('Job Status Graph Directive', function() { var element, scope, httpBackend; + var resizeHandler = sinon.spy(); + beforeEach(module('Tower')); beforeEach(module(function($provide) { $provide.value('LoadBasePaths', angular.noop); + $provide.value('adjustGraphSize', resizeHandler); })); beforeEach(inject(function($rootScope, $compile, $httpBackend) { @@ -36,8 +39,9 @@ describe('Job Status Graph Directive', function() { })); afterEach(function() { - httpBackend.verifyNoOutstandingExpectation(); - httpBackend.verifyNoOutstandingRequest(); + element.trigger('$destroy'); + httpBackend.verifyNoOutstandingExpectation(); + httpBackend.verifyNoOutstandingRequest(); }); function filterDataSeries(key, data) { @@ -68,5 +72,17 @@ describe('Job Status Graph Directive', function() { {x: 5, y: 0, series: 1}]); }); + it('cleans up external bindings', function() { + element.trigger('$destroy'); + + resizeHandler.reset(); + + inject(['$window', function($window) { + angular.element($window).trigger('resize'); + }]); + + expect(resizeHandler).not.to.have.been.called; + }); + }); From 6b509e28147544b652d287a3edd20a727bc74494 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 29 Jan 2015 16:40:50 -0500 Subject: [PATCH 39/43] Handle edge cases for auto resize module directive --- .../static/js/directives/auto-size-module.js | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/awx/ui/static/js/directives/auto-size-module.js b/awx/ui/static/js/directives/auto-size-module.js index daaec6442e..eee8e6b3e8 100644 --- a/awx/ui/static/js/directives/auto-size-module.js +++ b/awx/ui/static/js/directives/auto-size-module.js @@ -1,5 +1,5 @@ angular.module('DashboardGraphs') - .directive('autoSizeModule', ['$window', function($window) { + .directive('autoSizeModule', ['$window', '$timeout', function($window, $timeout) { // Adjusts the size of the module so that all modules // fit into a single a page; assumes there are 2 rows @@ -7,6 +7,21 @@ angular.module('DashboardGraphs') // by the navbar & the count summaries module return function(scope, element, attr) { + // We need to trigger a resize on the first call + // to this when the view things load; but we don't want + // to trigger a global window resize for everything that + // has an auto resize, since they'll all pick it up with + // a single call + var triggerResize = + _.throttle(function() { + $($window).resize(); + }, 1000); + + function adjustSizeInitially() { + adjustSize(); + triggerResize(); + } + function adjustSize() { var winHeight = $($window).height(), available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; @@ -19,11 +34,18 @@ angular.module('DashboardGraphs') $($window).off('resize', adjustSize); }); + // Wait a second or until dashboardReady triggers, + // whichever comes first. The timeout handles cases + // where dashboardReady never fires. + + var dashboardReadyTimeout = $timeout(adjustSizeInitially, 500); + // This makes sure count-container div is loaded // by controllers/Home.js before we use it // to determine the available window height scope.$on('dashboardReady', function() { - adjustSize(); + $timeout.cancel(dashboardReadyTimeout); + adjustSizeInitially(); }); }; From 2814300e1e591d0ebe901feb69973edf6f2efc03 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 29 Jan 2015 16:41:43 -0500 Subject: [PATCH 40/43] Consistency fixes --- awx/ui/static/js/directives/host-count-graph.js | 1 - awx/ui/static/js/directives/host-status-graph.js | 4 ++-- awx/ui/static/js/services/job-status-graph-data.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index 9b0f99440f..4e50836557 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -32,7 +32,6 @@ angular.module('DashboardGraphs'). function createGraph(data, license) { - //url = getBasePath('dashboard')+'graphs/'; var graphData = [ { diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js index 281e6749f9..e5d5accccf 100644 --- a/awx/ui/static/js/directives/host-status-graph.js +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -12,7 +12,7 @@ angular.module('DashboardGraphs') scope.$watch(attr.data, function(data) { if (data && data.hosts) { - buildGraph(data); + createGraph(data); } }); @@ -40,7 +40,7 @@ angular.module('DashboardGraphs') angular.element($window).off('resize', adjustGraphSize); }); - function buildGraph(data) { + function createGraph(data) { if(data.hosts.total+data.hosts.failed>0){ data = [ { diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index f578041d3a..b29c81456d 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -31,7 +31,7 @@ function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { hdr: 'Error!', msg: errorMessage }); - return response; + return response; }); return pluck('data', result); From ffc12c3065dbf0f39955483e15b1c55330cddb5f Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 29 Jan 2015 16:41:55 -0500 Subject: [PATCH 41/43] Don't show dashboard until we have a user signed in --- awx/ui/static/partials/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/static/partials/home.html b/awx/ui/static/partials/home.html index dc70714683..095222dff4 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -1,5 +1,5 @@ -
+
From 2464d68b383edaf905eaec75b53d70c58b77c7d8 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Fri, 30 Jan 2015 10:27:01 -0500 Subject: [PATCH 42/43] Fix jshint errors in my updates --- awx/ui/static/js/app.js | 12 +- awx/ui/static/js/controllers/Home.js | 16 +- .../static/js/directives/auto-size-module.js | 68 +++--- .../static/js/directives/host-count-graph.js | 214 ++++++++--------- .../static/js/directives/host-status-graph.js | 168 +++++++------- .../static/js/directives/job-status-graph.js | 215 +++++++++--------- .../static/js/services/adjust-graph-size.js | 148 ++++++------ .../js/services/host-count-graph-data.js | 80 +++---- .../js/services/job-status-graph-data.js | 99 ++++---- 9 files changed, 507 insertions(+), 513 deletions(-) diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index c1cf9dd844..a777d252c3 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -400,12 +400,12 @@ angular.module('Tower', [ templateUrl: urlPrefix + 'partials/home.html', controller: 'Home', resolve: { - graphData: function($q, jobStatusGraphData, hostCountGraphData) { - return $q.all({ - jobStatus: jobStatusGraphData.get("month", "all"), - hostCounts: hostCountGraphData.get() - }); - } + graphData: function($q, jobStatusGraphData, hostCountGraphData) { + return $q.all({ + jobStatus: jobStatusGraphData.get("month", "all"), + hostCounts: hostCountGraphData.get() + }); + } } }). diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index ab7da2e3d3..8ecb0fcb81 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -30,7 +30,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, ClearScope('home'); - var buttons, html, e, borderStyles, jobs_scope, schedule_scope; + var buttons, html, e, borderStyles; // Add buttons to the top of the Home page. We're using lib/ansible/generator_helpers.js-> Buttons() // to build buttons dynamically and insure all styling and icons match the rest of the application. @@ -111,13 +111,13 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, Wait('start'); Rest.setUrl(GetBasePath('dashboard')); Rest.get() - .success(function (data) { - $scope.dashboardData = data; - $scope.$emit('dashboardReady', data); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); - }); + .success(function (data) { + $scope.dashboardData = data; + $scope.$emit('dashboardReady', data); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); + }); }; $scope.refresh(); diff --git a/awx/ui/static/js/directives/auto-size-module.js b/awx/ui/static/js/directives/auto-size-module.js index eee8e6b3e8..2df92c53fd 100644 --- a/awx/ui/static/js/directives/auto-size-module.js +++ b/awx/ui/static/js/directives/auto-size-module.js @@ -1,52 +1,52 @@ angular.module('DashboardGraphs') - .directive('autoSizeModule', ['$window', '$timeout', function($window, $timeout) { +.directive('autoSizeModule', ['$window', '$timeout', function($window, $timeout) { // Adjusts the size of the module so that all modules // fit into a single a page; assumes there are 2 rows // of modules, with the available height being offset // by the navbar & the count summaries module - return function(scope, element, attr) { + return function(scope, element) { - // We need to trigger a resize on the first call - // to this when the view things load; but we don't want - // to trigger a global window resize for everything that - // has an auto resize, since they'll all pick it up with - // a single call - var triggerResize = - _.throttle(function() { - $($window).resize(); + // We need to trigger a resize on the first call + // to this when the view things load; but we don't want + // to trigger a global window resize for everything that + // has an auto resize, since they'll all pick it up with + // a single call + var triggerResize = + _.throttle(function() { + $($window).resize(); }, 1000); - function adjustSizeInitially() { - adjustSize(); - triggerResize(); - } + function adjustSizeInitially() { + adjustSize(); + triggerResize(); + } - function adjustSize() { - var winHeight = $($window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - element.height(available_height/2); - } + function adjustSize() { + var winHeight = $($window).height(), + available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; + element.height(available_height/2); + } - $($window).resize(adjustSize); + $($window).resize(adjustSize); - element.on('$destroy', function() { - $($window).off('resize', adjustSize); - }); + element.on('$destroy', function() { + $($window).off('resize', adjustSize); + }); - // Wait a second or until dashboardReady triggers, - // whichever comes first. The timeout handles cases - // where dashboardReady never fires. + // Wait a second or until dashboardReady triggers, + // whichever comes first. The timeout handles cases + // where dashboardReady never fires. - var dashboardReadyTimeout = $timeout(adjustSizeInitially, 500); + var dashboardReadyTimeout = $timeout(adjustSizeInitially, 500); - // This makes sure count-container div is loaded - // by controllers/Home.js before we use it - // to determine the available window height - scope.$on('dashboardReady', function() { - $timeout.cancel(dashboardReadyTimeout); - adjustSizeInitially(); - }); + // This makes sure count-container div is loaded + // by controllers/Home.js before we use it + // to determine the available window height + scope.$on('dashboardReady', function() { + $timeout.cancel(dashboardReadyTimeout); + adjustSizeInitially(); + }); }; diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index 4e50836557..bffb5f8c76 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -1,116 +1,126 @@ angular.module('DashboardGraphs'). - directive('hostCountGraph', ['GetBasePath', 'Rest', 'adjustGraphSize', '$window', function(getBasePath, Rest, adjustGraphSize, $window) { + directive('hostCountGraph', ['GetBasePath', 'Rest', 'adjustGraphSize', '$window', function(getBasePath, Rest, adjustGraphSize, $window) { - return { - restrict: 'E', - templateUrl: '/static/partials/host_count_graph.html', - link: link - }; + return { + restrict: 'E', + templateUrl: '/static/partials/host_count_graph.html', + link: link + }; - function link(scope, element, attr) { - var url, license, license_graph; + function link(scope, element, attr) { + var license_graph; - scope.$watch(attr.data, function(data) { - if(!data) return; - createGraph(data.hosts, data.license); - }); + scope.$watch(attr.data, function(data) { - function onResize() { - if(!license_graph) return; - adjustGraphSize(license_graph, element); - } + if(!data) { + return; + } - angular.element($window).on('resize', function(e) { - if(!license_graph) return; - adjustGraphSize(license_graph, element); - }); + createGraph(data.hosts, data.license); + }); - element.on('$destroy', function() { - angular.element($window).off('resize', onResize); - }); + function onResize() { + if(!license_graph) { + return; + } - - function createGraph(data, license) { - //url = getBasePath('dashboard')+'graphs/'; - var graphData = [ - { - "key" : "Hosts" , - "color" : "#1778c3", - "values": data.hosts - }, - { - "key" : "License" , - "color" : "#171717", - "values": data.hosts - } - ]; - - graphData.map(function(series) { - if(series.key==="Hosts"){ - series.values = series.values.map(function(d) { - return { - x: d[0], - y: d[1] - }; - }); + adjustGraphSize(license_graph, element); } - if(series.key==="License"){ - series.values = series.values.map(function(d) { - return { - x: d[0], - y: license - }; - }); + + angular.element($window).on('resize', function() { + + if(!license_graph) { + return; + } + + adjustGraphSize(license_graph, element); + }); + + element.on('$destroy', function() { + angular.element($window).off('resize', onResize); + }); + + + + function createGraph(data, license) { + //url = getBasePath('dashboard')+'graphs/'; + var graphData = [ + { "key" : "Hosts" , + "color" : "#1778c3", + "values": data.hosts + }, + { "key" : "License" , + "color" : "#171717", + "values": data.hosts + } + ]; + + graphData.map(function(series) { + if(series.key==="Hosts"){ + series.values = series.values.map(function(d) { + return { + x: d[0], + y: d[1] + }; + }); + } + if(series.key==="License"){ + series.values = series.values.map(function(d) { + return { + x: d[0], + y: license + }; + }); + + } + return series; + + }); + + var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, + height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5, + license_graph = nv.models.lineChart() + .margin({top: 15, right: 75, bottom: 40, left: 85}) + .x(function(d,i) { return i ;}) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .transitionDuration(350) //how fast do you want the lines to transition? + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true) //Show the x-axis + ; + + license_graph.xAxis + .axisLabel("Time") + .tickFormat(function(d) { + var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : ''; + }); + + license_graph.yAxis //Chart y-axis settings + .axisLabel('Hosts') + .tickFormat(d3.format('.f')); + + d3.select(element.find('svg')[0]) + .datum(graphData).transition() + .attr('width', width) + .attr('height', height) + .duration(500) + .call(license_graph) + .style({ + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + + scope.$emit('WidgetLoaded'); + + adjustGraphSize(license_graph, element); + + return license_graph; } - return series; - - }); - - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5, - license_graph = nv.models.lineChart() - .margin({top: 15, right: 75, bottom: 40, left: 85}) - .x(function(d,i) { return i ;}) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .transitionDuration(350) //how fast do you want the lines to transition? - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true) //Show the x-axis - ; - - license_graph.xAxis - .axisLabel("Time") - .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : ''; - }); - - license_graph.yAxis //Chart y-axis settings - .axisLabel('Hosts') - .tickFormat(d3.format('.f')); - - d3.select(element.find('svg')[0]) - .datum(graphData).transition() - .attr('width', width) - .attr('height', height) - .duration(500) - .call(license_graph) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - - - scope.$emit('WidgetLoaded'); - - adjustGraphSize(license_graph, element); - - return license_graph; - } - } }]); diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js index e5d5accccf..11b0dbf227 100644 --- a/awx/ui/static/js/directives/host-status-graph.js +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -1,106 +1,102 @@ angular.module('DashboardGraphs') .directive('hostStatusGraph', ['$compile', '$window', function ($compile, $window) { - return { - restrict: 'E', - link: link, - templateUrl: '/static/partials/host_status_graph.html' - }; + return { + restrict: 'E', + link: link, + templateUrl: '/static/partials/host_status_graph.html' + }; - function link(scope, element, attr) { - var html, canvas, context, winHeight, available_height, host_pie_chart; + function link(scope, element, attr) { + var host_pie_chart; - scope.$watch(attr.data, function(data) { - if (data && data.hosts) { - createGraph(data); - } - }); + scope.$watch(attr.data, function(data) { + if (data && data.hosts) { + createGraph(data); + } + }); - function adjustGraphSize() { + function adjustGraphSize() { - if (angular.isUndefined(host_pie_chart)) { - return; - } + if (angular.isUndefined(host_pie_chart)) { + return; + } - var parentHeight = element.parent().parent().height(); - var toolbarHeight = element.find('.toolbar').height(); - var container = element.find('svg').parent(); - var margins = host_pie_chart.margin(); + var parentHeight = element.parent().parent().height(); + var toolbarHeight = element.find('.toolbar').height(); + var container = element.find('svg').parent(); + var margins = host_pie_chart.margin(); - var newHeight = parentHeight - toolbarHeight - margins.bottom; + var newHeight = parentHeight - toolbarHeight - margins.bottom; - $(container).height(newHeight); + $(container).height(newHeight); - host_pie_chart.update(); - } - - angular.element($window).on('resize', adjustGraphSize); - - element.on('$destroy', function() { - angular.element($window).off('resize', adjustGraphSize); - }); - - function createGraph(data) { - if(data.hosts.total+data.hosts.failed>0){ - data = [ - { - "label": "Successful", - "color": "#00aa00", - "value" : data.hosts.total - } , - { - "label": "Failed", - "color" : "#aa0000", - "value" : data.hosts.failed + host_pie_chart.update(); } - ]; - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, - host_pie_chart = nv.models.pieChart() - .margin({top: 5, right: 75, bottom: 25, left: 85}) - .x(function(d) { return d.label; }) - .y(function(d) { return d.value; }) - .showLabels(true) - .labelThreshold(0.01) - .tooltipContent(function(x, y) { - return ''+x+''+ '

' + Math.floor(y.replace(',','')) + ' Hosts ' + '

'; - }) - .color(['#00aa00', '#aa0000']); + angular.element($window).on('resize', adjustGraphSize); - host_pie_chart.pie.pieLabelsOutside(true).labelType("percent"); - - d3.select(element.find('svg')[0]) - .datum(data) - .transition().duration(350) - .call(host_pie_chart) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" + element.on('$destroy', function() { + angular.element($window).off('resize', adjustGraphSize); }); - adjustGraphSize(); - return host_pie_chart; - } - else{ - // This should go in a template or something - // but I'm at the end of a card and need to get this done. - // We definitely need to refactor this, I'm letting - // good enough be good enough for right now. - var notFoundContainer = $('
'); - notFoundContainer.css({ - 'text-align': 'center', - 'width': '100%', - 'padding-top': '2em' - }); + function createGraph(data) { + if(data.hosts.total+data.hosts.failed>0){ + data = [ + { "label": "Successful", + "color": "#00aa00", + "value" : data.hosts.total + } , + { "label": "Failed", + "color" : "#aa0000", + "value" : data.hosts.failed + } + ]; - notFoundContainer.text('No host data'); + host_pie_chart = nv.models.pieChart() + .margin({top: 5, right: 75, bottom: 25, left: 85}) + .x(function(d) { return d.label; }) + .y(function(d) { return d.value; }) + .showLabels(true) + .labelThreshold(0.01) + .tooltipContent(function(x, y) { + return ''+x+''+ '

' + Math.floor(y.replace(',','')) + ' Hosts ' + '

'; + }) + .color(['#00aa00', '#aa0000']); - element.find('svg').replaceWith(notFoundContainer); - } + host_pie_chart.pie.pieLabelsOutside(true).labelType("percent"); + d3.select(element.find('svg')[0]) + .datum(data) + .transition().duration(350) + .call(host_pie_chart) + .style({ + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + adjustGraphSize(); + return host_pie_chart; + } + else{ + // This should go in a template or something + // but I'm at the end of a card and need to get this done. + // We definitely need to refactor this, I'm letting + // good enough be good enough for right now. + var notFoundContainer = $('
'); + notFoundContainer.css({ + 'text-align': 'center', + 'width': '100%', + 'padding-top': '2em' + }); + + notFoundContainer.text('No host data'); + + element.find('svg').replaceWith(notFoundContainer); + } + + } } - } - }]); + }]); diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 1d5f6f3827..3cf6696bf2 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -1,129 +1,120 @@ angular.module('DashboardGraphs') .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'adjustGraphSize', 'jobStatusGraphData', - function ($rootScope, $compile , $location, $window, Wait, adjustGraphSize, jobStatusGraphData) { + function ($rootScope, $compile , $location, $window, Wait, adjustGraphSize) { return { - restrict: 'E', - templateUrl: '/static/partials/job_status_graph.html', - link: link + restrict: 'E', + templateUrl: '/static/partials/job_status_graph.html', + link: link }; - function link(scope, element, attr) { - var html; - var url; - var job_status_chart = nv.models.lineChart(); - var cleanup = angular.noop; + function link(scope, element, attr) { + var job_type, job_status_chart = nv.models.lineChart(); - scope.period="month"; - scope.jobType="all"; + scope.period="month"; + scope.jobType="all"; - var data; - scope.$watch(attr.data, function(value) { - if (value) { - createGraph(value, scope.period, scope.jobType); - } - }); + scope.$watch(attr.data, function(value) { + if (value) { + createGraph(value, scope.period, scope.jobType); + } + }); - function createGraph(data, period, jobtype){ + function createGraph(data, period, jobtype){ - scope.period = period; - scope.jobType = jobtype; + scope.period = period; + scope.jobType = jobtype; - var timeFormat, graphData = [ - { - "color": "#00aa00", - "key": "Successful", - "values": data.jobs.successful - }, - { - "key" : "Failed" , - "color" : "#aa0000", - "values": data.jobs.failed + var timeFormat, graphData = [ + { "color": "#00aa00", + "key": "Successful", + "values": data.jobs.successful + }, + { "key" : "Failed" , + "color" : "#aa0000", + "values": data.jobs.failed + } + ]; + + if(period==="day") { + timeFormat="%H:%M"; + } + else { + timeFormat = '%m/%d'; + } + graphData.map(function(series) { + series.values = series.values.map(function(d) { + return { + x: d[0], + y: d[1] + }; + }); + return series; + }); + + job_status_chart + .margin({top: 5, right: 75, bottom: 40, left: 85}) //Adjust chart margins to give the x-axis some breathing room. + .x(function(d,i) { return i; }) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true); //Show the x-axis + + + job_status_chart.xAxis + .axisLabel("Time")//.showMaxMin(true) + .tickFormat(function(d) { + var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; + }); + + job_status_chart.yAxis //Chart y-axis settings + .axisLabel('Jobs') + .tickFormat(d3.format('.f')); + + d3.select(element.find('svg')[0]) + .datum(graphData) + .call(job_status_chart) + .style({ + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + // when the Period drop down filter is used, create a new graph based on the + d3.selectAll(element.find(".n")) + .on("click", function() { + period = this.getAttribute("id"); + $('#period-dropdown').replaceWith(""+this.text+"\n"); + + createGraph(data, period, job_type); + }); + + //On click, update with new data + d3.selectAll(element.find(".m")) + .on("click", function() { + job_type = this.getAttribute("id"); + $('#type-dropdown').replaceWith(""+this.text+"\n"); + + createGraph(data, period, job_type); + }); + + adjustGraphSize(job_status_chart, element); } - ]; - if(period==="day") { - timeFormat="%H:%M"; + function onResize() { + adjustGraphSize(job_status_chart, element); } - else { - timeFormat = '%m/%d'; + + angular.element($window).on('resize', onResize); + + element.on('$destroy', function() { + angular.element($window).off('resize', onResize); + }); + + if (scope.removeGraphDataReady) { + scope.removeGraphDataReady(); } - graphData.map(function(series) { - series.values = series.values.map(function(d) { - return { - x: d[0], - y: d[1] - }; - }); - return series; - }); - job_status_chart - .margin({top: 5, right: 75, bottom: 40, left: 85}) //Adjust chart margins to give the x-axis some breathing room. - .x(function(d,i) { return i; }) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true); //Show the x-axis - - - var width = $('.graph-container').width(); // nv.utils.windowSize().width/3, - var height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, - - job_status_chart.xAxis - .axisLabel("Time")//.showMaxMin(true) - .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; - }); - - job_status_chart.yAxis //Chart y-axis settings - .axisLabel('Jobs') - .tickFormat(d3.format('.f')); - - d3.select(element.find('svg')[0]) - .datum(graphData) - .call(job_status_chart) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - - // when the Period drop down filter is used, create a new graph based on the - d3.selectAll(element.find(".n")) - .on("click", function() { - period = this.getAttribute("id"); - $('#period-dropdown').replaceWith(""+this.text+"\n"); - - createGraph(data, period, job_type); - }); - - //On click, update with new data - d3.selectAll(element.find(".m")) - .on("click", function() { - job_type = this.getAttribute("id"); - $('#type-dropdown').replaceWith(""+this.text+"\n"); - - createGraph(data, period, job_type); - }); - - adjustGraphSize(job_status_chart, element); } - - function onResize() { - adjustGraphSize(job_status_chart, element); - } - - angular.element($window).on('resize', onResize); - - element.on('$destroy', function() { - angular.element($window).off('resize', onResize); - }); - - if (scope.removeGraphDataReady) { - scope.removeGraphDataReady(); - } - - } }]); diff --git a/awx/ui/static/js/services/adjust-graph-size.js b/awx/ui/static/js/services/adjust-graph-size.js index ed973bbb23..e447f94b38 100644 --- a/awx/ui/static/js/services/adjust-graph-size.js +++ b/awx/ui/static/js/services/adjust-graph-size.js @@ -1,90 +1,90 @@ angular.module('DashboardGraphs'). - factory('adjustGraphSize', function() { + factory('adjustGraphSize', function() { - // Adjusts the size of graphs based on the current height - // of the outer parent (see auto-size-module directive). - // - // Since the graph's svg element is set to width & height of 100%, - // it will automatically size itself when the size of its container - // changes. Since boxes in HTML automatically fill the width of their - // parent, we don't have to change the container's width. However, - // since the makers HTML never heard of vertical rhythm, - // we have to manually set a new height on the container. - // - // ## Calculating the container's new height - // - // newHeight is the height we assign to the graph's immediate parent. - // This is calculated as the height of the graph-container (the - // outer parent), offset by the height of the toolbar row - // (the contains the title and/or any filters) and the - // bottom margin. - // - // ## Responsive Graph Stuff - // - // Letting the svg element automatically scale only solves part of - // the responsive graph problem. d3 draws graphs as paths, with static - // positioning of all elements. Therefore, we need to tell the graph how - // to adjust itself so that it can resize properly. - // - // ### Resizing the axes - // - // First we get the width & height of the chart after it has been modified - // by setting the height on its parent (see Calculating the New Container's - // Height above). Note that we need to offset the width/height by the margins - // to make sure we keep all the spacing intact. - // - // Next, we update the range for x & y to take the new width & height into - // account. d3 uses this range to map domain values (the actual data) onto - // pixels. - // - // After that we adjust the number of ticks on the axes. This makes sure we - // will never have overlapping ticks. If that does become a problem, try - // changing the divisor in the calculations to a different number until you - // find something that helps. For example, (width / 75) should make the x - // axis only ever display 1 tick per every 75 pixels. - // - // ### Redrawing the line - // - // Since this is a line graph, now that we've changed the range & ticks, - // we need to instruct d3 to repaint (redraw) the actual lines representing - // the data. We do this by setting the "d" attribute of the path element - // that represents the line to the line function on the chart model. This - // function triggers the mapping of domain to range, and plots the chart. - // Calling chartModel.update() at the end instructs nv to process our changes. - // + // Adjusts the size of graphs based on the current height + // of the outer parent (see auto-size-module directive). + // + // Since the graph's svg element is set to width & height of 100%, + // it will automatically size itself when the size of its container + // changes. Since boxes in HTML automatically fill the width of their + // parent, we don't have to change the container's width. However, + // since the makers HTML never heard of vertical rhythm, + // we have to manually set a new height on the container. + // + // ## Calculating the container's new height + // + // newHeight is the height we assign to the graph's immediate parent. + // This is calculated as the height of the graph-container (the + // outer parent), offset by the height of the toolbar row + // (the contains the title and/or any filters) and the + // bottom margin. + // + // ## Responsive Graph Stuff + // + // Letting the svg element automatically scale only solves part of + // the responsive graph problem. d3 draws graphs as paths, with static + // positioning of all elements. Therefore, we need to tell the graph how + // to adjust itself so that it can resize properly. + // + // ### Resizing the axes + // + // First we get the width & height of the chart after it has been modified + // by setting the height on its parent (see Calculating the New Container's + // Height above). Note that we need to offset the width/height by the margins + // to make sure we keep all the spacing intact. + // + // Next, we update the range for x & y to take the new width & height into + // account. d3 uses this range to map domain values (the actual data) onto + // pixels. + // + // After that we adjust the number of ticks on the axes. This makes sure we + // will never have overlapping ticks. If that does become a problem, try + // changing the divisor in the calculations to a different number until you + // find something that helps. For example, (width / 75) should make the x + // axis only ever display 1 tick per every 75 pixels. + // + // ### Redrawing the line + // + // Since this is a line graph, now that we've changed the range & ticks, + // we need to instruct d3 to repaint (redraw) the actual lines representing + // the data. We do this by setting the "d" attribute of the path element + // that represents the line to the line function on the chart model. This + // function triggers the mapping of domain to range, and plots the chart. + // Calling chartModel.update() at the end instructs nv to process our changes. + // return function adjustGraphSize(chartModel, element) { - var parentHeight = element.parent().parent().height(); - var toolbarHeight = element.find('.toolbar').height(); - var container = element.find('svg').parent(); - var margins = chartModel.margin(); + var parentHeight = element.parent().parent().height(); + var toolbarHeight = element.find('.toolbar').height(); + var container = element.find('svg').parent(); + var margins = chartModel.margin(); - var newHeight = parentHeight - toolbarHeight - margins.bottom; + var newHeight = parentHeight - toolbarHeight - margins.bottom; - $(container).height(newHeight); + $(container).height(newHeight); - var graph = d3.select(element.find('svg')[0]); - var width = parseInt(graph.style('width')) - margins.left - margins.right; - var height = parseInt(graph.style('height')) - margins.top - margins.bottom; + var graph = d3.select(element.find('svg')[0]); + var width = parseInt(graph.style('width')) - margins.left - margins.right; + var height = parseInt(graph.style('height')) - margins.top - margins.bottom; - chartModel.xRange([0, width]); - chartModel.yRange([height, 0]); + chartModel.xRange([0, width]); + chartModel.yRange([height, 0]); - chartModel.xAxis.ticks(Math.max(width / 75, 2)); - chartModel.yAxis.ticks(Math.max(height / 50, 2)); + chartModel.xAxis.ticks(Math.max(width / 75, 2)); + chartModel.yAxis.ticks(Math.max(height / 50, 2)); - if (height < 160) { - graph.select('.y.nv-axis').select('.domain').style('display', 'none'); - graph.select('.y.nv-axis').select('.domain').style('display', 'initial'); - } + if (height < 160) { + graph.select('.y.nv-axis').select('.domain').style('display', 'none'); + graph.select('.y.nv-axis').select('.domain').style('display', 'initial'); + } - graph.select('.x.nv-axis') + graph.select('.x.nv-axis') .attr('transform', 'translate(0, ' + height + ')') .call(chartModel.xAxis); - graph.selectAll('.line') - .attr('d', chartModel.lines) + graph.selectAll('.line') + .attr('d', chartModel.lines); - chartModel.update(); - } + chartModel.update(); + }; }); diff --git a/awx/ui/static/js/services/host-count-graph-data.js b/awx/ui/static/js/services/host-count-graph-data.js index e81fd0fe14..4bc1f817c3 100644 --- a/awx/ui/static/js/services/host-count-graph-data.js +++ b/awx/ui/static/js/services/host-count-graph-data.js @@ -1,47 +1,47 @@ angular.module('DataServices') - .service('hostCountGraphData', - ["Rest", - "GetBasePath", - "ProcessErrors", - "$q", - HostCountGraphData]); +.service('hostCountGraphData', + ["Rest", + "GetBasePath", + "ProcessErrors", + "$q", + HostCountGraphData]); function HostCountGraphData(Rest, getBasePath, processErrors, $q) { - function pluck(property, promise) { - return promise.then(function(value) { - return value[property]; - }); - } - - function getLicenseData() { - url = getBasePath('config'); - Rest.setUrl(url); - return Rest.get() - .then(function (data){ - license = data.data.license_info.instance_count; - return license; - }) - } - - function getHostData() { - url = getBasePath('dashboard')+'graphs/inventory/'; - Rest.setUrl(url); - return pluck('data', Rest.get()); - } - - return { - get: function() { - return $q.all({ - license: getLicenseData(), - hosts: getHostData() - }).catch(function (response) { - var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; - processErrors(null, response.data, response.status, null, { hdr: 'Error!', - msg: errorMessage - }); - return response; + function pluck(property, promise) { + return promise.then(function(value) { + return value[property]; }); } - }; + + function getLicenseData() { + var url = getBasePath('config'); + Rest.setUrl(url); + return Rest.get() + .then(function (data){ + var license = data.data.license_info.instance_count; + return license; + }); + } + + function getHostData() { + var url = getBasePath('dashboard')+'graphs/inventory/'; + Rest.setUrl(url); + return pluck('data', Rest.get()); + } + + return { + get: function() { + return $q.all({ + license: getLicenseData(), + hosts: getHostData() + }).catch(function (response) { + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; + processErrors(null, response.data, response.status, null, { hdr: 'Error!', + msg: errorMessage + }); + return response; + }); + } + }; } diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index b29c81456d..242368fc0f 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -1,62 +1,59 @@ angular.module('DataServices') - .service('jobStatusGraphData', - ["Rest", - "GetBasePath", - "ProcessErrors", - "$rootScope", - "$q", - JobStatusGraphData]); +.service('jobStatusGraphData', + ["Rest", + "GetBasePath", + "ProcessErrors", + "$rootScope", + JobStatusGraphData]); -function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { - var callbacks = {}; - var currentCallbackId = 0; +function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope) { - function pluck(property, promise) { - return promise.then(function(value) { - return value[property]; - }); - } + function pluck(property, promise) { + return promise.then(function(value) { + return value[property]; + }); + } - function getData(period, jobType) { - var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; - Rest.setUrl(url); - var result = Rest.get() - .catch(function(response) { - var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; + function getData(period, jobType) { + var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; + Rest.setUrl(url); + var result = Rest.get() + .catch(function(response) { + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; - processErrors(null, - response.data, - response.status, - null, { - hdr: 'Error!', - msg: errorMessage - }); - return response; - }); + processErrors(null, + response.data, + response.status, + null, { + hdr: 'Error!', + msg: errorMessage + }); + return response; + }); - return pluck('data', result); - } + return pluck('data', result); + } - return { - destroyWatcher: angular.noop, - setupWatcher: function(period, jobType) { - this.destroyWatcher = - $rootScope.$on('JobStatusChange', function() { - getData(period, jobType).then(function(result) { - $rootScope. - $broadcast('DataReceived:JobStatusGraph', - result); - return result; - }); - }); - }, - get: function(period, jobType) { + return { + destroyWatcher: angular.noop, + setupWatcher: function(period, jobType) { + this.destroyWatcher = + $rootScope.$on('JobStatusChange', function() { + getData(period, jobType).then(function(result) { + $rootScope. + $broadcast('DataReceived:JobStatusGraph', + result); + return result; + }); + }); + }, + get: function(period, jobType) { - this.destroyWatcher(); - this.setupWatcher(period, jobType); + this.destroyWatcher(); + this.setupWatcher(period, jobType); - return getData(period, jobType); + return getData(period, jobType); - } - }; + } + }; } From 477552376447df44bfd2400d9f1650fc62506161 Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Fri, 30 Jan 2015 15:08:28 -0500 Subject: [PATCH 43/43] Updates jshint settings --- .jshintrc | 20 +++++++++++-- awx/ui/static/js/config.js | 2 +- awx/ui/static/js/controllers/JobTemplates.js | 17 +++++++---- awx/ui/static/js/helpers/Groups.js | 3 +- awx/ui/static/js/helpers/JobDetail.js | 30 +++++++++++++------- awx/ui/static/js/helpers/JobSubmission.js | 10 +++++-- awx/ui/static/js/helpers/Jobs.js | 4 ++- awx/ui/static/js/helpers/Permissions.js | 4 +-- awx/ui/static/js/helpers/Survey.js | 8 ++++-- awx/ui/static/lib/ansible/directives.js | 3 +- awx/ui/static/lib/ansible/list-generator.js | 3 +- awx/ui/static/lib/ansible/prompt-dialog.js | 11 ++++--- 12 files changed, 81 insertions(+), 34 deletions(-) diff --git a/.jshintrc b/.jshintrc index 255d14024b..8562d25f2e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,15 +3,29 @@ "jquery": true, "esnext": true, "globalstrict": true, - "globals": { "angular":false, "alert":false, "$AnsibleConfig":true, "$basePath":true, "jsyaml":false, "_":false, "d3":false, "Donut3D":false, "nv":false }, + "curly": true, + "immed": true, + "latedef": "nofunc", + "noarg": true, + "nonew": true, + "notypeof": true, + "globals": { + "angular":false, + "alert":false, + "$AnsibleConfig":true, + "$basePath":true, + "jsyaml":false, + "_":false, + "d3":false, + "Donut3D":false, + "nv":false + }, "strict": false, "quotmark": false, - "smarttabs": true, "trailing": true, "undef": true, "unused": true, "eqeqeq": true, "indent": 4, - "onevar": true, "newcap": false } diff --git a/awx/ui/static/js/config.js b/awx/ui/static/js/config.js index abed710242..3531acafc0 100644 --- a/awx/ui/static/js/config.js +++ b/awx/ui/static/js/config.js @@ -28,7 +28,7 @@ // > password_strength = green // It also controls password validation. Passwords are rejected if the score is not > password_strength. - session_timeout: 1800, // Number of seconds before an inactive session is automatically timed out and forced to log in again. + session_timeout: 1800, // Number of seconds before an inactive session is automatically timed out and forced to log in again. // Separate from time out value set in API. diff --git a/awx/ui/static/js/controllers/JobTemplates.js b/awx/ui/static/js/controllers/JobTemplates.js index 7719229cd9..2238c81698 100644 --- a/awx/ui/static/js/controllers/JobTemplates.js +++ b/awx/ui/static/js/controllers/JobTemplates.js @@ -527,7 +527,9 @@ function JobTemplatesAdd($scope, $rootScope, $compile, $location, $log, $routePa if($scope.survey_enabled === true && $scope.survey_exists!==true){ $scope.$emit("PromptForSurvey"); - } else $scope.$emit("GatherFormFields"); + } else { + $scope.$emit("GatherFormFields"); + } }; @@ -634,7 +636,9 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP ' project or make the playbooks available on the file system.', 'alert-info'); }); } - else Wait('stop'); + else { + Wait('stop'); + } }; // Detect and alert user to potential SCM status issues @@ -964,7 +968,9 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP if($scope.survey_enabled === true && $scope.survey_exists!==true){ $scope.$emit("PromptForSurvey"); - } else $scope.$emit("GatherFormFields"); + } else { + $scope.$emit("GatherFormFields"); + } }; @@ -1021,11 +1027,12 @@ function JobTemplatesEdit($scope, $rootScope, $compile, $location, $log, $routeP if($scope.survey_enabled === true && $scope.survey_exists!==true){ $scope.$emit("PromptForSurvey"); } - else + else { PlaybookRun({ scope: $scope, id: id }); + } }; // handler for 'Enable Survey' button @@ -1063,4 +1070,4 @@ JobTemplatesEdit.$inject = ['$scope', '$rootScope', '$compile', '$location', '$l 'GetBasePath', 'md5Setup', 'ParseTypeChange', 'JobStatusToolTip', 'FormatDate', 'Wait', 'Stream', 'Empty', 'Prompt', 'ParseVariableString', 'ToJSON', 'SchedulesControllerInit', 'JobsControllerInit', 'JobsListUpdate', 'GetChoices', 'SchedulesListInit', 'SchedulesList', 'CallbackHelpInit', 'PlaybookRun' , 'SurveyControllerInit', '$sce' -]; \ No newline at end of file +]; diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index 6c240530b6..0b34c48d2f 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -1074,8 +1074,9 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' msg: 'Failed to retrieve inventory source. GET status: ' + status }); }); } - else + else { modal_scope.$emit('groupVariablesLoaded'); // JT-- "groupVariablesLoaded" is where the schedule info is loaded, so I make a call after the sources_scope.source has been loaded + } }); if (sources_scope.removeScopeSourceTypeOptionsReady) { diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index f71e9850fe..87f8aa38f4 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -1208,10 +1208,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge keys; function listSort(a,b) { - if (parseInt(a,10) < parseInt(b,10)) + if (parseInt(a,10) < parseInt(b,10)) { return -1; - if (parseInt(a,10) > parseInt(b,10)) + } + if (parseInt(a,10) > parseInt(b,10)) { return 1; + } return 0; } @@ -1276,10 +1278,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge idx, key, keys, newKeys, tasks, t; function listSort(a,b) { - if (parseInt(a,10) < parseInt(b,10)) + if (parseInt(a,10) < parseInt(b,10)) { return -1; - if (parseInt(a,10) > parseInt(b,10)) + } + if (parseInt(a,10) > parseInt(b,10)) { return 1; + } return 0; } @@ -1385,15 +1389,19 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge keys = Object.keys(filteredListB); keys.sort(function compare(a, b) { if (filteredListB[a].name === filteredListB[b].name) { - if (filteredListB[a].counter < filteredListB[b].counter) + if (filteredListB[a].counter < filteredListB[b].counter) { return -1; - if (filteredListB[a].counter >filteredListB[b].counter) + } + if (filteredListB[a].counter >filteredListB[b].counter) { return 1; + } } else { - if (filteredListB[a].name < filteredListB[b].name) + if (filteredListB[a].name < filteredListB[b].name) { return -1; - if (filteredListB[a].name > filteredListB[b].name) + } + if (filteredListB[a].name > filteredListB[b].name) { return 1; + } } // a must be equal to b return 0; @@ -1453,10 +1461,12 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge keys = Object.keys(filteredListB); keys.sort(function(a,b) { - if (filteredListB[a].name > filteredListB[b].name) + if (filteredListB[a].name > filteredListB[b].name) { return 1; - if (filteredListB[a].name < filteredListB[b].name) + } + if (filteredListB[a].name < filteredListB[b].name) { return -1; + } // a must be equal to b return 0; }); diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index e8e89ed40e..90bf9e62ca 100644 --- a/awx/ui/static/js/helpers/JobSubmission.js +++ b/awx/ui/static/js/helpers/JobSubmission.js @@ -111,7 +111,9 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential if(scope.prompt_for_vars===false && scope.survey_enabled===true){ scope.$emit('GetExtraVars'); } - else scope.$emit('BuildData'); + else { + scope.$emit('BuildData'); + } }; @@ -831,7 +833,9 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi else if (!Empty(scope.survey_enabled) && scope.survey_enabled===true) { scope.$emit('PromptForSurvey', html, url); } - else scope.$emit('StartPlaybookRun', url); + else { + scope.$emit('StartPlaybookRun', url); + } } }) @@ -1021,4 +1025,4 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi }); }; } -]); \ No newline at end of file +]); diff --git a/awx/ui/static/js/helpers/Jobs.js b/awx/ui/static/js/helpers/Jobs.js index e161167605..52d52f96b5 100644 --- a/awx/ui/static/js/helpers/Jobs.js +++ b/awx/ui/static/js/helpers/Jobs.js @@ -95,7 +95,9 @@ angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'Job if(scope.$parent.portalMode===true){ $window.open('/#/jobs/' + job.id, '_blank'); } - else $location.url('/jobs/' + job.id); + else { + $location.url('/jobs/' + job.id); + } } else { LogViewer({ diff --git a/awx/ui/static/js/helpers/Permissions.js b/awx/ui/static/js/helpers/Permissions.js index bc263549cb..de2a64156b 100644 --- a/awx/ui/static/js/helpers/Permissions.js +++ b/awx/ui/static/js/helpers/Permissions.js @@ -34,8 +34,8 @@ angular.module('PermissionsHelper', []) } else { scope.projectrequired = true; html = "
\n" + - "
Create
\n" + - "
Allow the user or team to create job templates. This implies that they have the Run and Check permissions.
\n" + + "
Create
\n" + + "
Allow the user or team to create job templates. This implies that they have the Run and Check permissions.
\n" + "
Run
\n" + "
Allow the user or team to run a job template from the project against the inventory. In Run mode modules will " + "be executed, and changes to the inventory will occur.
\n" + diff --git a/awx/ui/static/js/helpers/Survey.js b/awx/ui/static/js/helpers/Survey.js index 2ba260f1d9..889922ebcc 100644 --- a/awx/ui/static/js/helpers/Survey.js +++ b/awx/ui/static/js/helpers/Survey.js @@ -73,7 +73,9 @@ angular.module('SurveyHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', if(scope.can_edit === false){ $('#survey-save-button').attr('disabled', "disabled"); } - else $('#survey-save-button').attr('ng-disabled', "survey_questions.length<1 "); + else { + $('#survey-save-button').attr('ng-disabled', "survey_questions.length<1 "); + } element = angular.element(document.getElementById('survey-save-button')); $compile(element)(scope); @@ -513,7 +515,9 @@ angular.module('SurveyHelper', [ 'Utilities', 'RestServices', 'SchedulesHelper', if(scope.mode === 'add'){ questions = []; } - else scope.survey_questions = []; + else { + scope.survey_questions = []; + } $(me).dialog('close'); }; diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index ebda3089d7..51b79bf818 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -100,8 +100,9 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job scope.update = function(){ var val = []; angular.forEach(scope.cbModel, function(v,k){ - if (v) + if (v) { val.push(k); + } }); if (val.length>0){ scope.ngModel.value = val; diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index e046d461d7..aed5faa4cc 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -425,10 +425,11 @@ angular.module('ListGenerator', ['GeneratorHelpers']) list.iterator + ".id }}\" ng-click=\"toggle_" + list.iterator + "(" + list.iterator + ".id, true)\" ng-value=\"1\" " + "ng-false-value=\"0\" id=\"check_{{" + list.iterator + ".id}}\" />"; } - else // its assumed that options.input_type = checkbox + else { // its assumed that options.input_type = checkbox html += ""; + } } else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) { // Row level actions diff --git a/awx/ui/static/lib/ansible/prompt-dialog.js b/awx/ui/static/lib/ansible/prompt-dialog.js index a0c7f3a064..8bbe8692c6 100644 --- a/awx/ui/static/lib/ansible/prompt-dialog.js +++ b/awx/ui/static/lib/ansible/prompt-dialog.js @@ -63,14 +63,16 @@ angular.module('PromptDialog', ['Utilities']) focus = function() { var focusableElement = focusableChildren[currentIndex]; - if (focusableElement) + if (focusableElement) { focusableElement.focus(); + } }; focusPrevious = function () { currentIndex--; - if (currentIndex < 0) + if (currentIndex < 0) { currentIndex = numElements - 1; + } focus(); @@ -79,8 +81,9 @@ angular.module('PromptDialog', ['Utilities']) focusNext = function () { currentIndex++; - if (currentIndex >= numElements) + if (currentIndex >= numElements) { currentIndex = 0; + } focus(); @@ -118,4 +121,4 @@ angular.module('PromptDialog', ['Utilities']) }; } - ]); \ No newline at end of file + ]);