Warning Max Users per Project
parent
19820a8d2d
commit
acfd53edbf
|
@ -8,6 +8,7 @@
|
||||||
- Moved from iconfont to SVG sprite icon system and redesign.
|
- Moved from iconfont to SVG sprite icon system and redesign.
|
||||||
- Redesign 'Admin > Project > Modules' panel.
|
- Redesign 'Admin > Project > Modules' panel.
|
||||||
- Add badge to project owners
|
- Add badge to project owners
|
||||||
|
- Limit of user per project.
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
- Lots of small and not so small bugfixes.
|
- Lots of small and not so small bugfixes.
|
||||||
|
|
|
@ -47,11 +47,13 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
"$tgAnalytics",
|
"$tgAnalytics",
|
||||||
"tgAppMetaService",
|
"tgAppMetaService",
|
||||||
"$translate"
|
"$translate",
|
||||||
|
"tgCurrentUserService",
|
||||||
|
"$tgAuth"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @analytics,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @analytics,
|
||||||
@appMetaService, @translate) ->
|
@appMetaService, @translate, @currentUserService, @tgAuth) ->
|
||||||
bindMethods(@)
|
bindMethods(@)
|
||||||
|
|
||||||
@scope.project = {}
|
@scope.project = {}
|
||||||
|
@ -63,6 +65,7 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
||||||
title = @translate.instant("ADMIN.MEMBERSHIPS.PAGE_TITLE", {projectName: @scope.project.name})
|
title = @translate.instant("ADMIN.MEMBERSHIPS.PAGE_TITLE", {projectName: @scope.project.name})
|
||||||
description = @scope.project.description
|
description = @scope.project.description
|
||||||
@appMetaService.setAll(title, description)
|
@appMetaService.setAll(title, description)
|
||||||
|
@._checkUsersLimit()
|
||||||
|
|
||||||
promise.then null, @.onInitialDataError.bind(@)
|
promise.then null, @.onInitialDataError.bind(@)
|
||||||
|
|
||||||
|
@ -93,8 +96,11 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
||||||
|
|
||||||
loadInitialData: ->
|
loadInitialData: ->
|
||||||
promise = @.loadProject()
|
promise = @.loadProject()
|
||||||
promise.then =>
|
|
||||||
@.loadMembers()
|
@q.all([
|
||||||
|
@.loadMembers(),
|
||||||
|
@tgAuth.refresh()
|
||||||
|
])
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
|
|
||||||
|
@ -106,6 +112,22 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
||||||
addNewMembers: ->
|
addNewMembers: ->
|
||||||
@rootscope.$broadcast("membersform:new")
|
@rootscope.$broadcast("membersform:new")
|
||||||
|
|
||||||
|
_checkUsersLimit: ->
|
||||||
|
@scope.canAddUsers = true
|
||||||
|
userData = @currentUserService.getUser().toJS()
|
||||||
|
|
||||||
|
if @currentUserService.canAddMoreMembersInPrivateProjects(@scope.projectId).valid == false
|
||||||
|
@.maxMembers = userData.max_members_private_projects
|
||||||
|
@scope.canAddUsers = false
|
||||||
|
else if @currentUserService.canAddMoreMembersInPublicProjects(@scope.projectId).valid == false
|
||||||
|
@.maxMembers = userData.max_members_private_projects
|
||||||
|
@scope.canAddUsers = false
|
||||||
|
|
||||||
|
limitUsersWarning: ->
|
||||||
|
title = @translate.instant("ADMIN.MEMBERSHIPS.LIMIT_USERS_WARNING")
|
||||||
|
message = @translate.instant("ADMIN.MEMBERSHIPS.LIMIT_USERS_WARNING_MESSAGE", {members: @.maxMembers})
|
||||||
|
icon = "/" + window._version + "/svg/icons/team-question.svg"
|
||||||
|
@confirm.success(title, message,icon)
|
||||||
|
|
||||||
module.controller("MembershipsController", MembershipsController)
|
module.controller("MembershipsController", MembershipsController)
|
||||||
|
|
||||||
|
|
|
@ -161,11 +161,16 @@ class ConfirmService extends taiga.Service
|
||||||
|
|
||||||
return defered.promise
|
return defered.promise
|
||||||
|
|
||||||
success: (title, message) ->
|
success: (title, message, icon) ->
|
||||||
defered = @q.defer()
|
defered = @q.defer()
|
||||||
|
|
||||||
el = angular.element(".lightbox-generic-success")
|
el = angular.element(".lightbox-generic-success")
|
||||||
|
|
||||||
|
el.find("img").remove()
|
||||||
|
detailImage = $('<img>').addClass('lb-icon').attr('src', icon)
|
||||||
|
if detailImage
|
||||||
|
el.find('section').prepend(detailImage)
|
||||||
|
|
||||||
# Render content
|
# Render content
|
||||||
el.find(".title").html(title) if title
|
el.find(".title").html(title) if title
|
||||||
el.find(".message").html(message) if message
|
el.find(".message").html(message) if message
|
||||||
|
|
|
@ -408,7 +408,9 @@
|
||||||
"TITLE": "Manage members",
|
"TITLE": "Manage members",
|
||||||
"PAGE_TITLE": "Memberships - {{projectName}}",
|
"PAGE_TITLE": "Memberships - {{projectName}}",
|
||||||
"ADD_BUTTON": "+ New member",
|
"ADD_BUTTON": "+ New member",
|
||||||
"ADD_BUTTON_TITLE": "Add new member"
|
"ADD_BUTTON_TITLE": "Add new member",
|
||||||
|
"LIMIT_USERS_WARNING": "Why can't I add more members",
|
||||||
|
"LIMIT_USERS_WARNING_MESSAGE": "Currently you can only have <strong>{{members}}</strong> members per project. If you want to add more members get in touch with the administrators"
|
||||||
},
|
},
|
||||||
"PROJECT_EXPORT": {
|
"PROJECT_EXPORT": {
|
||||||
"TITLE": "Export",
|
"TITLE": "Export",
|
||||||
|
|
|
@ -4,8 +4,8 @@ a(href="", title="Projects", tg-nav="projects")
|
||||||
|
|
||||||
div.navbar-dropdown.dropdown-project-list
|
div.navbar-dropdown.dropdown-project-list
|
||||||
ul
|
ul
|
||||||
li(tg-repeat="project in vm.projects track by project.get('id')")
|
li(tg-repeat="project in vm.projects track by project.get('id')")
|
||||||
a(href="#", tg-nav="project:project=project.get('slug')") {{::project.get("name")}}
|
a(href="#", tg-nav="project:project=project.get('slug')") {{::project.get("name")}}
|
||||||
|
|
||||||
a.see-more-projects-btn.button-gray(
|
a.see-more-projects-btn.button-gray(
|
||||||
href="#",
|
href="#",
|
||||||
|
|
|
@ -118,6 +118,25 @@ class CurrentUserService
|
||||||
|
|
||||||
return @.projects
|
return @.projects
|
||||||
|
|
||||||
|
canAddMoreMembersInPrivateProjects: (projectId) ->
|
||||||
|
project = @.projects.get('all').find (project) -> project.get('id') == projectId
|
||||||
|
user = @.getUser()
|
||||||
|
|
||||||
|
if user.get('max_members_private_projects') != null && project.get('members').size >= user.get('max_members_private_projects')
|
||||||
|
return {valid: false, reason: 'max_members_private_projects', type: 'private_project'}
|
||||||
|
|
||||||
|
return {valid: true}
|
||||||
|
|
||||||
|
canAddMoreMembersInPublicProjects: (projectId) ->
|
||||||
|
project = @.projects.get('all').find (project) -> project.get('id') == projectId
|
||||||
|
user = @.getUser()
|
||||||
|
|
||||||
|
if user.get('max_members_public_projects') != null && project.get('members').size >= user.get('max_members_public_projects')
|
||||||
|
return {valid: false, reason: 'max_members_public_projects', type: 'public_project'}
|
||||||
|
|
||||||
|
return {valid: true}
|
||||||
|
|
||||||
|
|
||||||
canBePrivateProject: (projectId) ->
|
canBePrivateProject: (projectId) ->
|
||||||
project = @.projects.get('all').find (project) -> project.get('id') == projectId
|
project = @.projects.get('all').find (project) -> project.get('id') == projectId
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,112 @@ describe "tgCurrentUserService", ->
|
||||||
|
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
it "the user can't add more members in private projects", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_members_private_projects: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
projects = Immutable.fromJS({
|
||||||
|
all: [
|
||||||
|
{id: 1, name: "fake1"},
|
||||||
|
{id: 2, name: "fake2", members: [1, 2, 3, 4, 5], is_private: true},
|
||||||
|
{id: 3, name: "fake3"},
|
||||||
|
{id: 4, name: "fake4"}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
currentUserService._projects = projects
|
||||||
|
|
||||||
|
result = currentUserService.canAddMoreMembersInPrivateProjects(2)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: false,
|
||||||
|
reason: 'max_members_private_projects',
|
||||||
|
type: 'private_project'
|
||||||
|
})
|
||||||
|
|
||||||
|
it "the user can add more members in private projects", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_members_private_projects: 7
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
|
||||||
|
projects = Immutable.fromJS({
|
||||||
|
all: [
|
||||||
|
{id: 1, name: "fake1"},
|
||||||
|
{id: 2, name: "fake2", members: [1, 2, 3, 4, 5], is_private: true},
|
||||||
|
{id: 3, name: "fake3"},
|
||||||
|
{id: 4, name: "fake4"}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._projects = projects
|
||||||
|
|
||||||
|
result = currentUserService.canAddMoreMembersInPrivateProjects(2)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: true
|
||||||
|
})
|
||||||
|
|
||||||
|
it "the user can't add more members in public projects", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_members_public_projects: 2
|
||||||
|
})
|
||||||
|
|
||||||
|
projects = Immutable.fromJS({
|
||||||
|
all: [
|
||||||
|
{id: 1, name: "fake1"},
|
||||||
|
{id: 2, name: "fake2", members: [1, 2, 3, 4, 5], is_private: false},
|
||||||
|
{id: 3, name: "fake3"},
|
||||||
|
{id: 4, name: "fake4"}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
currentUserService._projects = projects
|
||||||
|
|
||||||
|
result = currentUserService.canAddMoreMembersInPublicProjects(2)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: false,
|
||||||
|
reason: 'max_members_public_projects',
|
||||||
|
type: 'public_project'
|
||||||
|
})
|
||||||
|
|
||||||
|
it "the user can add more members in public projects", () ->
|
||||||
|
user = Immutable.fromJS({
|
||||||
|
id: 1,
|
||||||
|
name: "fake1",
|
||||||
|
max_members_public_projects: 7
|
||||||
|
})
|
||||||
|
|
||||||
|
projects = Immutable.fromJS({
|
||||||
|
all: [
|
||||||
|
{id: 1, name: "fake1"},
|
||||||
|
{id: 2, name: "fake2", members: [1, 2, 3, 4, 5], is_private: false},
|
||||||
|
{id: 3, name: "fake3"},
|
||||||
|
{id: 4, name: "fake4"}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
currentUserService._user = user
|
||||||
|
currentUserService._projects = projects
|
||||||
|
|
||||||
|
result = currentUserService.canAddMoreMembersInPublicProjects(2)
|
||||||
|
|
||||||
|
expect(result).to.be.eql({
|
||||||
|
valid: true
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
it "the user can't create private projects if they reach the maximum number of private projects", () ->
|
it "the user can't create private projects if they reach the maximum number of private projects", () ->
|
||||||
user = Immutable.fromJS({
|
user = Immutable.fromJS({
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -214,7 +320,7 @@ describe "tgCurrentUserService", ->
|
||||||
|
|
||||||
currentUserService._user = user
|
currentUserService._user = user
|
||||||
|
|
||||||
result = currentUserService.canCreatePrivateProjects(0)
|
result = currentUserService.canCreatePrivateProjects()
|
||||||
|
|
||||||
expect(result).to.be.eql({
|
expect(result).to.be.eql({
|
||||||
valid: false,
|
valid: false,
|
||||||
|
|
|
@ -13,9 +13,19 @@ div.wrapper.memberships(ng-controller="MembershipsController as ctrl",
|
||||||
include ../includes/components/mainTitle
|
include ../includes/components/mainTitle
|
||||||
|
|
||||||
.action-buttons
|
.action-buttons
|
||||||
a.button-green(href="", title="{{ ADMIN.MEMBERSHIPS.ADD_BUTTON_TITLE | translate }}",
|
a.limit-users-warning(
|
||||||
ng-click="ctrl.addNewMembers()")
|
ng-if="!canAddUsers"
|
||||||
span.text(translate="ADMIN.MEMBERSHIPS.ADD_BUTTON")
|
translate="ADMIN.MEMBERSHIPS.LIMIT_USERS_WARNING"
|
||||||
|
translate-values="{members: ctrl.maxMembers}"
|
||||||
|
href=""
|
||||||
|
ng-click="ctrl.limitUsersWarning()"
|
||||||
|
)
|
||||||
|
button.button-green(
|
||||||
|
title="{{ ADMIN.MEMBERSHIPS.ADD_BUTTON_TITLE | translate }}",
|
||||||
|
ng-click="ctrl.addNewMembers()"
|
||||||
|
translate="ADMIN.MEMBERSHIPS.ADD_BUTTON"
|
||||||
|
ng-disabled="!canAddUsers"
|
||||||
|
)
|
||||||
|
|
||||||
include ../includes/modules/admin/admin-membership-table
|
include ../includes/modules/admin/admin-membership-table
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.trans-button {
|
.trans-button {
|
||||||
@extend %medium;
|
@extend %medium;
|
||||||
@extend %title;
|
@extend %title;
|
||||||
|
@ -61,12 +60,9 @@
|
||||||
color: $blackish;
|
color: $blackish;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.submit-button {
|
.submit-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-green,
|
.button-green,
|
||||||
a.button-green {
|
a.button-green {
|
||||||
@extend %button;
|
@extend %button;
|
||||||
|
@ -77,7 +73,6 @@ a.button-green {
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-gray,
|
.button-gray,
|
||||||
a.button-gray {
|
a.button-gray {
|
||||||
@extend %button;
|
@extend %button;
|
||||||
|
@ -88,7 +83,6 @@ a.button-gray {
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-blackish {
|
.button-blackish {
|
||||||
@extend %button;
|
@extend %button;
|
||||||
background: $blackish;
|
background: $blackish;
|
||||||
|
@ -98,7 +92,6 @@ a.button-gray {
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-red {
|
.button-red {
|
||||||
@extend %button;
|
@extend %button;
|
||||||
background: $red-light;
|
background: $red-light;
|
||||||
|
@ -110,7 +103,6 @@ a.button-gray {
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-block {
|
.button-block {
|
||||||
background: $white;
|
background: $white;
|
||||||
color: $red;
|
color: $red;
|
||||||
|
@ -119,7 +111,6 @@ a.button-gray {
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-bulk {
|
.button-bulk {
|
||||||
@extend %button;
|
@extend %button;
|
||||||
background: $primary;
|
background: $primary;
|
||||||
|
@ -134,7 +125,6 @@ a.button-gray {
|
||||||
background: $primary-light;
|
background: $primary-light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-auth {
|
.button-auth {
|
||||||
@extend %button;
|
@extend %button;
|
||||||
background: $grayer;
|
background: $grayer;
|
||||||
|
|
|
@ -50,6 +50,11 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity .3s ease;
|
transition: opacity .3s ease;
|
||||||
}
|
}
|
||||||
|
.lb-icon {
|
||||||
|
@include svg-size(6rem);
|
||||||
|
display: block;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
.title {
|
.title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.limit-users-warning {
|
||||||
|
@extend %small;
|
||||||
|
color: $primary;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
.check {
|
.check {
|
||||||
input { // IE needs smaller size
|
input { // IE needs smaller size
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
.lightbox {
|
.lightbox {
|
||||||
@extend %lightbox;
|
@extend %lightbox;
|
||||||
h2 {
|
h2 {
|
||||||
@extend %larger;
|
@extend %xlarge;
|
||||||
@extend %text;
|
@extend %light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 400 400">
|
||||||
|
<path
|
||||||
|
fill="#b8b8b8" d="M165 91.5c-33 0-60 28.7-60 64 0 35.2 27 64 60 64s60-28.8 60-64c0-35.3-27-64-60-64zm0 16.2c24.8 0 44.8 21.3 44.8 47.8 0 26.5-20 47.8-44.8 47.8-25 0-45-21.3-45-47.8 0-26.5 20-47.8 45-47.8zm-77.8 13.5c-29.2 0-53 25.5-53 56.7 0 31 23.8 56.5 53 56.5 16 0 27.5-4.5 37.3-16.6-21.8-9.7-34-36-34-62.5 0-12 3-23.3 8.5-33-3.8-.8-7.7-1.3-11.8-1.3zM165 229.7c-53.3 0-95.8 50.8-96 112.5v8h192v-8c-.2-61.7-42.8-112.5-96-112.5zm-78.7 13.6c-3.4 0-5.8.3-5.8.3-47.2 0-78 45-78.3 99.5v7.3h52.2v-8c0-41.3 15.2-78.6 43.7-98.2-3.8-.6-8.2-.8-11.7-.7zM165 246c42 0 76.8 38.2 80.5 88h-161c3.6-49.8 38.6-88 80.5-88zM339.3 49.7C307 49.7 281 76 281 108.2c0 32.2 26 58.4 58.3 58.4s58.5-26.2 58.5-58.4c0-32.3-26.2-58.5-58.5-58.5zm0 7.3c28.3 0 51.2 23 51.2 51.2 0 28.3-23 51-51.2 51-28.3 0-51-22.7-51-51S311 57 339.2 57zm-.2 11.8c-7 0-14 1.7-20.5 5l3 7c3.5-1.6 6.5-2.7 9-3.3 2.7-.7 5.4-1 8-1 4.4 0 7.6 1 10 3 2.2 1.8 3.3 4.5 3.3 8 0 3-.6 5.4-1.8 7.4-1.2 2-4.2 5-9 9-3.3 2.8-5.7 5.6-7 8.2-1.2 2.6-1.8 6-1.8 10v2.8h6.7v-1.7c0-3.3.5-6 1.6-8 1-2 3.5-4.4 7-7.3 4.4-4 7.2-6.6 8.5-8.3 1.4-1.8 2.4-3.6 3-5.5.8-2 1-4.2 1-6.7 0-6-1.7-10.6-5.4-13.8-3.7-3.3-8.8-5-15.5-5zm-6.8 62.5V148h7.3v-16.7h-7.3z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 67 KiB |
Loading…
Reference in New Issue