Add invite by email (WIP)
This commit is contained in:
parent
f15b3f345e
commit
ae92098b23
5 changed files with 91 additions and 7 deletions
|
@ -231,6 +231,32 @@ class TeamMember(ApiResource):
|
||||||
raise Unauthorized()
|
raise Unauthorized()
|
||||||
|
|
||||||
|
|
||||||
|
@resource('/v1/organization/<orgname>/team/<teamname>/invite/<email>')
|
||||||
|
class InviteTeamMember(ApiResource):
|
||||||
|
""" Resource for inviting a team member via email address. """
|
||||||
|
@require_scope(scopes.ORG_ADMIN)
|
||||||
|
@nickname('inviteTeamMemberEmail')
|
||||||
|
def put(self, orgname, teamname, email):
|
||||||
|
""" Invites an email address to an existing team. """
|
||||||
|
permission = AdministerOrganizationPermission(orgname)
|
||||||
|
if permission.can():
|
||||||
|
team = None
|
||||||
|
|
||||||
|
# Find the team.
|
||||||
|
try:
|
||||||
|
team = model.get_organization_team(orgname, teamname)
|
||||||
|
except model.InvalidTeamException:
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
|
# Invite the email to the team.
|
||||||
|
inviter = get_authenticated_user()
|
||||||
|
invite = handle_addinvite_team(inviter, team, email=email)
|
||||||
|
log_action('org_invite_team_member', orgname, {'email': email, 'team': teamname})
|
||||||
|
return invite_view(invite)
|
||||||
|
|
||||||
|
raise Unauthorized()
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/teaminvite/<code>')
|
@resource('/v1/teaminvite/<code>')
|
||||||
@internal_only
|
@internal_only
|
||||||
class TeamMemberInvite(ApiResource):
|
class TeamMemberInvite(ApiResource):
|
||||||
|
|
|
@ -3544,6 +3544,12 @@ p.editable:hover i {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tt-message {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.tt-suggestion p {
|
.tt-suggestion p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
<div class="entity-search"
|
<div class="entity-search"
|
||||||
namespace="orgname" placeholder="'Add a registered user or robot...'"
|
namespace="orgname" placeholder="'Add a registered user or robot...'"
|
||||||
entity-selected="addNewMember(entity)"
|
entity-selected="addNewMember(entity)"
|
||||||
|
email-selected="inviteEmail(email)"
|
||||||
current-entity="selectedMember"
|
current-entity="selectedMember"
|
||||||
auto-clear="true"
|
auto-clear="true"
|
||||||
allowed-entities="['user', 'robot']"
|
allowed-entities="['user', 'robot']"
|
||||||
pull-right="true"
|
pull-right="true"
|
||||||
|
allow-emails="true"
|
||||||
|
email-message="Press enter to invite the entered e-mail address to this team"
|
||||||
ng-show="!addingMember"></div>
|
ng-show="!addingMember"></div>
|
||||||
<div class="quay-spinner" ng-show="addingMember"></div>
|
<div class="quay-spinner" ng-show="addingMember"></div>
|
||||||
<div class="help-text" ng-show="!addingMember">
|
<div class="help-text" ng-show="!addingMember">
|
||||||
|
|
|
@ -399,6 +399,11 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
||||||
$provide.factory('UtilService', ['$sanitize', function($sanitize) {
|
$provide.factory('UtilService', ['$sanitize', function($sanitize) {
|
||||||
var utilService = {};
|
var utilService = {};
|
||||||
|
|
||||||
|
utilService.isEmailAddress = function(val) {
|
||||||
|
var emailRegex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
|
||||||
|
return emailRegex.test(val);
|
||||||
|
};
|
||||||
|
|
||||||
utilService.escapeHtmlString = function(text) {
|
utilService.escapeHtmlString = function(text) {
|
||||||
var adjusted = text.replace(/&/g, "&")
|
var adjusted = text.replace(/&/g, "&")
|
||||||
.replace(/</g, "<")
|
.replace(/</g, "<")
|
||||||
|
@ -3478,7 +3483,9 @@ quayApp.directive('entitySearch', function () {
|
||||||
'allowedEntities': '=allowedEntities',
|
'allowedEntities': '=allowedEntities',
|
||||||
|
|
||||||
'currentEntity': '=currentEntity',
|
'currentEntity': '=currentEntity',
|
||||||
|
|
||||||
'entitySelected': '&entitySelected',
|
'entitySelected': '&entitySelected',
|
||||||
|
'emailSelected': '&emailSelected',
|
||||||
|
|
||||||
// When set to true, the contents of the control will be cleared as soon
|
// When set to true, the contents of the control will be cleared as soon
|
||||||
// as an entity is selected.
|
// as an entity is selected.
|
||||||
|
@ -3487,9 +3494,14 @@ quayApp.directive('entitySearch', function () {
|
||||||
// Set this property to immediately clear the contents of the control.
|
// Set this property to immediately clear the contents of the control.
|
||||||
'clearValue': '=clearValue',
|
'clearValue': '=clearValue',
|
||||||
|
|
||||||
|
// Whether e-mail addresses are allowed.
|
||||||
|
'allowEmails': '@allowEmails',
|
||||||
|
'emailMessage': '@emailMessage',
|
||||||
|
|
||||||
|
// True if the menu should pull right.
|
||||||
'pullRight': '@pullRight'
|
'pullRight': '@pullRight'
|
||||||
},
|
},
|
||||||
controller: function($rootScope, $scope, $element, Restangular, UserService, ApiService, Config) {
|
controller: function($rootScope, $scope, $element, Restangular, UserService, ApiService, UtilService, Config) {
|
||||||
$scope.lazyLoading = true;
|
$scope.lazyLoading = true;
|
||||||
|
|
||||||
$scope.teams = null;
|
$scope.teams = null;
|
||||||
|
@ -3686,9 +3698,13 @@ quayApp.directive('entitySearch', function () {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.indexOf('@') > 0) {
|
if (UtilService.isEmailAddress(val)) {
|
||||||
|
if ($scope.allowEmails) {
|
||||||
|
return '<div class="tt-message">' + $scope.emailMessage + '</div>';
|
||||||
|
} else {
|
||||||
return '<div class="tt-empty">A ' + Config.REGISTRY_TITLE_SHORT + ' username (not an e-mail address) must be specified</div>';
|
return '<div class="tt-empty">A ' + Config.REGISTRY_TITLE_SHORT + ' username (not an e-mail address) must be specified</div>';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var classes = [];
|
var classes = [];
|
||||||
|
|
||||||
|
@ -3743,6 +3759,16 @@ quayApp.directive('entitySearch', function () {
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(input).on('keypress', function(e) {
|
||||||
|
var val = $(input).val();
|
||||||
|
var code = e.keyCode || e.which;
|
||||||
|
if (code == 13 && $scope.allowEmails && UtilService.isEmailAddress(val)) {
|
||||||
|
$scope.$apply(function() {
|
||||||
|
$scope.emailSelected({'email': val});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(input).on('input', function(e) {
|
$(input).on('input', function(e) {
|
||||||
$scope.$apply(function() {
|
$scope.$apply(function() {
|
||||||
$scope.clearEntityInternal();
|
$scope.clearEntityInternal();
|
||||||
|
|
|
@ -2371,6 +2371,28 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.inviteEmail = function(email) {
|
||||||
|
if (!email || $scope.memberMap[email]) { return; }
|
||||||
|
|
||||||
|
$scope.addingMember = true;
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
'orgname': orgname,
|
||||||
|
'teamname': teamname,
|
||||||
|
'email': email
|
||||||
|
};
|
||||||
|
|
||||||
|
var errorHandler = ApiService.errorDisplay('Cannot invite team member', function() {
|
||||||
|
$scope.addingMember = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
ApiService.inviteTeamMemberEmail(null, params).then(function(resp) {
|
||||||
|
$scope.members.push(resp);
|
||||||
|
$scope.memberMap[resp.name] = resp;
|
||||||
|
$scope.addingMember = false;
|
||||||
|
}, errorHandler);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.addNewMember = function(member) {
|
$scope.addNewMember = function(member) {
|
||||||
if (!member || $scope.memberMap[member.name]) { return; }
|
if (!member || $scope.memberMap[member.name]) { return; }
|
||||||
|
|
||||||
|
@ -2380,15 +2402,16 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams)
|
||||||
'membername': member.name
|
'membername': member.name
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var errorHandler = ApiService.errorDisplay('Cannot add team member', function() {
|
||||||
|
$scope.addingMember = false;
|
||||||
|
});
|
||||||
|
|
||||||
$scope.addingMember = true;
|
$scope.addingMember = true;
|
||||||
ApiService.updateOrganizationTeamMember(null, params).then(function(resp) {
|
ApiService.updateOrganizationTeamMember(null, params).then(function(resp) {
|
||||||
$scope.members.push(resp);
|
$scope.members.push(resp);
|
||||||
$scope.memberMap[resp.name] = resp;
|
$scope.memberMap[resp.name] = resp;
|
||||||
$scope.addingMember = false;
|
$scope.addingMember = false;
|
||||||
}, function() {
|
}, errorHandler);
|
||||||
$('#cannotChangeMembersModal').modal({});
|
|
||||||
$scope.addingMember = false;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeMember = function(username) {
|
$scope.removeMember = function(username) {
|
||||||
|
|
Reference in a new issue