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); + }); + + }); + +}); +