US #49 (taiga-subscriptions): Transfer project ownership
parent
f3d93e7a0f
commit
2e4a63fe75
|
@ -11,6 +11,7 @@
|
||||||
- Add badge to project owners
|
- Add badge to project owners
|
||||||
- Limit of user per project.
|
- Limit of user per project.
|
||||||
- Redesign of the create project wizard
|
- Redesign of the create project wizard
|
||||||
|
- Transfer project ownership
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
- Lots of small and not so small bugfixes.
|
- Lots of small and not so small bugfixes.
|
||||||
|
|
|
@ -333,6 +333,16 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
$routeProvider.when("/project/:pslug/admin/contrib/:plugin",
|
$routeProvider.when("/project/:pslug/admin/contrib/:plugin",
|
||||||
{templateUrl: "contrib/main.html"})
|
{templateUrl: "contrib/main.html"})
|
||||||
|
|
||||||
|
# Transfer project
|
||||||
|
$routeProvider.when("/project/:pslug/transfer/:token",
|
||||||
|
{
|
||||||
|
templateUrl: "projects/transfer/transfer-page.html",
|
||||||
|
loader: true,
|
||||||
|
controller: "Project",
|
||||||
|
controllerAs: "vm"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# User settings
|
# User settings
|
||||||
$routeProvider.when("/user-settings/user-profile",
|
$routeProvider.when("/user-settings/user-profile",
|
||||||
{templateUrl: "user/user-profile.html"})
|
{templateUrl: "user/user-profile.html"})
|
||||||
|
|
|
@ -136,3 +136,144 @@ LightboxAddMembersWarningMessageDirective = () ->
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgLightboxAddMembersWarningMessage", [LightboxAddMembersWarningMessageDirective])
|
module.directive("tgLightboxAddMembersWarningMessage", [LightboxAddMembersWarningMessageDirective])
|
||||||
|
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
## Transfer project ownership
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
LbRequestOwnershipDirective = (lightboxService, rs, confirmService, $translate) ->
|
||||||
|
return {
|
||||||
|
link: (scope, el) ->
|
||||||
|
lightboxService.open(el)
|
||||||
|
|
||||||
|
scope.request = () ->
|
||||||
|
scope.loading = true
|
||||||
|
|
||||||
|
rs.projects.transferRequest(scope.projectId).then () ->
|
||||||
|
scope.loading = false
|
||||||
|
|
||||||
|
lightboxService.close(el)
|
||||||
|
|
||||||
|
confirmService.notify("success", $translate.instant("ADMIN.PROJECT_PROFILE.REQUEST_OWNERSHIP_SUCCESS"))
|
||||||
|
|
||||||
|
templateUrl: "common/lightbox/lightbox-request-ownership.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive('tgLbRequestOwnership', [
|
||||||
|
"lightboxService",
|
||||||
|
"tgResources",
|
||||||
|
"$tgConfirm",
|
||||||
|
"$translate",
|
||||||
|
LbRequestOwnershipDirective])
|
||||||
|
|
||||||
|
class ChangeOwnerLightboxController
|
||||||
|
constructor: (@rs, @lightboxService, @confirm, @translate) ->
|
||||||
|
@.users = []
|
||||||
|
@.q = ""
|
||||||
|
@.commentOpen = false
|
||||||
|
|
||||||
|
limit: 3
|
||||||
|
|
||||||
|
normalizeString: (normalizedString) ->
|
||||||
|
normalizedString = normalizedString.replace("Á", "A").replace("Ä", "A").replace("À", "A")
|
||||||
|
normalizedString = normalizedString.replace("É", "E").replace("Ë", "E").replace("È", "E")
|
||||||
|
normalizedString = normalizedString.replace("Í", "I").replace("Ï", "I").replace("Ì", "I")
|
||||||
|
normalizedString = normalizedString.replace("Ó", "O").replace("Ö", "O").replace("Ò", "O")
|
||||||
|
normalizedString = normalizedString.replace("Ú", "U").replace("Ü", "U").replace("Ù", "U")
|
||||||
|
return normalizedString
|
||||||
|
|
||||||
|
filterUsers: (user) ->
|
||||||
|
username = user.full_name_display.toUpperCase()
|
||||||
|
username = @.normalizeString(username)
|
||||||
|
text = @.q.toUpperCase()
|
||||||
|
text = @.normalizeString(text)
|
||||||
|
|
||||||
|
return _.includes(username, text)
|
||||||
|
|
||||||
|
getUsers: () ->
|
||||||
|
if !@.users.length && !@.q.length
|
||||||
|
users = @.activeUsers
|
||||||
|
else
|
||||||
|
users = @.users
|
||||||
|
|
||||||
|
users = users.slice(0, @.limit)
|
||||||
|
users = _.reject(users, {"selected": true})
|
||||||
|
|
||||||
|
return _.reject(users, {"id": @.currentOwnerId})
|
||||||
|
|
||||||
|
userSearch: () ->
|
||||||
|
@.users = @.activeUsers
|
||||||
|
|
||||||
|
@.selected = _.find(@.users, {"selected": true})
|
||||||
|
|
||||||
|
@.users = _.filter(@.users, @.filterUsers.bind(this)) if @.q
|
||||||
|
|
||||||
|
selectUser: (user) ->
|
||||||
|
@.activeUsers = _.map @.activeUsers, (user) ->
|
||||||
|
user.selected = false
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
user.selected = true
|
||||||
|
|
||||||
|
@.userSearch()
|
||||||
|
|
||||||
|
submit: () ->
|
||||||
|
@.loading = true
|
||||||
|
@rs.projects.transferStart(@.projectId, @.selected.id, @.comment)
|
||||||
|
.then () =>
|
||||||
|
@.loading = false
|
||||||
|
@lightboxService.closeAll()
|
||||||
|
|
||||||
|
title = @translate.instant("ADMIN.PROJECT_PROFILE.CHANGE_OWNER_SUCCESS_TITLE")
|
||||||
|
desc = @translate.instant("ADMIN.PROJECT_PROFILE.CHANGE_OWNER_SUCCESS_DESC")
|
||||||
|
|
||||||
|
@confirm.success(title, desc, {
|
||||||
|
type: "svg",
|
||||||
|
name: "icon-speak-up"
|
||||||
|
})
|
||||||
|
|
||||||
|
ChangeOwnerLightboxController.$inject = [
|
||||||
|
"tgResources",
|
||||||
|
"lightboxService",
|
||||||
|
"$tgConfirm",
|
||||||
|
"$translate"
|
||||||
|
]
|
||||||
|
|
||||||
|
module.controller('ChangeOwnerLightbox', ChangeOwnerLightboxController)
|
||||||
|
|
||||||
|
ChangeOwnerLightboxDirective = (lightboxService, lightboxKeyboardNavigationService, $template, $compile) ->
|
||||||
|
link = (scope, el) ->
|
||||||
|
lightboxService.open(el)
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: true,
|
||||||
|
controller: "ChangeOwnerLightbox",
|
||||||
|
controllerAs: "vm",
|
||||||
|
bindToController: {
|
||||||
|
currentOwnerId: "=",
|
||||||
|
projectId: "=",
|
||||||
|
activeUsers: "="
|
||||||
|
},
|
||||||
|
templateUrl: "common/lightbox/lightbox-change-owner.html"
|
||||||
|
link:link
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.directive("tgLbChangeOwner", ["lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", ChangeOwnerLightboxDirective])
|
||||||
|
|
||||||
|
TransferProjectStartSuccessDirective = (lightboxService) ->
|
||||||
|
link = (scope, el) ->
|
||||||
|
scope.close = () ->
|
||||||
|
lightboxService.close(el)
|
||||||
|
|
||||||
|
lightboxService.open(el)
|
||||||
|
|
||||||
|
return {
|
||||||
|
templateUrl: "common/lightbox/lightbox-transfer-project-start-success.html"
|
||||||
|
link:link
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.directive("tgLbTransferProjectStartSuccess", ["lightboxService", TransferProjectStartSuccessDirective])
|
||||||
|
|
|
@ -129,7 +129,10 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
||||||
members: @scope.project.max_memberships
|
members: @scope.project.max_memberships
|
||||||
})
|
})
|
||||||
icon = "/" + window._version + "/svg/icons/team-question.svg"
|
icon = "/" + window._version + "/svg/icons/team-question.svg"
|
||||||
@confirm.success(title, message,icon)
|
@confirm.success(title, message, {
|
||||||
|
name: icon,
|
||||||
|
type: "img"
|
||||||
|
})
|
||||||
|
|
||||||
module.controller("MembershipsController", MembershipsController)
|
module.controller("MembershipsController", MembershipsController)
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,8 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
description = @scope.project.description
|
description = @scope.project.description
|
||||||
@appMetaService.setAll(title, description)
|
@appMetaService.setAll(title, description)
|
||||||
|
|
||||||
|
@.fillUsersAndRoles(@scope.project.members, @scope.project.roles)
|
||||||
|
|
||||||
promise.then null, @.onInitialDataError.bind(@)
|
promise.then null, @.onInitialDataError.bind(@)
|
||||||
|
|
||||||
@scope.$on "project:loaded", =>
|
@scope.$on "project:loaded", =>
|
||||||
|
@ -535,3 +537,47 @@ AdminProjectRestrictionsDirective = () ->
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive('tgAdminProjectRestrictions', [AdminProjectRestrictionsDirective])
|
module.directive('tgAdminProjectRestrictions', [AdminProjectRestrictionsDirective])
|
||||||
|
|
||||||
|
AdminProjectRequestOwnershipDirective = (lightboxFactory) ->
|
||||||
|
return {
|
||||||
|
link: (scope) ->
|
||||||
|
scope.requestOwnership = () ->
|
||||||
|
lightboxFactory.create("tg-lb-request-ownership", {
|
||||||
|
"class": "lightbox lightbox-request-ownership"
|
||||||
|
}, {
|
||||||
|
projectId: scope.projectId
|
||||||
|
})
|
||||||
|
|
||||||
|
scope: {
|
||||||
|
"projectId": "=",
|
||||||
|
"owner": "="
|
||||||
|
},
|
||||||
|
templateUrl: "admin/admin-project-request-ownership.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive('tgAdminProjectRequestOwnership', ["tgLightboxFactory", AdminProjectRequestOwnershipDirective])
|
||||||
|
|
||||||
|
AdminProjectChangeOwnerDirective = (lightboxFactory) ->
|
||||||
|
return {
|
||||||
|
link: (scope) ->
|
||||||
|
scope.changeOwner = () ->
|
||||||
|
lightboxFactory.create("tg-lb-change-owner", {
|
||||||
|
"class": "lightbox lightbox-select-user",
|
||||||
|
"project-id": "projectId",
|
||||||
|
"active-users": "activeUsers",
|
||||||
|
"current-owner-id": "currentOwnerId"
|
||||||
|
}, {
|
||||||
|
projectId: scope.projectId,
|
||||||
|
activeUsers: scope.activeUsers,
|
||||||
|
currentOwnerId: scope.owner.id
|
||||||
|
})
|
||||||
|
|
||||||
|
scope: {
|
||||||
|
"activeUsers": "="
|
||||||
|
"projectId": "="
|
||||||
|
"owner": "="
|
||||||
|
},
|
||||||
|
templateUrl: "admin/admin-project-change-owner.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive('tgAdminProjectChangeOwner', ["tgLightboxFactory", AdminProjectChangeOwnerDirective])
|
||||||
|
|
|
@ -167,8 +167,20 @@ class ConfirmService extends taiga.Service
|
||||||
el = angular.element(".lightbox-generic-success")
|
el = angular.element(".lightbox-generic-success")
|
||||||
|
|
||||||
el.find("img").remove()
|
el.find("img").remove()
|
||||||
|
el.find("svg").remove()
|
||||||
|
|
||||||
|
if icon.type == "img"
|
||||||
|
detailImage = $('<img>').addClass('lb-icon').attr('src', icon.name)
|
||||||
|
else if icon.type == "svg"
|
||||||
|
useSVG = document.createElementNS('http://www.w3.org/2000/svg', 'use')
|
||||||
|
useSVG.setAttributeNS('http://www.w3.org/1999/xlink','href', '#' + icon.name)
|
||||||
|
|
||||||
|
detailImage = document.createElementNS("http://www.w3.org/2000/svg", "svg")
|
||||||
|
detailImage.classList.add("icon")
|
||||||
|
detailImage.classList.add("lb-icon")
|
||||||
|
detailImage.classList.add(icon.name)
|
||||||
|
detailImage.appendChild(useSVG)
|
||||||
|
|
||||||
detailImage = $('<img>').addClass('lb-icon').attr('src', icon)
|
|
||||||
if detailImage
|
if detailImage
|
||||||
el.find('section').prepend(detailImage)
|
el.find('section').prepend(detailImage)
|
||||||
|
|
||||||
|
@ -254,6 +266,7 @@ class ConfirmService extends taiga.Service
|
||||||
body.find(selector)
|
body.find(selector)
|
||||||
.removeClass('active')
|
.removeClass('active')
|
||||||
.addClass('inactive')
|
.addClass('inactive')
|
||||||
|
.one 'animationend', () -> $(this).removeClass('inactive')
|
||||||
|
|
||||||
delete @.tsem
|
delete @.tsem
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,11 @@ urls = {
|
||||||
"project-unlike": "/projects/%s/unlike"
|
"project-unlike": "/projects/%s/unlike"
|
||||||
"project-watch": "/projects/%s/watch"
|
"project-watch": "/projects/%s/watch"
|
||||||
"project-unwatch": "/projects/%s/unwatch"
|
"project-unwatch": "/projects/%s/unwatch"
|
||||||
|
"project-transfer-validate-token": "/projects/%s/transfer_validate_token"
|
||||||
|
"project-transfer-accept": "/projects/%s/transfer_accept"
|
||||||
|
"project-transfer-reject": "/projects/%s/transfer_reject"
|
||||||
|
"project-transfer-request": "/projects/%s/transfer_request"
|
||||||
|
"project-transfer-start": "/projects/%s/transfer_start"
|
||||||
|
|
||||||
# Project Values - Choises
|
# Project Values - Choises
|
||||||
"userstory-statuses": "/userstory-statuses"
|
"userstory-statuses": "/userstory-statuses"
|
||||||
|
|
|
@ -472,7 +472,16 @@
|
||||||
"MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects",
|
"MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects",
|
||||||
"MAX_PRIVATE_PROJECTS_MEMBERS": "The project exceeds the maximum members number in private projects",
|
"MAX_PRIVATE_PROJECTS_MEMBERS": "The project exceeds the maximum members number in private projects",
|
||||||
"MAX_PUBLIC_PROJECTS": "You've reached the maximum number of public projects",
|
"MAX_PUBLIC_PROJECTS": "You've reached the maximum number of public projects",
|
||||||
"MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds the maximum members number in public projects"
|
"MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds the maximum members number in public projects",
|
||||||
|
"PROJECT_OWNER": "Project owner",
|
||||||
|
"REQUEST_OWNERSHIP": "Request ownership",
|
||||||
|
"REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Do you want to be the project owner?",
|
||||||
|
"REQUEST_OWNERSHIP_DESC": "Ask the owner {{name}} to transfert to you the project ownership.",
|
||||||
|
"REQUEST_OWNERSHIP_BUTTON": "Request",
|
||||||
|
"REQUEST_OWNERSHIP_SUCCESS": "We'll notify the project owner",
|
||||||
|
"CHANGE_OWNER": "Change owner",
|
||||||
|
"CHANGE_OWNER_SUCCESS_TITLE": "Ok, your request has been sent",
|
||||||
|
"CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email whether it accepts or rejects the ownership of the project."
|
||||||
},
|
},
|
||||||
"REPORTS": {
|
"REPORTS": {
|
||||||
"TITLE": "Reports",
|
"TITLE": "Reports",
|
||||||
|
@ -680,6 +689,24 @@
|
||||||
},
|
},
|
||||||
"SUBMENU_THIDPARTIES": {
|
"SUBMENU_THIDPARTIES": {
|
||||||
"TITLE": "Services"
|
"TITLE": "Services"
|
||||||
|
},
|
||||||
|
"PROJECT_TRANSFER": {
|
||||||
|
"DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Do you want to be the new project owner?",
|
||||||
|
"PRIVATE": "Private",
|
||||||
|
"ACCEPTED_PROJECT_OWNERNSHIP": "OK. Now you are the new owner of the project.",
|
||||||
|
"REJECTED_PROJECT_OWNERNSHIP": "OK. We will contact with the owner",
|
||||||
|
"ACCEPT": "Accept",
|
||||||
|
"REJECT": "Reject",
|
||||||
|
"PROPOSE_OWNERSHIP": "<strong>{{owner}}</strong>, the current owner of the project <strong>{{project}}</strong> wants you to be the new owner of the project.",
|
||||||
|
"ADD_COMMENT_QUESTION": "Do you want to add a comment for the owner?",
|
||||||
|
"ADD_COMMENT": "Do you want to add a comment for the owner?",
|
||||||
|
"UNLIMITED_PROJECTS": "Unlimited",
|
||||||
|
"OWNER_MESSAGE": {
|
||||||
|
"PRIVATE": "Remember, you can own up to <strong>{{maxProjects}}</strong> private projects and you already own <strong>{{currentProjects}}</strong> private projects",
|
||||||
|
"PUBLIC": "Remember, you can own <strong>{{maxProjects}}</strong> public projects and you already own <strong>{{currentProjects}}</strong> public projects"
|
||||||
|
},
|
||||||
|
"CANT_BE_OWNED": "Right now you can't be the owner of a project with this characteristics. To be the owner of this project you should contact the admin staff and change your account conditions.",
|
||||||
|
"CHANGE_MY_PLAN": "Change my plan"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"USER": {
|
"USER": {
|
||||||
|
@ -932,6 +959,11 @@
|
||||||
"DESC": "You can't delete the project owner, you must request a new owner before deleting the user.",
|
"DESC": "You can't delete the project owner, you must request a new owner before deleting the user.",
|
||||||
"BUTTON": "Request change project owner"
|
"BUTTON": "Request change project owner"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"CHANGE_OWNER": {
|
||||||
|
"TITLE": "Who do you want to be the new owner?",
|
||||||
|
"ADD_COMMENT": "Add comment",
|
||||||
|
"BUTTON": "Ask this teammate to be the owner"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"US": {
|
"US": {
|
||||||
|
|
|
@ -63,4 +63,14 @@ class ProjectsService extends taiga.Service
|
||||||
bulkUpdateProjectsOrder: (sortData) ->
|
bulkUpdateProjectsOrder: (sortData) ->
|
||||||
return @rs.projects.bulkUpdateOrder(sortData)
|
return @rs.projects.bulkUpdateOrder(sortData)
|
||||||
|
|
||||||
|
transferValidateToken: (projectId, token) ->
|
||||||
|
return @rs.projects.transferValidateToken(projectId, token)
|
||||||
|
|
||||||
|
transferAccept: (projectId, token, reason) ->
|
||||||
|
return @rs.projects.transferAccept(projectId, token, reason)
|
||||||
|
|
||||||
|
transferReject: (projectId, token, reason) ->
|
||||||
|
return @rs.projects.transferReject(projectId, token, reason)
|
||||||
|
|
||||||
|
|
||||||
angular.module("taigaProjects").service("tgProjectsService", ProjectsService)
|
angular.module("taigaProjects").service("tgProjectsService", ProjectsService)
|
||||||
|
|
|
@ -163,3 +163,16 @@ describe "tgProjectsService", ->
|
||||||
])
|
])
|
||||||
|
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
it "validateTransferToken", (done) ->
|
||||||
|
projectId = 3
|
||||||
|
|
||||||
|
tokenValidation = Immutable.fromJS({})
|
||||||
|
|
||||||
|
mocks.resources.projects = {}
|
||||||
|
mocks.resources.projects.transferValidateToken = sinon.stub()
|
||||||
|
mocks.resources.projects.transferValidateToken.withArgs(projectId).promise().resolve(tokenValidation)
|
||||||
|
|
||||||
|
projectsService.transferValidateToken(projectId).then (projects) ->
|
||||||
|
expect(projects.toJS()).to.be.eql({})
|
||||||
|
done()
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
###
|
||||||
|
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# File: cant-own-project-explanation.directive.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
CantOwnProjectExplanationDirective = () ->
|
||||||
|
return {
|
||||||
|
templateUrl: "projects/transfer/cant-own-project-explanation.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module("taigaProjects").directive("tgCantOwnProjectExplanation", CantOwnProjectExplanationDirective)
|
|
@ -0,0 +1,3 @@
|
||||||
|
p(
|
||||||
|
translate="ADMIN.PROJECT_TRANSFER.CANT_BE_OWNED"
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
tg-transfer-project.transfer-project(
|
||||||
|
ng-if="vm.project"
|
||||||
|
project = "vm.project"
|
||||||
|
)
|
|
@ -0,0 +1,98 @@
|
||||||
|
###
|
||||||
|
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# File: transfer-project.directive.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
module = angular.module('taigaProjects')
|
||||||
|
|
||||||
|
class TransferProject
|
||||||
|
@.$inject = [
|
||||||
|
"$routeParams",
|
||||||
|
"tgProjectsService"
|
||||||
|
"$location",
|
||||||
|
"$tgAuth",
|
||||||
|
"tgCurrentUserService",
|
||||||
|
"$tgNavUrls",
|
||||||
|
"$translate",
|
||||||
|
"$tgConfirm"
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor: (@routeParams, @projectService, @location, @authService, @currentUserService, @navUrls, @translate, @confirmService) ->
|
||||||
|
@.projectId = @.project.get("id")
|
||||||
|
@.token = @routeParams.token
|
||||||
|
@._refreshUserData()
|
||||||
|
@.showAddComment = false
|
||||||
|
|
||||||
|
_validateToken: () ->
|
||||||
|
@projectService.transferValidateToken(@.projectId, @.token).error (data, status) =>
|
||||||
|
@location.path(@navUrls.resolve("not-found"))
|
||||||
|
|
||||||
|
_refreshUserData: () ->
|
||||||
|
@authService.refresh().then () =>
|
||||||
|
@._validateToken()
|
||||||
|
@._setProjectData()
|
||||||
|
@._checkOwnerData()
|
||||||
|
|
||||||
|
_setProjectData: () ->
|
||||||
|
@.canBeOwnedByUser = @currentUserService.canOwnProject(@.project)
|
||||||
|
|
||||||
|
_checkOwnerData: () ->
|
||||||
|
currentUser = @currentUserService.getUser()
|
||||||
|
if(@.project.get('is_private'))
|
||||||
|
@.ownerMessage = 'ADMIN.PROJECT_TRANSFER.OWNER_MESSAGE.PRIVATE'
|
||||||
|
@.maxProjects = currentUser.get('max_private_projects')
|
||||||
|
if @.maxProjects == null
|
||||||
|
@.maxProjects = @translate.instant('ADMIN.PROJECT_TRANSFER.UNLIMITED_PROJECTS')
|
||||||
|
@.currentProjects = currentUser.get('total_private_projects')
|
||||||
|
maxMemberships = currentUser.get('max_memberships_private_projects')
|
||||||
|
|
||||||
|
else
|
||||||
|
@.ownerMessage = 'ADMIN.PROJECT_TRANSFER.OWNER_MESSAGE.PUBLIC'
|
||||||
|
@.maxProjects = currentUser.get('max_public_projects')
|
||||||
|
if @.maxProjects == null
|
||||||
|
@.maxProjects = @translate.instant('ADMIN.PROJECT_TRANSFER.UNLIMITED_PROJECTS')
|
||||||
|
@.currentProjects = currentUser.get('total_public_projects')
|
||||||
|
maxMemberships = currentUser.get('max_memberships_public_projects')
|
||||||
|
|
||||||
|
@.validNumberOfMemberships = maxMemberships == null || @.project.get('total_memberships') <= maxMemberships
|
||||||
|
|
||||||
|
transferAccept: (token, reason) ->
|
||||||
|
@projectService.transferAccept(@.project.get("id"), token, reason).success () =>
|
||||||
|
newUrl = @navUrls.resolve("project-admin-project-profile-details", {
|
||||||
|
project: @.project.get("slug")
|
||||||
|
})
|
||||||
|
@location.path(newUrl)
|
||||||
|
@confirmService.notify("success", @translate.instant("ADMIN.PROJECT_TRANSFER.ACCEPTED_PROJECT_OWNERNSHIP"), '', 5000)
|
||||||
|
|
||||||
|
transferReject: (token, reason) ->
|
||||||
|
@projectService.transferReject(@.project.get("id"), token, reason).success () =>
|
||||||
|
newUrl = @navUrls.resolve("project-admin-project-profile-details", {
|
||||||
|
project: @project.get("slug")
|
||||||
|
})
|
||||||
|
@location.path(newUrl)
|
||||||
|
@confirmService.notify("success", @translate.instant("ADMIN.PROJECT_TRANSFER.REJECTED_PROJECT_OWNERNSHIP"), '', 5000)
|
||||||
|
|
||||||
|
addComment: () ->
|
||||||
|
@.showAddComment = true
|
||||||
|
|
||||||
|
hideComment: () ->
|
||||||
|
@.showAddComment = false
|
||||||
|
@.reason = ''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.controller("TransferProjectController", TransferProject)
|
|
@ -0,0 +1,33 @@
|
||||||
|
###
|
||||||
|
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# File: transfer-project.directive.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
module = angular.module('taigaProjects')
|
||||||
|
|
||||||
|
TransferProjectDirective = () ->
|
||||||
|
return {
|
||||||
|
scope: {},
|
||||||
|
bindToController: {
|
||||||
|
project: "="
|
||||||
|
},
|
||||||
|
templateUrl: "projects/transfer/transfer-project.html",
|
||||||
|
controller: 'TransferProjectController',
|
||||||
|
controllerAs: 'vm'
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive('tgTransferProject', TransferProjectDirective)
|
|
@ -0,0 +1,69 @@
|
||||||
|
.transfer-project-wrapper
|
||||||
|
h2.transfer-title(translate="ADMIN.PROJECT_TRANSFER.DO_YOU_ACCEPT_PROJECT_OWNERNSHIP")
|
||||||
|
.transfer-project-detail
|
||||||
|
img.transfer-project-image(
|
||||||
|
tg-project-logo-small-src="vm.project"
|
||||||
|
alt="{{vm.project.get('name')}}"
|
||||||
|
)
|
||||||
|
.transfer-project-data
|
||||||
|
h3.transfer-project-title {{::vm.project.get("name")}}
|
||||||
|
.transfer-project-statistics
|
||||||
|
span.transfer-project-private(ng-if="vm.project.get('is_private')")
|
||||||
|
svg.icon.icon-lock
|
||||||
|
use(xlink:href="#icon-lock")
|
||||||
|
span(translate="ADMIN.PROJECT_TRANSFER.PRIVATE")
|
||||||
|
span.transfer-project-members
|
||||||
|
svg.icon.icon-team
|
||||||
|
use(xlink:href="#icon-team")
|
||||||
|
span {{::vm.project.get("members").size}}
|
||||||
|
|
||||||
|
p(
|
||||||
|
translate="ADMIN.PROJECT_TRANSFER.PROPOSE_OWNERSHIP"
|
||||||
|
translate-values="{owner: vm.project.getIn(['owner', 'full_name_display']), project: vm.project.get('name')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
div(ng-if="vm.canBeOwnedByUser.valid")
|
||||||
|
|
||||||
|
p(
|
||||||
|
translate="{{vm.ownerMessage}}"
|
||||||
|
translate-values="{maxProjects: vm.maxProjects, currentProjects: vm.currentProjects}"
|
||||||
|
)
|
||||||
|
|
||||||
|
a.transfer-project-comment-link.ng-animate-disabled(
|
||||||
|
href=""
|
||||||
|
ng-click="vm.addComment()"
|
||||||
|
ng-if="!vm.showAddComment"
|
||||||
|
translate="ADMIN.PROJECT_TRANSFER.ADD_COMMENT_QUESTION"
|
||||||
|
)
|
||||||
|
|
||||||
|
fieldset.transfer-project-comment-form(
|
||||||
|
ng-if="vm.showAddComment"
|
||||||
|
ng-class="{'open': vm.showAddComment}"
|
||||||
|
)
|
||||||
|
.transfer-project-comment-header
|
||||||
|
label.transfer-project-comment-label(
|
||||||
|
translate="ADMIN.PROJECT_TRANSFER.ADD_COMMENT"
|
||||||
|
)
|
||||||
|
svg.icon.icon-close(ng-click="vm.hideComment()")
|
||||||
|
use(xlink:href="#icon-close")
|
||||||
|
textarea.transfer-project-comment(
|
||||||
|
name="reason"
|
||||||
|
ng-model="vm.reason"
|
||||||
|
)
|
||||||
|
|
||||||
|
.transfer-project-options
|
||||||
|
a.button.button-gray(
|
||||||
|
ng-click="vm.transferReject(vm.token, vm.reason)"
|
||||||
|
href="#"
|
||||||
|
title="{{'ADMIN.PROJECT_TRANSFER.REJECT' | translate}}"
|
||||||
|
translate="ADMIN.PROJECT_TRANSFER.REJECT"
|
||||||
|
)
|
||||||
|
|
||||||
|
a.button.button-green(
|
||||||
|
ng-click="vm.transferAccept(vm.token, vm.reason)"
|
||||||
|
href="#"
|
||||||
|
title="{{'ADMIN.PROJECT_TRANSFER.ACCEPT' | translate}}"
|
||||||
|
translate="ADMIN.PROJECT_TRANSFER.ACCEPT"
|
||||||
|
)
|
||||||
|
|
||||||
|
div(ng-if="!vm.canBeOwnedByUser.valid", tg-cant-own-project-explanation)
|
|
@ -0,0 +1,93 @@
|
||||||
|
.transfer-project-wrapper {
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transfer-project {
|
||||||
|
align-items: center;
|
||||||
|
background: url('../images/discover.png') bottom center repeat-x;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: calc(100vh - 40px);
|
||||||
|
.transfer-title {
|
||||||
|
@extend %light;
|
||||||
|
}
|
||||||
|
&-detail {
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid $whitish;
|
||||||
|
border-top: 1px solid $whitish;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1rem 0 3rem;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
&-image {
|
||||||
|
margin-right: 1rem;
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
&-title {
|
||||||
|
@extend %light;
|
||||||
|
@extend %larger;
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
}
|
||||||
|
&-statistics {
|
||||||
|
span {
|
||||||
|
color: $gray-light;
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
fill: $gray-light;
|
||||||
|
margin-right: .25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-private {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
&-comment-link {
|
||||||
|
color: $primary;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
&:hover {
|
||||||
|
color: $primary-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-comment-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.icon-close {
|
||||||
|
cursor: pointer;
|
||||||
|
fill: $gray-light;
|
||||||
|
&:hover {
|
||||||
|
fill: $red-light;
|
||||||
|
transition: fill .2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-comment-form {
|
||||||
|
&.ng-enter {
|
||||||
|
animation: dropdownFade .2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-comment-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
&-comment {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
min-height: 6rem;
|
||||||
|
}
|
||||||
|
&-options {
|
||||||
|
display: flex;
|
||||||
|
a {
|
||||||
|
@extend %large;
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
|
padding: .75rem;
|
||||||
|
&:first-child {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,6 +108,42 @@ Resource = (urlsService, http, paginateResponseService) ->
|
||||||
url = urlsService.resolve("project-unwatch", projectId)
|
url = urlsService.resolve("project-unwatch", projectId)
|
||||||
return http.post(url)
|
return http.post(url)
|
||||||
|
|
||||||
|
service.transferValidateToken = (projectId, token) ->
|
||||||
|
data = {
|
||||||
|
token: token
|
||||||
|
}
|
||||||
|
url = urlsService.resolve("project-transfer-validate-token", projectId)
|
||||||
|
return http.post(url, data)
|
||||||
|
|
||||||
|
service.transferAccept = (projectId, token, reason) ->
|
||||||
|
data = {
|
||||||
|
token: token
|
||||||
|
reason: reason
|
||||||
|
}
|
||||||
|
url = urlsService.resolve("project-transfer-accept", projectId)
|
||||||
|
return http.post(url, data)
|
||||||
|
|
||||||
|
service.transferReject = (projectId, token, reason) ->
|
||||||
|
data = {
|
||||||
|
token: token
|
||||||
|
reason: reason
|
||||||
|
}
|
||||||
|
url = urlsService.resolve("project-transfer-reject", projectId)
|
||||||
|
return http.post(url, data)
|
||||||
|
|
||||||
|
service.transferRequest = (projectId) ->
|
||||||
|
url = urlsService.resolve("project-transfer-request", projectId)
|
||||||
|
return http.post(url)
|
||||||
|
|
||||||
|
service.transferStart = (projectId, userId, reason) ->
|
||||||
|
data = {
|
||||||
|
user: userId,
|
||||||
|
reason: reason
|
||||||
|
}
|
||||||
|
|
||||||
|
url = urlsService.resolve("project-transfer-start", projectId)
|
||||||
|
return http.post(url, data)
|
||||||
|
|
||||||
return () ->
|
return () ->
|
||||||
return {"projects": service}
|
return {"projects": service}
|
||||||
|
|
||||||
|
|
|
@ -120,8 +120,7 @@ class CurrentUserService
|
||||||
|
|
||||||
canCreatePrivateProjects: () ->
|
canCreatePrivateProjects: () ->
|
||||||
user = @.getUser()
|
user = @.getUser()
|
||||||
|
if user.get('max_private_projects') != null && user.get('total_private_projects') >= user.get('max_private_projects')
|
||||||
if user.get('max_private_projects') != null && user.get('max_private_projects') <= user.get('total_private_projects')
|
|
||||||
return {valid: false, reason: 'max_private_projects', type: 'private_project'}
|
return {valid: false, reason: 'max_private_projects', type: 'private_project'}
|
||||||
|
|
||||||
return {valid: true}
|
return {valid: true}
|
||||||
|
@ -129,9 +128,27 @@ class CurrentUserService
|
||||||
canCreatePublicProjects: () ->
|
canCreatePublicProjects: () ->
|
||||||
user = @.getUser()
|
user = @.getUser()
|
||||||
|
|
||||||
if user.get('max_public_projects') != null && user.get('max_public_projects') <= user.get('total_public_projects')
|
if user.get('max_public_projects') != null && user.get('total_public_projects') >= user.get('max_public_projects')
|
||||||
return {valid: false, reason: 'max_public_projects', type: 'public_project'}
|
return {valid: false, reason: 'max_public_projects', type: 'public_project'}
|
||||||
|
|
||||||
return {valid: true}
|
return {valid: true}
|
||||||
|
|
||||||
|
canOwnProject: (project) ->
|
||||||
|
user = @.getUser()
|
||||||
|
if project.get('is_private')
|
||||||
|
result = @.canCreatePrivateProjects()
|
||||||
|
return result if !result.valid
|
||||||
|
|
||||||
|
if user.get('max_memberships_private_projects') != null && project.get('total_memberships') > user.get('max_memberships_private_projects')
|
||||||
|
return {valid: false, reason: 'max_members_private_projects', type: 'private_project'}
|
||||||
|
|
||||||
|
else
|
||||||
|
result = @.canCreatePublicProjects()
|
||||||
|
return result if !result.valid
|
||||||
|
|
||||||
|
if user.get('max_memberships_public_projects') != null && project.get('total_memberships') > user.get('max_memberships_public_projects')
|
||||||
|
return {valid: false, reason: 'max_members_public_projects', type: 'public_project'}
|
||||||
|
|
||||||
|
return {valid: true}
|
||||||
|
|
||||||
angular.module("taigaCommon").service("tgCurrentUserService", CurrentUserService)
|
angular.module("taigaCommon").service("tgCurrentUserService", CurrentUserService)
|
||||||
|
|
|
@ -273,3 +273,157 @@ describe "tgCurrentUserService", ->
|
||||||
expect(result).to.be.eql({
|
expect(result).to.be.eql({
|
||||||
valid: true
|
valid: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it "the user can own public project", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_public_projects: 10,
|
||||||
|
total_public_projects: 1,
|
||||||
|
max_memberships_public_projects: 20
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
|
||||||
|
project = Immutable.fromJS({
|
||||||
|
id: 2,
|
||||||
|
name: "fake2",
|
||||||
|
total_memberships: 5,
|
||||||
|
is_private: false
|
||||||
|
})
|
||||||
|
|
||||||
|
result = currentUserService.canOwnProject(project)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: true
|
||||||
|
})
|
||||||
|
|
||||||
|
it "the user can't own public project because of max projects", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_public_projects: 1,
|
||||||
|
total_public_projects: 1,
|
||||||
|
max_memberships_public_projects: 20
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
|
||||||
|
project = Immutable.fromJS({
|
||||||
|
id: 2,
|
||||||
|
name: "fake2",
|
||||||
|
total_memberships: 5,
|
||||||
|
is_private: false
|
||||||
|
})
|
||||||
|
|
||||||
|
result = currentUserService.canOwnProject(project)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: false
|
||||||
|
reason: 'max_public_projects'
|
||||||
|
type: 'public_project'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it "the user can't own public project because of max memberships", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_public_projects: 5,
|
||||||
|
total_public_projects: 1,
|
||||||
|
max_memberships_public_projects: 4
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
|
||||||
|
project = Immutable.fromJS({
|
||||||
|
id: 2,
|
||||||
|
name: "fake2",
|
||||||
|
total_memberships: 5,
|
||||||
|
is_private: false
|
||||||
|
})
|
||||||
|
|
||||||
|
result = currentUserService.canOwnProject(project)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: false
|
||||||
|
reason: 'max_members_public_projects'
|
||||||
|
type: 'public_project'
|
||||||
|
})
|
||||||
|
|
||||||
|
it "the user can own private project", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_private_projects: 10,
|
||||||
|
total_private_projects: 1,
|
||||||
|
max_memberships_private_projects: 20
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
|
||||||
|
project = Immutable.fromJS({
|
||||||
|
id: 2,
|
||||||
|
name: "fake2",
|
||||||
|
total_memberships: 5,
|
||||||
|
is_private: true
|
||||||
|
})
|
||||||
|
|
||||||
|
result = currentUserService.canOwnProject(project)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: true
|
||||||
|
})
|
||||||
|
|
||||||
|
it "the user can't own private project because of max projects", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_private_projects: 1,
|
||||||
|
total_private_projects: 1,
|
||||||
|
max_memberships_private_projects: 20
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
|
||||||
|
project = Immutable.fromJS({
|
||||||
|
id: 2,
|
||||||
|
name: "fake2",
|
||||||
|
total_memberships: 5,
|
||||||
|
is_private: true
|
||||||
|
})
|
||||||
|
|
||||||
|
result = currentUserService.canOwnProject(project)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: false
|
||||||
|
reason: 'max_private_projects'
|
||||||
|
type: 'private_project'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it "the user can't own private project because of max memberships", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_private_projects: 10,
|
||||||
|
total_private_projects: 1,
|
||||||
|
max_memberships_private_projects: 4
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
|
||||||
|
project = Immutable.fromJS({
|
||||||
|
id: 2,
|
||||||
|
name: "fake2",
|
||||||
|
total_memberships: 5,
|
||||||
|
is_private: true
|
||||||
|
})
|
||||||
|
|
||||||
|
result = currentUserService.canOwnProject(project)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: false
|
||||||
|
reason: 'max_members_private_projects'
|
||||||
|
type: 'private_project'
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
.owner-avatar
|
||||||
|
img(ng-src="{{::owner.photo || '/#{v}/images/user-noimage.png'}}", alt="{{::owner.full_name_display}}")
|
||||||
|
|
||||||
|
.owner-info
|
||||||
|
.owner-info-title {{ 'ADMIN.PROJECT_PROFILE.PROJECT_OWNER' | translate }}
|
||||||
|
.owner-name {{::owner.full_name_display}}
|
||||||
|
|
||||||
|
a.request(href="", ng-click="changeOwner()") {{ 'ADMIN.PROJECT_PROFILE.CHANGE_OWNER' | translate }}
|
|
@ -78,6 +78,19 @@ div.wrapper(
|
||||||
ng-model="project.tags"
|
ng-model="project.tags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldset(ng-if="project.owner.id != user.id")
|
||||||
|
tg-admin-project-request-ownership.admin-project-profile-owner-actions(
|
||||||
|
owner="project.owner",
|
||||||
|
project-id="project.id"
|
||||||
|
)
|
||||||
|
|
||||||
|
fieldset(ng-if="project.owner.id == user.id")
|
||||||
|
tg-admin-project-change-owner.admin-project-profile-owner-actions(
|
||||||
|
owner="project.owner",
|
||||||
|
project-id="project.id"
|
||||||
|
active-users="activeUsers"
|
||||||
|
)
|
||||||
|
|
||||||
fieldset.looking-for-people
|
fieldset.looking-for-people
|
||||||
.looking-for-people-selector
|
.looking-for-people-selector
|
||||||
span {{ 'ADMIN.PROJECT_PROFILE.RECRUITING' | translate }}
|
span {{ 'ADMIN.PROJECT_PROFILE.RECRUITING' | translate }}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
.owner-avatar
|
||||||
|
img(ng-src="{{::owner.photo || '/#{v}/images/user-noimage.png'}}", alt="{{::owner.full_name_display}}")
|
||||||
|
|
||||||
|
.owner-info
|
||||||
|
.title {{ 'ADMIN.PROJECT_PROFILE.PROJECT_OWNER' | translate }}
|
||||||
|
.owner-name {{::owner.full_name_display}}
|
||||||
|
|
||||||
|
a.request(href="", ng-click="requestOwnership()") {{ 'ADMIN.PROJECT_PROFILE.REQUEST_OWNERSHIP' | translate }}
|
|
@ -0,0 +1,72 @@
|
||||||
|
svg.close.icon.icon-close(href="", title="{{'COMMON.CLOSE' | translate}}")
|
||||||
|
use(xlink:href="#icon-close")
|
||||||
|
|
||||||
|
.form
|
||||||
|
h2.title(translate="LIGHTBOX.CHANGE_OWNER.TITLE")
|
||||||
|
fieldset
|
||||||
|
input(
|
||||||
|
type="text",
|
||||||
|
data-maxlength="500",
|
||||||
|
placeholder="{{'LIGHTBOX.ASSIGNED_TO.SEARCH' | translate}}",
|
||||||
|
ng-model="vm.q",
|
||||||
|
ng-change="vm.userSearch()"
|
||||||
|
)
|
||||||
|
|
||||||
|
.assigned-to-list
|
||||||
|
.user-list-single.is-active(ng-if="vm.selected")
|
||||||
|
.user-list-avatar
|
||||||
|
a(
|
||||||
|
href="#"
|
||||||
|
title="{{'COMMON.ASSIGNED_TO.TITLE' | translate}}"
|
||||||
|
)
|
||||||
|
img(ng-src="{{vm.selected.photo}}")
|
||||||
|
a.user-list-name(
|
||||||
|
href=""
|
||||||
|
title="{{vm.selected.full_name_display}}"
|
||||||
|
) {{vm.selected.full_name_display}}
|
||||||
|
|
||||||
|
.user-list-single.ng-animate-disabled(
|
||||||
|
ng-repeat="user in vm.getUsers()",
|
||||||
|
ng-click="vm.selectUser(user)"
|
||||||
|
)
|
||||||
|
.user-list-avatar
|
||||||
|
a(
|
||||||
|
href="#"
|
||||||
|
title="{{'COMMON.ASSIGNED_TO.TITLE' | translate}}"
|
||||||
|
)
|
||||||
|
img(ng-src="{{user.photo}}")
|
||||||
|
a.user-list-name(
|
||||||
|
href=""
|
||||||
|
title="{{user.full_name_display}}"
|
||||||
|
) {{user.full_name_display}}
|
||||||
|
|
||||||
|
.more-watchers(ng-if="!vm.q.length")
|
||||||
|
span(translate="COMMON.ASSIGNED_TO.TOO_MANY")
|
||||||
|
|
||||||
|
.add-comment
|
||||||
|
a(
|
||||||
|
href="",
|
||||||
|
class="ng-animate-disabled"
|
||||||
|
ng-if="!vm.commentOpen",
|
||||||
|
ng-click="vm.commentOpen = true"
|
||||||
|
translate="LIGHTBOX.CHANGE_OWNER.ADD_COMMENT"
|
||||||
|
)
|
||||||
|
|
||||||
|
fieldset(ng-if="vm.commentOpen")
|
||||||
|
svg.icon.icon-close(
|
||||||
|
ng-click="vm.commentOpen = false"
|
||||||
|
href="",
|
||||||
|
title="{{'COMMON.CLOSE' | translate}}"
|
||||||
|
)
|
||||||
|
use(xlink:href="#icon-close")
|
||||||
|
label(translate="LIGHTBOX.CHANGE_OWNER.ADD_COMMENT")
|
||||||
|
textarea(ng-model="vm.comment")
|
||||||
|
|
||||||
|
button.button-green.submit-button(
|
||||||
|
tg-loading="vm.loading",
|
||||||
|
ng-click="vm.submit()",
|
||||||
|
ng-disabled="!vm.selected",
|
||||||
|
type="submit",
|
||||||
|
title="{{'LIGHTBOX.CHANGE_OWNER.BUTTON' | translate}}",
|
||||||
|
translate="LIGHTBOX.CHANGE_OWNER.BUTTON"
|
||||||
|
)
|
|
@ -0,0 +1,14 @@
|
||||||
|
a.close(href="", title="{{'COMMON.CLOSE' | translate}}")
|
||||||
|
svg.icon.icon-close
|
||||||
|
use(xlink:href="#icon-close")
|
||||||
|
|
||||||
|
.content
|
||||||
|
h2.title(translate="ADMIN.PROJECT_PROFILE.REQUEST_OWNERSHIP_CONFIRMATION_TITLE")
|
||||||
|
p(translate="ADMIN.PROJECT_PROFILE.REQUEST_OWNERSHIP_DESC")
|
||||||
|
|
||||||
|
a.button-green(
|
||||||
|
href="",
|
||||||
|
ng-click="request()",
|
||||||
|
tg-loading="loading"
|
||||||
|
)
|
||||||
|
span(translate="ADMIN.PROJECT_PROFILE.REQUEST_OWNERSHIP_BUTTON")
|
|
@ -33,13 +33,13 @@
|
||||||
}
|
}
|
||||||
&.disabled,
|
&.disabled,
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
background: lighten($whitish, 10%);
|
background: $whitish;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
color: $gray-light;
|
color: $gray-light;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: .65;
|
opacity: .65;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: lighten($whitish, 10%);
|
background: $whitish;
|
||||||
color: $gray-light;
|
color: $gray-light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
.notification-message-success {
|
.notification-message-success {
|
||||||
background: rgba($primary-light, .95);
|
background: rgba($primary-light, .95);
|
||||||
box-shadow: 0 25px 10px -15px rgba($black, .05);
|
box-shadow: 0 25px 10px -15px rgba($black, .05);
|
||||||
opacity: 1;
|
|
||||||
right: -370px;
|
right: -370px;
|
||||||
top: 2%;
|
top: 2%;
|
||||||
transition: opacity .2s ease-in;
|
transition: opacity .2s ease-in;
|
||||||
width: 370px;
|
width: 370px;
|
||||||
&.active {
|
&.active {
|
||||||
animation: animSlide 2000ms linear both;
|
animation: animSlide 2000ms;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
animation-iteration-count: 1;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
&.inactive {
|
||||||
|
animation: animSlideOut .5s;
|
||||||
|
opacity: 0;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -40,8 +47,11 @@
|
||||||
20% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -413.214, 0, 0, 1); }
|
20% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -413.214, 0, 0, 1); }
|
||||||
27.23% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -403.135, 0, 0, 1); }
|
27.23% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -403.135, 0, 0, 1); }
|
||||||
38.34% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -399.585, 0, 0, 1); }
|
38.34% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -399.585, 0, 0, 1); }
|
||||||
60.56% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -400.01, 0, 0, 1); }
|
100% { transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -400, 0, 0, 1); }
|
||||||
82.78% { opacity: 1; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -400, 0, 0, 1); }
|
}
|
||||||
|
|
||||||
|
@keyframes animSlideOut {
|
||||||
|
0% { opacity: 1; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -400, 0, 0, 1); }
|
||||||
100% { opacity: 0; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -400, 0, 0, 1); }
|
100% { opacity: 0; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -400, 0, 0, 1); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
.user-list-single {
|
.user-list-single {
|
||||||
&:hover,
|
&:hover,
|
||||||
&.selected {
|
&.selected {
|
||||||
background: lighten($primary, 58%);
|
background: rgba(lighten($primary-light, 30%), .3);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
transition-delay: .2s;
|
transition-delay: .2s;
|
||||||
}
|
}
|
||||||
&.is-active {
|
&.is-active {
|
||||||
background: lighten($primary, 55%);
|
background: rgba(lighten($primary-light, 30%), .3);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -59,6 +59,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drop Down element animations
|
||||||
|
|
||||||
|
@keyframes dropdownFade {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-.25rem);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes blink {
|
@keyframes blink {
|
||||||
85% {
|
85% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
@ -96,6 +96,10 @@ em {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
@extend %xsmall;
|
||||||
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 99910;
|
z-index: 99910;
|
||||||
.close {
|
.close {
|
||||||
|
@include svg-size(2rem);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
fill: $gray;
|
fill: $gray;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -53,6 +54,7 @@
|
||||||
.lb-icon {
|
.lb-icon {
|
||||||
@include svg-size(6rem);
|
@include svg-size(6rem);
|
||||||
display: block;
|
display: block;
|
||||||
|
fill: $whitish;
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
@import '../dependencies/mixins/profile-form';
|
@import '../dependencies/mixins/profile-form';
|
||||||
|
|
||||||
.project-details {
|
.project-details {
|
||||||
@include profile-form;
|
@include profile-form;
|
||||||
.looking-for-people {
|
.looking-for-people {
|
||||||
|
@ -31,7 +30,6 @@
|
||||||
animation-delay: .1s;
|
animation-delay: .1s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-project {
|
.delete-project {
|
||||||
@extend %xsmall;
|
@extend %xsmall;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -52,9 +50,7 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-privacy-settings {
|
.project-privacy-settings {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
|
@ -117,7 +113,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tg-admin-project-restrictions {
|
tg-admin-project-restrictions {
|
||||||
p {
|
p {
|
||||||
@extend %xsmall;
|
@extend %xsmall;
|
||||||
|
@ -147,3 +142,36 @@ tg-admin-project-restrictions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.admin-project-profile-owner-actions {
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid $whitish;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 1rem;
|
||||||
|
a {
|
||||||
|
color: $primary;
|
||||||
|
&:hover {
|
||||||
|
color: $primary-light;
|
||||||
|
transition: color .2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.owner-info {
|
||||||
|
flex: 1;
|
||||||
|
padding-left: .5rem;
|
||||||
|
}
|
||||||
|
.owner-info-title {
|
||||||
|
color: $gray-light;
|
||||||
|
}
|
||||||
|
.owner-name {
|
||||||
|
@extend %bold;
|
||||||
|
}
|
||||||
|
.owner-avatar {
|
||||||
|
width: 2.5rem;
|
||||||
|
}
|
||||||
|
.request {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -504,7 +504,7 @@
|
||||||
.user-list-single {
|
.user-list-single {
|
||||||
&:hover,
|
&:hover,
|
||||||
&.selected {
|
&.selected {
|
||||||
background: lighten($primary, 58%);
|
background: rgba(lighten($primary-light, 30%), .3);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -518,6 +518,33 @@
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.submit-button {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
.add-comment {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
.icon-close {
|
||||||
|
cursor: pointer;
|
||||||
|
fill: $gray;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transition: fill .2s;
|
||||||
|
&:hover {
|
||||||
|
fill: $red-light;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
@include svg-size(2rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-create-edit-userstory {
|
.lb-create-edit-userstory {
|
||||||
|
@ -573,3 +600,10 @@
|
||||||
width: 500px;
|
width: 500px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lightbox-request-ownership {
|
||||||
|
text-align: center;
|
||||||
|
.content {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,3 +25,52 @@ helper.editLogo = function() {
|
||||||
helper.getLogoSrc = function() {
|
helper.getLogoSrc = function() {
|
||||||
return $('.image-container .image');
|
return $('.image-container .image');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
helper.requestOwnershipLb = function() {
|
||||||
|
return $('div[tg-lb-request-ownership]');
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.requestOwnership = function() {
|
||||||
|
$('tg-admin-project-request-ownership .request').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.changeOwner = function() {
|
||||||
|
$('tg-admin-project-change-owner .request').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.acceptRequestOwnership = function() {
|
||||||
|
helper.requestOwnershipLb().$('.button-green').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.changeOwnerSuccessLb = function() {
|
||||||
|
return $('.lightbox-generic-success');
|
||||||
|
};
|
||||||
|
|
||||||
|
helper.getChangeOwnerLb = function() {
|
||||||
|
let el = $('div[tg-lb-change-owner]');
|
||||||
|
|
||||||
|
let obj = {
|
||||||
|
el: el,
|
||||||
|
waitOpen: function() {
|
||||||
|
return utils.lightbox.open(el);
|
||||||
|
},
|
||||||
|
waitClose: function() {
|
||||||
|
return utils.lightbox.close(el);
|
||||||
|
},
|
||||||
|
search: function(q) {
|
||||||
|
el.$$('input').get(0).sendKeys(q);
|
||||||
|
},
|
||||||
|
select: function(index) {
|
||||||
|
el.$$('.user-list-single').get(index).click();
|
||||||
|
},
|
||||||
|
addComment: function(text) {
|
||||||
|
el.$('.add-comment a').click();
|
||||||
|
el.$('textarea').sendKeys(text);
|
||||||
|
},
|
||||||
|
send: function() {
|
||||||
|
el.$('.submit-button').click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
|
@ -78,4 +78,43 @@ describe('project detail', function() {
|
||||||
|
|
||||||
expect(src).to.contains('upload-image-test.png');
|
expect(src).to.contains('upload-image-test.png');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('request ownership', async function() {
|
||||||
|
adminHelper.requestOwnership();
|
||||||
|
|
||||||
|
await utils.lightbox.open(adminHelper.requestOwnershipLb());
|
||||||
|
|
||||||
|
expect(utils.notifications.success.open()).to.be.eventually.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('change ownership', async function() {
|
||||||
|
await utils.common.createProject(['user5@taigaio.demo']);
|
||||||
|
|
||||||
|
await utils.nav
|
||||||
|
.init()
|
||||||
|
.admin()
|
||||||
|
.go();
|
||||||
|
|
||||||
|
adminHelper.changeOwner();
|
||||||
|
|
||||||
|
let lb = adminHelper.getChangeOwnerLb();
|
||||||
|
|
||||||
|
await lb.waitOpen();
|
||||||
|
|
||||||
|
lb.search('Alicia Flores');
|
||||||
|
lb.select(0);
|
||||||
|
lb.addComment('text');
|
||||||
|
|
||||||
|
utils.common.takeScreenshot('admin', 'project-transfer-lb');
|
||||||
|
|
||||||
|
lb.send();
|
||||||
|
|
||||||
|
let changeOwnerSuccessLb = adminHelper.changeOwnerSuccessLb();
|
||||||
|
|
||||||
|
await utils.lightbox.open(changeOwnerSuccessLb);
|
||||||
|
|
||||||
|
changeOwnerSuccessLb.$('.button-green').click();
|
||||||
|
|
||||||
|
await utils.lightbox.close(changeOwnerSuccessLb);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -435,3 +435,50 @@ common.closeJoyride = async function() {
|
||||||
await browser.sleep(600);
|
await browser.sleep(600);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
common.createProject = async function(members = []) {
|
||||||
|
var createProject = require('../helpers').createProject;
|
||||||
|
var notifications = require('./notifications');
|
||||||
|
|
||||||
|
browser.get(browser.params.glob.host + 'projects/');
|
||||||
|
await common.waitLoader();
|
||||||
|
|
||||||
|
let lb = createProject.createProjectLightbox();
|
||||||
|
|
||||||
|
createProject.openWizard();
|
||||||
|
|
||||||
|
await lb.waitOpen();
|
||||||
|
|
||||||
|
lb.name().sendKeys('aaa');
|
||||||
|
|
||||||
|
lb.description().sendKeys('bbb');
|
||||||
|
|
||||||
|
await lb.submit();
|
||||||
|
|
||||||
|
await notifications.success.open();
|
||||||
|
await notifications.success.close();
|
||||||
|
|
||||||
|
if (members.length) {
|
||||||
|
var adminMembershipsHelper = require('../helpers').adminMemberships;
|
||||||
|
|
||||||
|
let url = await browser.getCurrentUrl();
|
||||||
|
url = url.split('/');
|
||||||
|
url = browser.params.glob.host + '/project/' + url[4] + '/admin/memberships';
|
||||||
|
|
||||||
|
browser.get(url);
|
||||||
|
await common.waitLoader();
|
||||||
|
|
||||||
|
let newMemberLightbox = adminMembershipsHelper.getNewMemberLightbox();
|
||||||
|
adminMembershipsHelper.openNewMemberLightbox();
|
||||||
|
|
||||||
|
await newMemberLightbox.waitOpen();
|
||||||
|
|
||||||
|
for(var i = 0; i < members.length; i++) {
|
||||||
|
newMemberLightbox.newEmail(members[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
newMemberLightbox.submit();
|
||||||
|
|
||||||
|
await newMemberLightbox.waitClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -46,6 +46,11 @@ var actions = {
|
||||||
browser.get(browser.params.glob.host);
|
browser.get(browser.params.glob.host);
|
||||||
return common.waitLoader();
|
return common.waitLoader();
|
||||||
},
|
},
|
||||||
|
admin: async function() {
|
||||||
|
await common.link($('#nav-admin a'));
|
||||||
|
|
||||||
|
return common.waitLoader();
|
||||||
|
},
|
||||||
taskboard: async function(index) {
|
taskboard: async function(index) {
|
||||||
let link = $$('.sprints .button-gray').get(index);
|
let link = $$('.sprints .button-gray').get(index);
|
||||||
|
|
||||||
|
@ -92,6 +97,10 @@ var nav = {
|
||||||
this.actions.push(actions.home.bind(null));
|
this.actions.push(actions.home.bind(null));
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
admin: function() {
|
||||||
|
this.actions.push(actions.admin.bind(null));
|
||||||
|
return this;
|
||||||
|
},
|
||||||
taskboard: function(index) {
|
taskboard: function(index) {
|
||||||
this.actions.push(actions.taskboard.bind(null, index));
|
this.actions.push(actions.taskboard.bind(null, index));
|
||||||
return this;
|
return this;
|
||||||
|
|
Loading…
Reference in New Issue