Merge "Add UI for making security teams related to projects"
This commit is contained in:
commit
b9f8711367
|
@ -18,28 +18,36 @@
|
|||
* Edit/view team controller
|
||||
*/
|
||||
angular.module('sb.admin').controller('TeamEditController',
|
||||
function($scope, team, members, $state, Team, User, $q) {
|
||||
function($scope, team, members, projects, $state, Team, User, Project,
|
||||
DSCacheFactory, storyboardApiBase, $q) {
|
||||
'use strict';
|
||||
|
||||
$scope.team = team;
|
||||
$scope.members = members;
|
||||
$scope.projects = projects;
|
||||
$scope.editing = false;
|
||||
$scope.isUpdating = false;
|
||||
|
||||
$scope.save = function() {
|
||||
$scope.isUpdating = true;
|
||||
$scope.team.$update(function() {
|
||||
$scope.team.$update(function(updated) {
|
||||
DSCacheFactory.get('defaultCache').put(
|
||||
storyboardApiBase + '/teams/' + $scope.team.id,
|
||||
updated);
|
||||
$scope.isUpdating = false;
|
||||
$scope.editing = false;
|
||||
});
|
||||
};
|
||||
|
||||
var oldName = $scope.team.name;
|
||||
var oldSecurity = $scope.team.security;
|
||||
$scope.toggleEdit = function() {
|
||||
if (!$scope.editing) {
|
||||
oldName = $scope.team.name;
|
||||
oldSecurity = $scope.team.security;
|
||||
} else if ($scope.editing) {
|
||||
$scope.team.name = oldName;
|
||||
$scope.team.security = oldSecurity;
|
||||
}
|
||||
$scope.editing = !$scope.editing;
|
||||
};
|
||||
|
@ -53,6 +61,10 @@ angular.module('sb.admin').controller('TeamEditController',
|
|||
Team.UsersController.create({
|
||||
team_id: $scope.team.id,
|
||||
user_id: model.id
|
||||
}, function() {
|
||||
DSCacheFactory.get('defaultCache').remove(
|
||||
storyboardApiBase + '/teams/' +
|
||||
$scope.team.id + '/users');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -62,6 +74,10 @@ angular.module('sb.admin').controller('TeamEditController',
|
|||
Team.UsersController.delete({
|
||||
team_id: $scope.team.id,
|
||||
user_id: user.id
|
||||
}, function() {
|
||||
DSCacheFactory.get('defaultCache').remove(
|
||||
storyboardApiBase + '/teams/' +
|
||||
$scope.team.id + '/users');
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -87,4 +103,56 @@ angular.module('sb.admin').controller('TeamEditController',
|
|||
);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
$scope.toggleAddProject = function() {
|
||||
$scope.addingProject = !$scope.addingProject;
|
||||
};
|
||||
|
||||
$scope.addProject = function(model) {
|
||||
$scope.projects.push(model);
|
||||
Team.ProjectsController.create({
|
||||
team_id: $scope.team.id,
|
||||
project_id: model.id
|
||||
}, function() {
|
||||
DSCacheFactory.get('defaultCache').remove(
|
||||
storyboardApiBase + '/teams/' +
|
||||
$scope.team.id + '/projects');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeProject = function(project) {
|
||||
var idx = $scope.projects.indexOf(project);
|
||||
$scope.projects.splice(idx, 1);
|
||||
Team.ProjectsController.delete({
|
||||
team_id: $scope.team.id,
|
||||
project_id: project.id
|
||||
}, function() {
|
||||
DSCacheFactory.get('defaultCache').remove(
|
||||
storyboardApiBase + '/teams/' +
|
||||
$scope.team.id + '/projects');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Project typeahead search method.
|
||||
*/
|
||||
$scope.searchProjects = function(value) {
|
||||
var projectIds = $scope.projects.map(function(project) {
|
||||
return project.id;
|
||||
});
|
||||
var deferred = $q.defer();
|
||||
|
||||
Project.browse({name: value, limit: 10},
|
||||
function(searchResults) {
|
||||
var results = [];
|
||||
angular.forEach(searchResults, function(result) {
|
||||
if (projectIds.indexOf(result.id) === -1) {
|
||||
results.push(result);
|
||||
}
|
||||
});
|
||||
deferred.resolve(results);
|
||||
}
|
||||
);
|
||||
return deferred.promise;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
* New Team modal controller.
|
||||
*/
|
||||
angular.module('sb.admin').controller('TeamNewController',
|
||||
function ($log, $scope, $modalInstance, Team, User, $q) {
|
||||
function ($log, $scope, $modalInstance, Team, User, Project, $q) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
|
@ -42,6 +42,13 @@ angular.module('sb.admin').controller('TeamNewController',
|
|||
*/
|
||||
$scope.members = [];
|
||||
|
||||
/**
|
||||
* The projects related to the new team.
|
||||
*
|
||||
* @type {[Project]}
|
||||
*/
|
||||
$scope.projects = [];
|
||||
|
||||
/**
|
||||
* Saves the team
|
||||
*/
|
||||
|
@ -78,7 +85,7 @@ angular.module('sb.admin').controller('TeamNewController',
|
|||
* Toggle the "Add Member" text box.
|
||||
*/
|
||||
$scope.toggleAddMember = function() {
|
||||
$scope.adding = !$scope.adding;
|
||||
$scope.addingMember = !$scope.addingMember;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -118,4 +125,40 @@ angular.module('sb.admin').controller('TeamNewController',
|
|||
);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
$scope.toggleAddProject = function() {
|
||||
$scope.addingProject = !$scope.addingProject;
|
||||
};
|
||||
|
||||
$scope.addProject = function(model) {
|
||||
$scope.projects.push(model);
|
||||
};
|
||||
|
||||
$scope.removeProject = function(project) {
|
||||
var idx = $scope.projects.indexOf(project);
|
||||
$scope.projects.splice(idx, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Project typeahead search method.
|
||||
*/
|
||||
$scope.searchProjects = function(value) {
|
||||
var projectIds = $scope.projects.map(function(project) {
|
||||
return project.id;
|
||||
});
|
||||
var deferred = $q.defer();
|
||||
|
||||
Project.browse({name: value, limit: 10},
|
||||
function(searchResults) {
|
||||
var results = [];
|
||||
angular.forEach(searchResults, function(result) {
|
||||
if (projectIds.indexOf(result.id) === -1) {
|
||||
results.push(result);
|
||||
}
|
||||
});
|
||||
deferred.resolve(results);
|
||||
}
|
||||
);
|
||||
return deferred.promise;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -76,6 +76,11 @@ angular.module('sb.admin', [ 'sb.services', 'sb.templates', 'sb.util',
|
|||
return Team.UsersController.get({
|
||||
team_id: $stateParams.id
|
||||
}).$promise;
|
||||
},
|
||||
projects: function($stateParams, Team) {
|
||||
return Team.ProjectsController.get({
|
||||
team_id: $stateParams.id
|
||||
}).$promise;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||
~ Copyright (c) 2016 Codethink Ltd
|
||||
~ Copyright (c) 2016, 2019 Codethink Ltd
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
~ not use this file except in compliance with the License. You may obtain
|
||||
|
@ -19,126 +19,225 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h1 ng-show="!editing">
|
||||
<i class="fa fa-sb-team"></i> {{team.name}}
|
||||
<i class="fa fa-sb-team text-muted"></i> 
|
||||
<span view-title>{{ team.name }}</span>
|
||||
<small>
|
||||
<a ng-click="toggleEdit()">
|
||||
<a href ng-click="toggleEdit()">
|
||||
<i class="fa fa-pencil-alt"></i>
|
||||
</a>
|
||||
</small>
|
||||
</h1>
|
||||
<form name="teamForm" role="form" ng-show="editing">
|
||||
<hr ng-if="editing">
|
||||
<form name="teamForm" role="form" class="form-horizontal" ng-show="editing">
|
||||
<div class="form-group has-feedback"
|
||||
ng-class="{'has-error': teamForm.name.$invalid,
|
||||
'has-success': !teamForm.name.$invalid}">
|
||||
<div class="input-group">
|
||||
<div class="clearfix feedback-wrapper">
|
||||
<input id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
class="form-control context-edit h1"
|
||||
ng-model="team.name"
|
||||
autocomplete="off"
|
||||
required
|
||||
autofocus
|
||||
ng-disabled="isUpdating"
|
||||
ng-pattern="PROJECT_NAME_REGEX"
|
||||
ng-minlength="3"
|
||||
maxlength="255"
|
||||
placeholder="Team Name" />
|
||||
<span class="form-control-feedback"
|
||||
ng-show="teamForm.name.$invalid">
|
||||
<i class="fa fa-times fa-lg"></i>
|
||||
<label for="name" class="col-sm-2 control-label">
|
||||
Name
|
||||
</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<input id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
class="form-control"
|
||||
ng-model="team.name"
|
||||
autofocus
|
||||
required
|
||||
ng-disabled="isSaving"
|
||||
ng-pattern="PROJECT_NAME_REGEX"
|
||||
ng-minlength="3"
|
||||
maxlength="255"
|
||||
placeholder="The team name.">
|
||||
<span class="form-control-feedback"
|
||||
ng-show="teamForm.name.$invalid">
|
||||
<i class="fa fa-times fa-lg"></i>
|
||||
</span>
|
||||
<span class="form-control-feedback"
|
||||
ng-show="!teamForm.name.$invalid">
|
||||
<i class="fa fa-check fa-lg"></i>
|
||||
</span>
|
||||
<div class="help-block text-danger"
|
||||
ng-show="teamForm.name.$invalid">
|
||||
<span ng-show="teamForm.name.$error.required">
|
||||
A team name is required.
|
||||
</span>
|
||||
<span class="form-control-feedback"
|
||||
ng-show="!teamForm.name.$invalid">
|
||||
<i class="fa fa-check fa-lg"></i>
|
||||
<span ng-show="teamForm.name.$error.pattern">
|
||||
A team name must begin with a letter, and may only
|
||||
contain letters, numbers, forward slashes, periods, and
|
||||
dashes. It should not start or end with a separator and
|
||||
must not contain two or more sequential separators.
|
||||
</span>
|
||||
<span ng-show="teamForm.name.$error.minlength">
|
||||
A team name must have at least 3 characters.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="security"
|
||||
class="col-sm-2 control-label">
|
||||
Security Team
|
||||
</label>
|
||||
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-lg h1"
|
||||
type="button"
|
||||
ng-click="save()"
|
||||
ng-disabled="!teamForm.$valid || isUpdating">
|
||||
<i class="fa fa-check"></i>
|
||||
<span class="hidden-xs">Save</span>
|
||||
</button>
|
||||
<button class="btn btn-danger btn-lg h1"
|
||||
type="button"
|
||||
ng-click="toggleEdit()"
|
||||
ng-disabled="isUpdating">
|
||||
<i class="fa fa-times"></i>
|
||||
<span class="hidden-xs">Cancel</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="help-block text-danger"
|
||||
ng-show="teamForm.name.$invalid">
|
||||
<span ng-show="teamForm.name.$error.required">
|
||||
A team name is required.
|
||||
</span>
|
||||
<span ng-show="teamForm.name.$error.pattern">
|
||||
A team name must begin with a letter, and may only
|
||||
contain letters, numbers, forward slashes, periods, and
|
||||
dashes. It should not start or end with a separator and
|
||||
must not contain two or more sequential separators.
|
||||
</span>
|
||||
<span ng-show="teamForm.name.$error.minlength">
|
||||
A team name must have at least 3 characters.
|
||||
</span>
|
||||
<div class="col-sm-10 checkbox">
|
||||
<input id="security"
|
||||
type="checkbox"
|
||||
class="modal-checkbox"
|
||||
ng-model="team.security"
|
||||
ng-disabled="isSaving"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-primary"
|
||||
type="button"
|
||||
ng-click="save()"
|
||||
ng-disabled="!teamForm.$valid || isUpdating">
|
||||
<i class="fa fa-check"></i>
|
||||
<span class="hidden-xs">Save</span>
|
||||
</button>
|
||||
<button class="btn btn-default"
|
||||
type="button"
|
||||
ng-click="toggleEdit()"
|
||||
ng-disabled="isUpdating">
|
||||
<i class="fa fa-times"></i>
|
||||
<span class="hidden-xs">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-if="!editing">
|
||||
<div class="col-xs-12" ng-if="team.security">
|
||||
<h3>
|
||||
<i class="fa fa-lock text-muted"></i> 
|
||||
Security Team
|
||||
</h3>
|
||||
<p class="text-muted">
|
||||
This team will automatically be granted permissions to
|
||||
view and edit security stories affecting any of their
|
||||
related projects.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12" ng-if="!team.security">
|
||||
<p class="text-muted">
|
||||
This team has no special permissions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-xs-offset-3">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Team Members</th>
|
||||
<th class="text-right">
|
||||
<a href ng-click="toggleAddMember()">
|
||||
<i class="fa fa-plus" ng-show="!adding"></i>
|
||||
<i class="fa fa-minus" ng-show="adding"></i>
|
||||
Add member
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="member in members">
|
||||
<td colspan="2">
|
||||
{{member.full_name}}
|
||||
<a href class="close"
|
||||
ng-click="removeUser(member)">
|
||||
×
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!adding && members.length < 1">
|
||||
<td colspan="2">This team has no members yet.</td>
|
||||
</tr>
|
||||
<tr ng-show="adding">
|
||||
<td colspan="2">
|
||||
<div class="col-sm-6">
|
||||
<div class="card">
|
||||
<h3>
|
||||
<i class="fa fa-sb-user fa-lg text-muted"></i> 
|
||||
Team Members
|
||||
</h3>
|
||||
<div class="card-row" ng-repeat="member in members">
|
||||
<a href class="close"
|
||||
ng-click="removeUser(member)">
|
||||
×
|
||||
</a>
|
||||
<a href="#!/admin/user/{{ member.id }}">
|
||||
{{ member.full_name }}
|
||||
</a>
|
||||
<p class="text-muted">
|
||||
{{ member.email }}
|
||||
</p>
|
||||
</div>
|
||||
<p ng-show="!adding && members.length == 0"
|
||||
class="card-row text-center text-muted">
|
||||
<em>
|
||||
This team has no members yet.
|
||||
</em>
|
||||
</p>
|
||||
<p class="card-row text-center">
|
||||
<button class="btn btn-link" type="button"
|
||||
ng-click="toggleAddMember()"
|
||||
ng-if="!adding">
|
||||
<i class="fa fa-plus"></i>
|
||||
Add member
|
||||
</button>
|
||||
<span class="input-group" ng-if="adding">
|
||||
<input id="user"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
placeholder="Click to add a user"
|
||||
placeholder="Start typing to add a user"
|
||||
ng-model="asyncUser"
|
||||
typeahead-wait-ms="200"
|
||||
typeahead-editable="false"
|
||||
typeahead="user as user.full_name for user in
|
||||
searchUsers($viewValue)"
|
||||
searchUsers($viewValue)"
|
||||
typeahead-loading="loadingUsers"
|
||||
typeahead-input-formatter="formatUserName($model)"
|
||||
typeahead-on-select="addUser($model)"
|
||||
class="form-control input-sm"
|
||||
class="form-control"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button"
|
||||
ng-click="toggleAddMember()">
|
||||
Cancel
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="card">
|
||||
<h3>
|
||||
<i class="fa fa-sb-project fa-lg text-muted"></i> 
|
||||
Related Projects
|
||||
</h3>
|
||||
<div class="card-row" ng-repeat="project in projects">
|
||||
<a href class="close"
|
||||
ng-click="removeProject(project)">
|
||||
×
|
||||
</a>
|
||||
<a href="#!/project/{{ project.name }}">
|
||||
{{ project.name }}
|
||||
</a>
|
||||
<p class="text-muted">
|
||||
{{ project.description }}
|
||||
</p>
|
||||
</div>
|
||||
<p ng-show="!adding && projects.length == 0"
|
||||
class="card-row text-center text-muted">
|
||||
<em>
|
||||
This team isn't related to any projects yet.
|
||||
</em>
|
||||
</p>
|
||||
<p class="card-row text-center">
|
||||
<button class="btn btn-link" type="button"
|
||||
ng-click="toggleAddProject()"
|
||||
ng-if="!addingProject">
|
||||
<i class="fa fa-plus"></i>
|
||||
Add project
|
||||
</button>
|
||||
<span class="input-group" ng-if="addingProject">
|
||||
<input id="user"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
placeholder="Start typing to add a project"
|
||||
ng-model="asyncUser"
|
||||
typeahead-wait-ms="200"
|
||||
typeahead-editable="false"
|
||||
typeahead="project as project.name for project in
|
||||
searchProjects($viewValue)"
|
||||
typeahead-loading="loadingProjects"
|
||||
typeahead-input-formatter="formatProjectName($model)"
|
||||
typeahead-on-select="addProject($model)"
|
||||
class="form-control"
|
||||
/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button"
|
||||
ng-click="toggleAddProject()">
|
||||
Cancel
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -71,6 +71,21 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="security"
|
||||
class="col-sm-3 control-label">
|
||||
Security Team
|
||||
</label>
|
||||
|
||||
<div class="col-sm-9 checkbox">
|
||||
<input id="security"
|
||||
type="checkbox"
|
||||
class="modal-checkbox"
|
||||
ng-model="team.security"
|
||||
ng-disabled="isSaving"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -82,8 +97,8 @@
|
|||
<th>Team Members</th>
|
||||
<th class="text-right">
|
||||
<a href ng-click="toggleAddMember()">
|
||||
<i class="fa fa-plus" ng-show="!adding"></i>
|
||||
<i class="fa fa-minus" ng-show="adding"></i>
|
||||
<i class="fa fa-plus" ng-show="!addingMember"></i>
|
||||
<i class="fa fa-minus" ng-show="addingMember"></i>
|
||||
Add member
|
||||
</a>
|
||||
</th>
|
||||
|
@ -99,10 +114,10 @@
|
|||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!adding && members.length < 1">
|
||||
<tr ng-if="!addingMember && members.length < 1">
|
||||
<td colspan="2">This team has no members yet.</td>
|
||||
</tr>
|
||||
<tr ng-show="adding">
|
||||
<tr ng-show="addingMember">
|
||||
<td colspan="2">
|
||||
<input id="user"
|
||||
type="text"
|
||||
|
@ -124,6 +139,56 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Related Projects</th>
|
||||
<th class="text-right">
|
||||
<a href ng-click="toggleAddProject()">
|
||||
<i class="fa fa-plus" ng-show="!addingProject"></i>
|
||||
<i class="fa fa-minus" ng-show="addingProject"></i>
|
||||
Add project
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="project in projects">
|
||||
<td colspan="2">
|
||||
{{ project.name }}
|
||||
<a href class="close"
|
||||
ng-click="removeProject(project)">
|
||||
×
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="!addingProject && projects.length < 1">
|
||||
<td colspan="2">This team has no related projects yet.</td>
|
||||
</tr>
|
||||
<tr ng-show="addingProject">
|
||||
<td colspan="2">
|
||||
<input id="user"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
placeholder="Start typing to add a project"
|
||||
ng-model="asyncUser"
|
||||
typeahead-wait-ms="200"
|
||||
typeahead-editable="false"
|
||||
typeahead="project as project.name for project in
|
||||
searchProjects($viewValue)"
|
||||
typeahead-loading="loadingProjects"
|
||||
typeahead-input-formatter="formatProjectName($model)"
|
||||
typeahead-on-select="addProject($model)"
|
||||
class="form-control input-sm"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-right">
|
||||
<button type="button"
|
||||
|
|
Loading…
Reference in New Issue