From 79b79f0481c604bb514020c86fb771aa571ea3d6 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 21 Apr 2015 09:09:20 -0400 Subject: [PATCH 1/3] 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" } From 88d444ead7e539477688b95614472dfd239d22a8 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Mon, 4 May 2015 14:46:52 -0400 Subject: [PATCH 2/3] adding short doc explanation for aw-feature directive --- .../js/shared/features/features.directive.js | 18 ++++++++++++++++++ awx/ui/static/js/shared/features/main.js | 1 + 2 files changed, 19 insertions(+) diff --git a/awx/ui/static/js/shared/features/features.directive.js b/awx/ui/static/js/shared/features/features.directive.js index a5fa6f5873..7f349cc00a 100644 --- a/awx/ui/static/js/shared/features/features.directive.js +++ b/awx/ui/static/js/shared/features/features.directive.js @@ -1,4 +1,22 @@ +/** + * @ngdoc overview + * @name features + * @scope + * @description enables/disables features based on license + * + * @ngdoc directive + * @name features.directive:awFeature + * @description The aw-feature directive works by taking in a string + * that maps to a license feature, and removes that feature from the + * DOM if it is a feature not supported by the user's license. + * For example, adding `aw-feature="system-tracking"` will enable or disable + * the system tracking button based on the license configuration on the + * /config endpoint. + * + * +*/ import featureController from 'tower/shared/features/features.controller'; + export default [ function() { return { restrict: 'A', diff --git a/awx/ui/static/js/shared/features/main.js b/awx/ui/static/js/shared/features/main.js index ad7cb50cdf..f94224864d 100644 --- a/awx/ui/static/js/shared/features/main.js +++ b/awx/ui/static/js/shared/features/main.js @@ -1,5 +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) From 263dafd84a5f6fbb531d50809ee6d32a342fbec0 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 5 May 2015 10:32:01 -0400 Subject: [PATCH 3/3] fixing spelling error on home.partial for activity streams --- 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 c46a30cfa2..21ce6c07c2 100644 --- a/awx/ui/static/partials/home.html +++ b/awx/ui/static/partials/home.html @@ -20,7 +20,7 @@ aw-tool-tip="View Activity Stream" icon-name="stream" toolbar="true" - aw-feature="activity_stream"> + aw-feature="activity_streams">