From 79b79f0481c604bb514020c86fb771aa571ea3d6 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 21 Apr 2015 09:09:20 -0400 Subject: [PATCH] initial files for license diff directive awLicenseFeature with show/hide changed file locations changing file location adjusting controller logic to only return list of features leveraging $rootScope instead of local storage adding awFeature directive for lists/forms for activity stream button Adding route resolvers and service for getting license In order to get the license info from the API and not from local storage, the UI needs to hit hte API before loading any pages, therefore I've added route resolvers that will ensure that we have the appropriate data (license features) before navigating to a new page. I've also added the awFeature directive to the organizations list -> add-button and the ldap checkbox on the user form page. adjusting alignment fixing jshint errors commting file for testings adding tests for features service and features controller adding features controller unit test --- awx/ui/static/js/app.js | 417 +++++++++++++++--- awx/ui/static/js/forms/Credentials.js | 3 +- awx/ui/static/js/forms/Inventories.js | 1 + awx/ui/static/js/forms/JobTemplates.js | 18 +- awx/ui/static/js/forms/Organizations.js | 1 + awx/ui/static/js/forms/Permissions.js | 1 + awx/ui/static/js/forms/Projects.js | 1 + awx/ui/static/js/forms/Teams.js | 1 + awx/ui/static/js/forms/Users.js | 4 +- awx/ui/static/js/lists/Credentials.js | 3 +- awx/ui/static/js/lists/HomeGroups.js | 3 +- awx/ui/static/js/lists/HomeHosts.js | 3 +- awx/ui/static/js/lists/Inventories.js | 3 +- awx/ui/static/js/lists/InventoryGroups.js | 3 +- awx/ui/static/js/lists/InventoryHosts.js | 3 +- awx/ui/static/js/lists/JobTemplates.js | 1 + awx/ui/static/js/lists/Organizations.js | 6 +- awx/ui/static/js/lists/Permissions.js | 3 +- awx/ui/static/js/lists/Projects.js | 3 +- awx/ui/static/js/lists/ScanJobs.js | 3 +- awx/ui/static/js/lists/Schedules.js | 3 +- awx/ui/static/js/lists/Teams.js | 3 +- awx/ui/static/js/lists/Users.js | 3 +- awx/ui/static/js/shared/AuthService.js | 2 + .../js/shared/features/features.controller.js | 10 + .../js/shared/features/features.directive.js | 15 + .../js/shared/features/features.service.js | 30 ++ awx/ui/static/js/shared/features/main.js | 6 + awx/ui/static/js/shared/form-generator.js | 1 + .../list-generator/list-actions.partial.html | 4 +- awx/ui/static/partials/home.html | 3 +- .../unit/features/features.controller-test.js | 25 ++ .../unit/features/features.service-test.js | 55 +++ nodemon.json | 3 +- 34 files changed, 546 insertions(+), 98 deletions(-) create mode 100644 awx/ui/static/js/shared/features/features.controller.js create mode 100644 awx/ui/static/js/shared/features/features.directive.js create mode 100644 awx/ui/static/js/shared/features/features.service.js create mode 100644 awx/ui/static/js/shared/features/main.js create mode 100644 awx/ui/tests/unit/features/features.controller-test.js create mode 100644 awx/ui/tests/unit/features/features.service-test.js diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index 80377297a0..c3f37e1528 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -33,7 +33,6 @@ import {PortalController} from 'tower/controllers/Portal'; import dataServices from 'tower/services/_data-services'; import dashboardGraphs from 'tower/directives/_dashboard-graphs'; - import {JobDetailController} from 'tower/controllers/JobDetail'; import {JobStdoutController} from 'tower/controllers/JobStdout'; import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from 'tower/controllers/JobTemplates'; @@ -58,6 +57,7 @@ import 'tower/shared/Timer'; import 'tower/shared/Socket'; import 'tower/job-templates/main'; +import 'tower/shared/features/main'; /*#if DEBUG#*/ import {__deferLoadIfEnabled} from 'tower/debug'; @@ -171,7 +171,8 @@ var tower = angular.module('Tower', [ 'ConfigureTowerJobsListDefinition', 'CreateCustomInventoryHelper', 'CustomInventoryListDefinition', - 'AdhocHelper' + 'AdhocHelper', + 'features' ]) .constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/') @@ -184,277 +185,552 @@ var tower = angular.module('Tower', [ when('/jobs', { templateUrl: urlPrefix + 'partials/jobs.html', - controller: JobsListController + controller: JobsListController, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/portal', { templateUrl: urlPrefix + 'partials/portal.html', - controller: PortalController + controller: PortalController, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/jobs/:id', { templateUrl: urlPrefix + 'partials/job_detail.html', - controller: JobDetailController + controller: JobDetailController, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/jobs/:id/stdout', { templateUrl: urlPrefix + 'partials/job_stdout.html', - controller: JobStdoutController + controller: JobStdoutController, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/ad_hoc_commands/:id', { templateUrl: urlPrefix + 'partials/job_stdout_adhoc.html', - controller: JobStdoutController + controller: JobStdoutController, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/job_templates', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: JobTemplatesList + controller: JobTemplatesList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/job_templates/add', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: JobTemplatesAdd + controller: JobTemplatesAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/job_templates/:template_id', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: JobTemplatesEdit + controller: JobTemplatesEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/job_templates/:id/schedules', { templateUrl: urlPrefix + 'partials/schedule_detail.html', - controller: ScheduleEditController + controller: ScheduleEditController, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/projects', { templateUrl: urlPrefix + 'partials/projects.html', - controller: ProjectsList + controller: ProjectsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/projects/add', { templateUrl: urlPrefix + 'partials/projects.html', - controller: ProjectsAdd + controller: ProjectsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/projects/:id', { templateUrl: urlPrefix + 'partials/projects.html', - controller: ProjectsEdit + controller: ProjectsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/projects/:id/schedules', { templateUrl: urlPrefix + 'partials/schedule_detail.html', - controller: ScheduleEditController + controller: ScheduleEditController, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/projects/:project_id/organizations', { templateUrl: urlPrefix + 'partials/projects.html', - controller: OrganizationsList + controller: OrganizationsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/projects/:project_id/organizations/add', { templateUrl: urlPrefix + 'partials/projects.html', - controller: OrganizationsAdd + controller: OrganizationsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/inventories', { templateUrl: urlPrefix + 'partials/inventories.html', - controller: InventoriesList + controller: InventoriesList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/inventories/add', { templateUrl: urlPrefix + 'partials/inventories.html', - controller: InventoriesAdd + controller: InventoriesAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/inventories/:inventory_id', { templateUrl: urlPrefix + 'partials/inventories.html', - controller: InventoriesEdit + controller: InventoriesEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/inventories/:inventory_id/job_templates/add', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: JobTemplatesAdd + controller: JobTemplatesAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/inventories/:inventory_id/job_templates/:template_id', { templateUrl: urlPrefix + 'partials/job_templates.html', - controller: JobTemplatesEdit + controller: JobTemplatesEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/inventories/:inventory_id/manage', { templateUrl: urlPrefix + 'partials/inventory-manage.html', - controller: InventoriesManage + controller: InventoriesManage, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/inventories/:inventory_id/adhoc', { templateUrl: urlPrefix + 'partials/adhoc.html', - controller: AdhocCtrl + controller: AdhocCtrl, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/organizations', { templateUrl: urlPrefix + 'partials/organizations.html', - controller: OrganizationsList + controller: OrganizationsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/organizations/add', { templateUrl: urlPrefix + 'partials/organizations.html', - controller: OrganizationsAdd + controller: OrganizationsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/organizations/:organization_id', { templateUrl: urlPrefix + 'partials/organizations.html', - controller: OrganizationsEdit + controller: OrganizationsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/organizations/:organization_id/admins', { templateUrl: urlPrefix + 'partials/organizations.html', - controller: AdminsList + controller: AdminsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/organizations/:organization_id/users', { templateUrl: urlPrefix + 'partials/users.html', - controller: UsersList + controller: UsersList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/organizations/:organization_id/users/add', { templateUrl: urlPrefix + 'partials/users.html', - controller: UsersAdd + controller: UsersAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/organizations/:organization_id/users/:user_id', { templateUrl: urlPrefix + 'partials/users.html', - controller: UsersEdit + controller: UsersEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams', { templateUrl: urlPrefix + 'partials/teams.html', - controller: TeamsList + controller: TeamsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: TeamsAdd + controller: TeamsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: TeamsEdit + controller: TeamsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/permissions/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: PermissionsAdd + controller: PermissionsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/permissions', { templateUrl: urlPrefix + 'partials/teams.html', - controller: PermissionsList + controller: PermissionsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/permissions/:permission_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: PermissionsEdit + controller: PermissionsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/users', { templateUrl: urlPrefix + 'partials/teams.html', - controller: UsersList + controller: UsersList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/users/:user_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: UsersEdit + controller: UsersEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/projects', { templateUrl: urlPrefix + 'partials/teams.html', - controller: ProjectsList + controller: ProjectsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/projects/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: ProjectsAdd + controller: ProjectsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/projects/:project_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: ProjectsEdit + controller: ProjectsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/credentials', { templateUrl: urlPrefix + 'partials/teams.html', - controller: CredentialsList + controller: CredentialsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/credentials/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: CredentialsAdd + controller: CredentialsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:team_id/credentials/:credential_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: CredentialsEdit + controller: CredentialsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/credentials', { templateUrl: urlPrefix + 'partials/credentials.html', - controller: CredentialsList + controller: CredentialsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/credentials/add', { templateUrl: urlPrefix + 'partials/credentials.html', - controller: CredentialsAdd + controller: CredentialsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/credentials/:credential_id', { templateUrl: urlPrefix + 'partials/credentials.html', - controller: CredentialsEdit + controller: CredentialsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/users', { templateUrl: urlPrefix + 'partials/users.html', - controller: UsersList + controller: UsersList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/users/add', { templateUrl: urlPrefix + 'partials/users.html', - controller: UsersAdd + controller: UsersAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/users/:user_id', { templateUrl: urlPrefix + 'partials/users.html', - controller: UsersEdit + controller: UsersEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/users/:user_id/credentials', { templateUrl: urlPrefix + 'partials/users.html', - controller: CredentialsList + controller: CredentialsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/users/:user_id/permissions/add', { templateUrl: urlPrefix + 'partials/users.html', - controller: PermissionsAdd + controller: PermissionsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/users/:user_id/permissions', { templateUrl: urlPrefix + 'partials/users.html', - controller: PermissionsList + controller: PermissionsList, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/users/:user_id/permissions/:permission_id', { templateUrl: urlPrefix + 'partials/users.html', - controller: PermissionsEdit + controller: PermissionsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/users/:user_id/credentials/add', { templateUrl: urlPrefix + 'partials/teams.html', - controller: CredentialsAdd + controller: CredentialsAdd, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/teams/:user_id/credentials/:credential_id', { templateUrl: urlPrefix + 'partials/teams.html', - controller: CredentialsEdit + controller: CredentialsEdit, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/login', { @@ -464,17 +740,23 @@ var tower = angular.module('Tower', [ when('/logout', { templateUrl: urlPrefix + 'partials/blank.html', - controller: Authenticate + controller: Authenticate, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/home', { templateUrl: urlPrefix + 'partials/home.html', controller: Home, resolve: { - graphData: ['$q', 'jobStatusGraphData', 'hostCountGraphData', function($q, jobStatusGraphData, hostCountGraphData) { + graphData: ['$q', 'jobStatusGraphData', 'hostCountGraphData', 'FeaturesService', function($q, jobStatusGraphData, hostCountGraphData, FeaturesService) { return $q.all({ jobStatus: jobStatusGraphData.get("month", "all"), - hostCounts: hostCountGraphData.get() + hostCounts: hostCountGraphData.get(), + features: FeaturesService.get() }); }] } @@ -482,12 +764,22 @@ var tower = angular.module('Tower', [ when('/home/groups', { templateUrl: urlPrefix + 'partials/subhome.html', - controller: HomeGroups + controller: HomeGroups, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/home/hosts', { templateUrl: urlPrefix + 'partials/subhome.html', - controller: HomeHosts + controller: HomeHosts, + resolve: { + features: ['FeaturesService', function(FeaturesService) { + return FeaturesService.get(); + }] + } }). when('/sockets', { @@ -684,7 +976,6 @@ var tower = angular.module('Tower', [ if($location.$$url !== '/login'){ $rootScope.$emit('OpenSocket'); } - } activateTab(); diff --git a/awx/ui/static/js/forms/Credentials.js b/awx/ui/static/js/forms/Credentials.js index 607c785bc3..fe2e7ada65 100644 --- a/awx/ui/static/js/forms/Credentials.js +++ b/awx/ui/static/js/forms/Credentials.js @@ -25,7 +25,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/forms/Inventories.js b/awx/ui/static/js/forms/Inventories.js index 3b628da008..bc0e957df9 100644 --- a/awx/ui/static/js/forms/Inventories.js +++ b/awx/ui/static/js/forms/Inventories.js @@ -30,6 +30,7 @@ export default 'class': "btn-primary btn-xs activity-btn", ngClick: "showActivity()", awToolTip: "View Activity Stream", + awFeature: 'activity_streams', dataPlacement: "top", icon: "icon-comments-alt", mode: 'edit', diff --git a/awx/ui/static/js/forms/JobTemplates.js b/awx/ui/static/js/forms/JobTemplates.js index 49f3b70f9f..62b72f7617 100644 --- a/awx/ui/static/js/forms/JobTemplates.js +++ b/awx/ui/static/js/forms/JobTemplates.js @@ -40,6 +40,7 @@ export default 'class': "btn-primary btn-xs activity-btn", ngClick: "showActivity()", awToolTip: "View Activity Stream", + awFeature: 'activity_streams', dataPlacement: "top", icon: "icon-comments-alt", mode: 'edit', @@ -258,27 +259,12 @@ export default dataTitle: 'Prompt for Extra Variables', dataContainer: "body" }, - // survey_enabled: { - // type: 'custom', - // column: 2, - // control: '
'+ - // '
'+ - // ''+ - // '
'+ - // ''+ - // '
'+ - // '
' - // }, survey_enabled: { label: 'Enable Survey', type: 'checkbox', addRequired: false, editRequird: false, - // trueValue: true, - // falseValue: false, + awFeature: 'surveys', ngChange: "surveyEnabled()", column: 2, awPopOver: "

If checked, user will be prompted at job launch with a series of questions related to the job.

", diff --git a/awx/ui/static/js/forms/Organizations.js b/awx/ui/static/js/forms/Organizations.js index b3b6eaa102..a25ee330fa 100644 --- a/awx/ui/static/js/forms/Organizations.js +++ b/awx/ui/static/js/forms/Organizations.js @@ -30,6 +30,7 @@ export default 'class': "btn-primary btn-xs activity-btn", ngClick: "showActivity()", awToolTip: "View Activity Stream", + awFeature: 'activity_streams', dataPlacement: "top", icon: "icon-comments-alt", mode: 'edit', diff --git a/awx/ui/static/js/forms/Permissions.js b/awx/ui/static/js/forms/Permissions.js index 274ab2285a..a09c1ec1f8 100644 --- a/awx/ui/static/js/forms/Permissions.js +++ b/awx/ui/static/js/forms/Permissions.js @@ -27,6 +27,7 @@ export default 'class': "btn-primary btn-xs activity-btn", ngClick: "showActivity()", awToolTip: "View Activity Stream", + awFeature: 'activity_streams', dataPlacement: "top", icon: "icon-comments-alt", mode: 'edit', diff --git a/awx/ui/static/js/forms/Projects.js b/awx/ui/static/js/forms/Projects.js index 14d6595b13..e56095bcf3 100644 --- a/awx/ui/static/js/forms/Projects.js +++ b/awx/ui/static/js/forms/Projects.js @@ -41,6 +41,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) 'class': "btn-primary btn-xs activity-btn", ngClick: "showActivity()", awToolTip: "View Activity Stream", + awFeature: 'activity_streams', dataPlacement: "top", icon: "icon-comments-alt", mode: 'edit', diff --git a/awx/ui/static/js/forms/Teams.js b/awx/ui/static/js/forms/Teams.js index fa12e26385..8783401631 100644 --- a/awx/ui/static/js/forms/Teams.js +++ b/awx/ui/static/js/forms/Teams.js @@ -30,6 +30,7 @@ export default 'class': "btn-primary btn-xs activity-btn", ngClick: "showActivity()", awToolTip: "View Activity Stream", + awFeature: 'activity_streams', dataPlacement: "top", icon: "icon-comments-alt", mode: 'edit', diff --git a/awx/ui/static/js/forms/Users.js b/awx/ui/static/js/forms/Users.js index 3d882ee784..86793fd90e 100644 --- a/awx/ui/static/js/forms/Users.js +++ b/awx/ui/static/js/forms/Users.js @@ -31,6 +31,7 @@ export default 'class': "btn-primary btn-xs activity-btn", ngClick: "showActivity()", awToolTip: "View Activity Stream", + awFeature: 'activity_streams', dataPlacement: "top", icon: "icon-comments-alt", mode: 'edit', @@ -114,7 +115,8 @@ export default ldap_user: { label: 'Created by LDAP', type: 'checkbox', - readonly: true + readonly: true, + awFeature: 'ldap' } }, diff --git a/awx/ui/static/js/lists/Credentials.js b/awx/ui/static/js/lists/Credentials.js index bc2ac87090..47d79677df 100644 --- a/awx/ui/static/js/lists/Credentials.js +++ b/awx/ui/static/js/lists/Credentials.js @@ -53,7 +53,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/lists/HomeGroups.js b/awx/ui/static/js/lists/HomeGroups.js index 0d1d294cc2..d732f2eb18 100644 --- a/awx/ui/static/js/lists/HomeGroups.js +++ b/awx/ui/static/js/lists/HomeGroups.js @@ -169,7 +169,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'all' + mode: 'all', + awFeature: 'activity_streams' } } diff --git a/awx/ui/static/js/lists/HomeHosts.js b/awx/ui/static/js/lists/HomeHosts.js index 992a46a7b8..3e7a15e3fa 100644 --- a/awx/ui/static/js/lists/HomeHosts.js +++ b/awx/ui/static/js/lists/HomeHosts.js @@ -102,7 +102,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'all' + mode: 'all', + awFeature: 'activity_streams' } } diff --git a/awx/ui/static/js/lists/Inventories.js b/awx/ui/static/js/lists/Inventories.js index 6fc95a4b50..5091c344a3 100644 --- a/awx/ui/static/js/lists/Inventories.js +++ b/awx/ui/static/js/lists/Inventories.js @@ -92,7 +92,8 @@ export default ngClick: "showActivity()", awToolTip: "View Activity Stream", icon: "icon-comments-alt", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/lists/InventoryGroups.js b/awx/ui/static/js/lists/InventoryGroups.js index 8d283cfa61..d405f6caa2 100644 --- a/awx/ui/static/js/lists/InventoryGroups.js +++ b/awx/ui/static/js/lists/InventoryGroups.js @@ -112,7 +112,8 @@ export default stream: { ngClick: "showGroupActivity()", awToolTip: "View Activity Stream", - mode: 'all' + mode: 'all', + awFeature: 'activity_streams' }, help: { mode: 'all', diff --git a/awx/ui/static/js/lists/InventoryHosts.js b/awx/ui/static/js/lists/InventoryHosts.js index 6bd7d69b09..bbc3ba4c31 100644 --- a/awx/ui/static/js/lists/InventoryHosts.js +++ b/awx/ui/static/js/lists/InventoryHosts.js @@ -101,7 +101,8 @@ export default stream: { ngClick: "showHostActivity()", awToolTip: "View Activity Stream", - mode: 'all' + mode: 'all', + awFeature: 'activity_streams' } } diff --git a/awx/ui/static/js/lists/JobTemplates.js b/awx/ui/static/js/lists/JobTemplates.js index 536768a692..6c2822c04e 100644 --- a/awx/ui/static/js/lists/JobTemplates.js +++ b/awx/ui/static/js/lists/JobTemplates.js @@ -54,6 +54,7 @@ export default ngClick: "showActivity()", awToolTip: "View Activity Stream", icon: "icon-comments-alt", + awFeature: 'activity_streams', mode: 'edit', ngHide: 'portalMode===true' } diff --git a/awx/ui/static/js/lists/Organizations.js b/awx/ui/static/js/lists/Organizations.js index e4f577cf55..7acea076dc 100644 --- a/awx/ui/static/js/lists/Organizations.js +++ b/awx/ui/static/js/lists/Organizations.js @@ -40,12 +40,14 @@ export default add: { mode: 'all', // One of: edit, select, all ngClick: 'addOrganization()', - awToolTip: 'Create a new organization' + awToolTip: 'Create a new organization', + awFeature: 'multiple_organizations' }, stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/lists/Permissions.js b/awx/ui/static/js/lists/Permissions.js index 0188366ee2..da2d362ffd 100644 --- a/awx/ui/static/js/lists/Permissions.js +++ b/awx/ui/static/js/lists/Permissions.js @@ -56,7 +56,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/lists/Projects.js b/awx/ui/static/js/lists/Projects.js index 1b8cd0c352..84adf22b17 100644 --- a/awx/ui/static/js/lists/Projects.js +++ b/awx/ui/static/js/lists/Projects.js @@ -75,7 +75,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/lists/ScanJobs.js b/awx/ui/static/js/lists/ScanJobs.js index 33b1d6b0f8..319a0873b6 100644 --- a/awx/ui/static/js/lists/ScanJobs.js +++ b/awx/ui/static/js/lists/ScanJobs.js @@ -48,7 +48,8 @@ export default awToolTip: "View Activity Stream", icon: "icon-comments-alt", mode: 'edit', - ngHide: 'portalMode===true' + ngHide: 'portalMode===true', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/lists/Schedules.js b/awx/ui/static/js/lists/Schedules.js index 0b4e156bfd..52e94d6187 100644 --- a/awx/ui/static/js/lists/Schedules.js +++ b/awx/ui/static/js/lists/Schedules.js @@ -67,7 +67,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/lists/Teams.js b/awx/ui/static/js/lists/Teams.js index 8540d175f5..c159786ab4 100644 --- a/awx/ui/static/js/lists/Teams.js +++ b/awx/ui/static/js/lists/Teams.js @@ -52,7 +52,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/lists/Users.js b/awx/ui/static/js/lists/Users.js index de539a622f..0e5d4b078f 100644 --- a/awx/ui/static/js/lists/Users.js +++ b/awx/ui/static/js/lists/Users.js @@ -50,7 +50,8 @@ export default stream: { ngClick: "showActivity()", awToolTip: "View Activity Stream", - mode: 'edit' + mode: 'edit', + awFeature: 'activity_streams' } }, diff --git a/awx/ui/static/js/shared/AuthService.js b/awx/ui/static/js/shared/AuthService.js index aab7f41aea..d1672404e4 100644 --- a/awx/ui/static/js/shared/AuthService.js +++ b/awx/ui/static/js/shared/AuthService.js @@ -95,6 +95,7 @@ angular.module('AuthService', ['ngCookies', Utilities.name]) }, getLicense: function () { + //check in here first to see if license is already obtained, if we do have it, then rootScope.license return $http({ method: 'GET', url: GetBasePath('config'), @@ -109,6 +110,7 @@ angular.module('AuthService', ['ngCookies', Utilities.name]) license.version = data.version; license.tested = false; Store('license', license); + $rootScope.features = Store('license').features; }, licenseTested: function () { diff --git a/awx/ui/static/js/shared/features/features.controller.js b/awx/ui/static/js/shared/features/features.controller.js new file mode 100644 index 0000000000..ad15be4771 --- /dev/null +++ b/awx/ui/static/js/shared/features/features.controller.js @@ -0,0 +1,10 @@ +export default ['$rootScope', function ($rootScope) { + + this.isFeatureEnabled = function(feature){ + if($rootScope.features[feature] === false){ + return false; + } else { + return true; + } + }; +}]; diff --git a/awx/ui/static/js/shared/features/features.directive.js b/awx/ui/static/js/shared/features/features.directive.js new file mode 100644 index 0000000000..a5fa6f5873 --- /dev/null +++ b/awx/ui/static/js/shared/features/features.directive.js @@ -0,0 +1,15 @@ +import featureController from 'tower/shared/features/features.controller'; +export default [ function() { + return { + restrict: 'A', + controller: featureController, + link: function (scope, element, attrs, controller){ + if(attrs.awFeature.length > 0){ + if(!controller.isFeatureEnabled(attrs.awFeature)){ + element.remove(); + } + } + } + + }; +}]; diff --git a/awx/ui/static/js/shared/features/features.service.js b/awx/ui/static/js/shared/features/features.service.js new file mode 100644 index 0000000000..2251e561b6 --- /dev/null +++ b/awx/ui/static/js/shared/features/features.service.js @@ -0,0 +1,30 @@ +export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$http', '$q', +function ($rootScope, Rest, GetBasePath, ProcessErrors, $http, $q) { + return { + getFeatures: function(){ + var promise; + Rest.setUrl(GetBasePath('config')); + promise = Rest.get(); + return promise.then(function (data) { + $rootScope.features = data.data.license_info.features; + return $rootScope.features; + }).catch(function (response) { + ProcessErrors($rootScope, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get license info. GET returned status: ' + + response.status + }); + }); + + }, + get: function(){ + if(_.isEmpty($rootScope.features)){ + return this.getFeatures(); + } else { + // $q.when will ensure that the result is returned + // as a resovled promise. + return $q.when($rootScope.features); + } + } + }; +}]; diff --git a/awx/ui/static/js/shared/features/main.js b/awx/ui/static/js/shared/features/main.js new file mode 100644 index 0000000000..ad7cb50cdf --- /dev/null +++ b/awx/ui/static/js/shared/features/main.js @@ -0,0 +1,6 @@ +import awFeatureDirective from 'tower/shared/features/features.directive'; +import FeaturesService from 'tower/shared/features/features.service'; +export default + angular.module('features', []) + .directive('awFeature', awFeatureDirective) + .service('FeaturesService', FeaturesService); diff --git a/awx/ui/static/js/shared/form-generator.js b/awx/ui/static/js/shared/form-generator.js index 50d24d561e..e8f89f3e56 100644 --- a/awx/ui/static/js/shared/form-generator.js +++ b/awx/ui/static/js/shared/form-generator.js @@ -752,6 +752,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "'"; html += (field.ngShow) ? this.attr(field, 'ngShow') : ""; html += (field.ngHide) ? this.attr(field, 'ngHide') : ""; + html += (field.awFeature) ? "aw-feature=\"" + field.awFeature + "\" " : ""; html += ">\n"; //text fields diff --git a/awx/ui/static/js/shared/list-generator/list-actions.partial.html b/awx/ui/static/js/shared/list-generator/list-actions.partial.html index 01749fdd01..e59af015cc 100644 --- a/awx/ui/static/js/shared/list-generator/list-actions.partial.html +++ b/awx/ui/static/js/shared/list-generator/list-actions.partial.html @@ -16,8 +16,10 @@ ng-hide="isHiddenByOptions(options) || hiddenOnCurrentPage(options.basePaths) || hiddenInCurrentMode(options.mode)" - toolbar="true"> + toolbar="true" + aw-feature="{{options.awFeature}}"> + diff --git a/awx/ui/tests/unit/features/features.controller-test.js b/awx/ui/tests/unit/features/features.controller-test.js new file mode 100644 index 0000000000..ac131334a1 --- /dev/null +++ b/awx/ui/tests/unit/features/features.controller-test.js @@ -0,0 +1,25 @@ +import featuresController from 'tower/shared/features/features.controller'; + +describe('featuresController', function() { + + it('checks if a feature is enabled', inject(['$rootScope', function($rootScope) { + var actual; + + $rootScope.features = { + activity_streams: true, + ldap: false + }; + + // TODO: extract into test controller in describeModule + var Controller = featuresController[1]; + var controller = new Controller($rootScope); + + actual = controller.isFeatureEnabled('activity_streams'); + expect(actual).to.be.true; + + actual = controller.isFeatureEnabled('ldap'); + expect(actual).to.be.false; + + + }])); +}) diff --git a/awx/ui/tests/unit/features/features.service-test.js b/awx/ui/tests/unit/features/features.service-test.js new file mode 100644 index 0000000000..26804f3254 --- /dev/null +++ b/awx/ui/tests/unit/features/features.service-test.js @@ -0,0 +1,55 @@ +import features from 'tower/shared/features/main'; +import {describeModule} from '../describe-module'; + +//test that it returns features, as well as test that it is returned in rootScope + +describeModule(features.name) + .testService('FeaturesService', function(test, restStub) { + + var service; + + test.withService(function(_service) { + service = _service; + }); + + it('returns list of features', function() { + var features = {}, + result = { + data: { + license_info: { + features: features + } + } + }; + + var actual = service.get(); + + restStub.succeed(result); + restStub.flush(); + + return expect(actual).to.eventually.equal(features); + + }); + + it('caches in rootScope', inject(['$rootScope', + function($rootScope){ + var features = {}, + result = { + data: { + license_info: { + features: features + } + } + }; + + var actual = service.get(); + + restStub.succeed(result); + restStub.flush(); + + return actual.then(function(){ + expect($rootScope.features).to.equal(features); + }); + }])); + + }); diff --git a/nodemon.json b/nodemon.json index 6ceb91b153..0ab79edb96 100644 --- a/nodemon.json +++ b/nodemon.json @@ -6,7 +6,8 @@ "awx/ui/static/docs/**/*" ], "watch": [ - "awx/ui/static" + "awx/ui/static", + "awx/ui/tests" ], "ext": "js json less html" }