Merge pull request #7541 from opf/feature/members_widget_on_dashboard
add members widget to dashboard [ci skip]pull/7544/head
commit
b66e4ae676
@ -0,0 +1,35 @@ |
|||||||
|
#-- encoding: UTF-8 |
||||||
|
|
||||||
|
#-- copyright |
||||||
|
# OpenProject is a project management system. |
||||||
|
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License version 3. |
||||||
|
# |
||||||
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||||
|
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||||
|
# Copyright (C) 2010-2013 the ChiliProject Team |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License |
||||||
|
# as published by the Free Software Foundation; either version 2 |
||||||
|
# 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 General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with this program; if not, write to the Free Software |
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||||
|
# |
||||||
|
# See docs/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
class Queries::Projects::Filters::IdFilter < Queries::Projects::Filters::ProjectFilter |
||||||
|
def type |
||||||
|
:integer |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,41 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is a project management system.
|
||||||
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import { |
||||||
|
SimpleResourceCollection |
||||||
|
} from 'core-app/modules/common/path-helper/apiv3/path-resources'; |
||||||
|
|
||||||
|
export class Apiv3GroupsPaths extends SimpleResourceCollection { |
||||||
|
constructor(basePath:string) { |
||||||
|
super(basePath, 'groups'); |
||||||
|
} |
||||||
|
|
||||||
|
public toString():string { |
||||||
|
throw 'Backend does not yet offer this end point'; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
// -- copyright
|
||||||
|
// OpenProject is a project management system.
|
||||||
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
// ++
|
||||||
|
|
||||||
|
import {SimpleResourceCollection} from 'core-app/modules/common/path-helper/apiv3/path-resources'; |
||||||
|
|
||||||
|
export class Apiv3MembershipsPaths extends SimpleResourceCollection { |
||||||
|
constructor(basePath:string) { |
||||||
|
super(basePath, 'memberships'); |
||||||
|
} |
||||||
|
|
||||||
|
// /api/v3/memberships/available_projects
|
||||||
|
public readonly available_projects = this.path + '/available_projects'; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
<widget-header |
||||||
|
[name]="widgetName" |
||||||
|
[icon]="'group'" |
||||||
|
(onRenamed)="renameWidget($event)"> |
||||||
|
|
||||||
|
<widget-menu |
||||||
|
[resource]="resource"> |
||||||
|
</widget-menu> |
||||||
|
</widget-header> |
||||||
|
|
||||||
|
<div class="grid--widget-content"> |
||||||
|
<no-results *ngIf="noMembers" |
||||||
|
[title]="text.noResults"> |
||||||
|
</no-results> |
||||||
|
<div class="attributes-key-value" *ngFor="let usersByRole of usersByRole"> |
||||||
|
<div class="attributes-key-value--key -minimal"> |
||||||
|
{{usersByRole.role.name}}: |
||||||
|
</div> |
||||||
|
<div class="attributes-key-value--value-container"> |
||||||
|
<div class="attributes-key-value--value"> |
||||||
|
<span *ngFor="let principal of usersByRole.users; let last = last"> |
||||||
|
|
||||||
|
<ng-container *ngIf="isGroup(principal)"> |
||||||
|
{{userName(principal)}} |
||||||
|
</ng-container> |
||||||
|
<ng-container *ngIf="!isGroup(principal)"> |
||||||
|
<user-avatar [user]="principal" |
||||||
|
data-class-list="avatar avatar-mini -spaced"> |
||||||
|
</user-avatar> |
||||||
|
<a [href]="userPath(principal)" |
||||||
|
[textContent]="userName(principal)"> |
||||||
|
</a> |
||||||
|
</ng-container> |
||||||
|
|
||||||
|
<ng-container *ngIf="!last">, </ng-container> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div *ngIf="moreMembers" |
||||||
|
class="members-widget--notification"> |
||||||
|
{{moreMembersText}} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="grid--widget-footer"> |
||||||
|
<a *ngIf="membersAddable" |
||||||
|
[href]="projectMembershipsPath + '?show_add_members=true'" |
||||||
|
class="button -alt-highlight"> |
||||||
|
<i class="button--icon icon-add" aria-hidden="true"></i> |
||||||
|
<span class="button--text" |
||||||
|
[textContent]="text.add"> |
||||||
|
</span> |
||||||
|
</a> |
||||||
|
|
||||||
|
<a *ngIf="!noMembers" |
||||||
|
[href]="projectMembershipsPath" |
||||||
|
class="button -highlight-inverted"> |
||||||
|
<i class="button--icon icon-group" aria-hidden="true"></i> |
||||||
|
<span class="button--text" |
||||||
|
[textContent]="text.viewAll"> |
||||||
|
</span> |
||||||
|
</a> |
||||||
|
</div> |
@ -0,0 +1,9 @@ |
|||||||
|
.members-widget--notification |
||||||
|
font-style: italic |
||||||
|
margin-top: 0.5rem |
||||||
|
|
||||||
|
.grid--widget-footer |
||||||
|
margin-top: 0.5rem |
||||||
|
|
||||||
|
.button |
||||||
|
margin-bottom: 0 |
@ -0,0 +1,139 @@ |
|||||||
|
import {AbstractWidgetComponent} from "core-app/modules/grids/widgets/abstract-widget.component"; |
||||||
|
import {Component, OnInit, ChangeDetectorRef, Injector, ChangeDetectionStrategy} from '@angular/core'; |
||||||
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||||
|
import {PathHelperService} from "core-app/modules/common/path-helper/path-helper.service"; |
||||||
|
import {UserResource} from "core-app/modules/hal/resources/user-resource"; |
||||||
|
import {CurrentProjectService} from "core-components/projects/current-project.service"; |
||||||
|
import {DmListParameter} from "core-app/modules/hal/dm-services/dm.service.interface"; |
||||||
|
import {MembershipResource} from "core-app/modules/hal/resources/membership-resource"; |
||||||
|
import {MembershipDmService} from "core-app/modules/hal/dm-services/membership-dm.service"; |
||||||
|
import {RoleResource} from "core-app/modules/hal/resources/role-resource"; |
||||||
|
|
||||||
|
const DISPLAYED_MEMBERS_LIMIT = 100; |
||||||
|
|
||||||
|
@Component({ |
||||||
|
templateUrl: './members.component.html', |
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||||
|
styleUrls: ['./members.component.sass'] |
||||||
|
}) |
||||||
|
export class WidgetMembersComponent extends AbstractWidgetComponent implements OnInit { |
||||||
|
public text = { |
||||||
|
add: this.i18n.t('js.grid.widgets.members.add'), |
||||||
|
noResults: this.i18n.t('js.grid.widgets.members.no_results'), |
||||||
|
viewAll: this.i18n.t('js.grid.widgets.members.view_all_members'), |
||||||
|
}; |
||||||
|
|
||||||
|
public totalMembers:number; |
||||||
|
public entriesByRoles:{[roleId:string]:{role:RoleResource, users:UserResource[]}} = {}; |
||||||
|
private entriesLoaded = false; |
||||||
|
public membersAddable:boolean = false; |
||||||
|
|
||||||
|
constructor(readonly pathHelper:PathHelperService, |
||||||
|
readonly i18n:I18nService, |
||||||
|
protected readonly injector:Injector, |
||||||
|
readonly membershipDm:MembershipDmService, |
||||||
|
readonly currentProject:CurrentProjectService, |
||||||
|
readonly cdr:ChangeDetectorRef) { |
||||||
|
super(i18n, injector); |
||||||
|
} |
||||||
|
|
||||||
|
ngOnInit() { |
||||||
|
this.membershipDm |
||||||
|
.list(this.listMembersParams) |
||||||
|
.then(collection => { |
||||||
|
this.partitionEntriesByRole(collection.elements); |
||||||
|
this.sortUsersByName(); |
||||||
|
this.totalMembers = collection.total; |
||||||
|
|
||||||
|
this.entriesLoaded = true; |
||||||
|
this.cdr.detectChanges(); |
||||||
|
}); |
||||||
|
|
||||||
|
this.membershipDm |
||||||
|
.listAvailableProjects(this.listAvailableProjectsParams) |
||||||
|
.then(collection => { |
||||||
|
this.membersAddable = collection.total > 0; |
||||||
|
}) |
||||||
|
.catch(() => { |
||||||
|
// nothing bad, the user is just not allowed to add members to the project
|
||||||
|
|
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public userPath(user:UserResource) { |
||||||
|
return this.pathHelper.userPath(user.id!); |
||||||
|
} |
||||||
|
|
||||||
|
public userName(user:UserResource) { |
||||||
|
return user.name; |
||||||
|
} |
||||||
|
|
||||||
|
public get noMembers() { |
||||||
|
return this.entriesLoaded && !Object.keys(this.entriesByRoles).length; |
||||||
|
} |
||||||
|
|
||||||
|
public get moreMembers() { |
||||||
|
return this.entriesLoaded && this.totalMembers > DISPLAYED_MEMBERS_LIMIT; |
||||||
|
} |
||||||
|
|
||||||
|
public get moreMembersText() { |
||||||
|
return I18n.t( |
||||||
|
'js.grid.widgets.members.too_many', |
||||||
|
{ count: DISPLAYED_MEMBERS_LIMIT, total: this.totalMembers } |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public get projectMembershipsPath() { |
||||||
|
return this.pathHelper.projectMembershipsPath(this.currentProject.identifier!); |
||||||
|
} |
||||||
|
|
||||||
|
public get usersByRole() { |
||||||
|
return Object.values(this.entriesByRoles); |
||||||
|
} |
||||||
|
|
||||||
|
public isGroup(principal:UserResource) { |
||||||
|
return this.pathHelper.api.v3.groups.id(principal.id!).toString() === principal.href; |
||||||
|
} |
||||||
|
|
||||||
|
private partitionEntriesByRole(memberships:MembershipResource[]) { |
||||||
|
memberships.forEach(membership => { |
||||||
|
membership.roles.forEach((role) => { |
||||||
|
if (!this.entriesByRoles[role.id!]) { |
||||||
|
this.entriesByRoles[role.id!] = { role: role, users: [] }; |
||||||
|
} |
||||||
|
|
||||||
|
this.entriesByRoles[role.id!].users.push(membership.principal); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private sortUsersByName() { |
||||||
|
Object.values(this.entriesByRoles).forEach(entry => { |
||||||
|
entry.users.sort((a, b) => { |
||||||
|
return this.userName(a).localeCompare(this.userName(b)); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private get listMembersParams() { |
||||||
|
let params:DmListParameter = { sortBy: [['created_on', 'desc']], pageSize: DISPLAYED_MEMBERS_LIMIT }; |
||||||
|
|
||||||
|
if (this.currentProject.id) { |
||||||
|
params['filters'] = [['project_id', '=', [this.currentProject.id]]]; |
||||||
|
} |
||||||
|
|
||||||
|
return params; |
||||||
|
} |
||||||
|
|
||||||
|
private get listAvailableProjectsParams() { |
||||||
|
// It would make sense to set the pageSize but the backend for projects
|
||||||
|
// returns an upaginated list which does not support that.
|
||||||
|
let params:DmListParameter = {}; |
||||||
|
|
||||||
|
if (this.currentProject.id) { |
||||||
|
params['filters'] = [['id', '=', [this.currentProject.id]]]; |
||||||
|
} |
||||||
|
|
||||||
|
return params; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
//-- copyright
|
||||||
|
// OpenProject is a project management system.
|
||||||
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
//++
|
||||||
|
|
||||||
|
import {Injectable} from '@angular/core'; |
||||||
|
import {AbstractDmService} from "core-app/modules/hal/dm-services/abstract-dm.service"; |
||||||
|
import {MembershipResource} from "core-app/modules/hal/resources/membership-resource"; |
||||||
|
import {DmListParameter} from "core-app/modules/hal/dm-services/dm.service.interface"; |
||||||
|
import {CollectionResource} from "core-app/modules/hal/resources/collection-resource"; |
||||||
|
import {ProjectResource} from "core-app/modules/hal/resources/project-resource"; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class MembershipDmService extends AbstractDmService<MembershipResource> { |
||||||
|
public listAvailableProjects(params:DmListParameter|null) { |
||||||
|
return this.listRequest(this.availableProjectsUrl(), params) as Promise<CollectionResource<ProjectResource>>; |
||||||
|
} |
||||||
|
|
||||||
|
protected listUrl() { |
||||||
|
return this.pathHelper.api.v3.memberships.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
protected oneUrl(id:number|string) { |
||||||
|
return this.pathHelper.api.v3.memberships.id(id).toString(); |
||||||
|
} |
||||||
|
|
||||||
|
protected availableProjectsUrl() { |
||||||
|
return this.pathHelper.api.v3.memberships.available_projects.toString(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
//-- copyright
|
||||||
|
// OpenProject is a project management system.
|
||||||
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
//++
|
||||||
|
|
||||||
|
import {HalResource} from 'core-app/modules/hal/resources/hal-resource'; |
||||||
|
import {UserResource} from "core-app/modules/hal/resources/user-resource"; |
||||||
|
import {RoleResource} from "core-app/modules/hal/resources/role-resource"; |
||||||
|
import {ProjectResource} from "core-app/modules/hal/resources/project-resource"; |
||||||
|
|
||||||
|
export interface MembershipResourceLinks { |
||||||
|
update(payload:unknown):Promise<unknown>; |
||||||
|
updateImmediately(payload:unknown):Promise<unknown>; |
||||||
|
delete():Promise<unknown>; |
||||||
|
} |
||||||
|
|
||||||
|
export interface MembershipResourceEmbedded { |
||||||
|
principal:UserResource; |
||||||
|
roles:RoleResource[]; |
||||||
|
project:ProjectResource; |
||||||
|
} |
||||||
|
|
||||||
|
export class MembershipResource extends HalResource { |
||||||
|
} |
||||||
|
|
||||||
|
export interface MembershipResource extends MembershipResourceLinks, MembershipResourceEmbedded {} |
@ -0,0 +1,32 @@ |
|||||||
|
//-- copyright
|
||||||
|
// OpenProject is a project management system.
|
||||||
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See doc/COPYRIGHT.rdoc for more details.
|
||||||
|
//++
|
||||||
|
|
||||||
|
import {HalResource} from 'core-app/modules/hal/resources/hal-resource'; |
||||||
|
|
||||||
|
export class RoleResource extends HalResource { |
||||||
|
} |
@ -0,0 +1,148 @@ |
|||||||
|
#-- copyright |
||||||
|
# OpenProject is a project management system. |
||||||
|
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License version 3. |
||||||
|
# |
||||||
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||||
|
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||||
|
# Copyright (C) 2010-2013 the ChiliProject Team |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License |
||||||
|
# as published by the Free Software Foundation; either version 2 |
||||||
|
# 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 General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with this program; if not, write to the Free Software |
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||||
|
# |
||||||
|
# See docs/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
require 'spec_helper' |
||||||
|
|
||||||
|
require_relative '../support/pages/dashboard' |
||||||
|
|
||||||
|
describe 'Members widget on dashboard', type: :feature, js: true do |
||||||
|
let!(:project) { FactoryBot.create :project } |
||||||
|
let!(:other_project) { FactoryBot.create :project } |
||||||
|
|
||||||
|
let!(:manager_user) do |
||||||
|
FactoryBot.create :user, lastname: "Manager", member_in_project: project, member_through_role: role |
||||||
|
end |
||||||
|
let!(:no_edit_member_user) do |
||||||
|
FactoryBot.create :user, lastname: "No_Edit", member_in_project: project, member_through_role: no_edit_member_role |
||||||
|
end |
||||||
|
let!(:no_view_member_user) do |
||||||
|
FactoryBot.create :user, lastname: "No_View", member_in_project: project, member_through_role: no_view_member_role |
||||||
|
end |
||||||
|
let!(:invisible_user) do |
||||||
|
FactoryBot.create :user, lastname: "Invisible", member_in_project: other_project, member_through_role: role |
||||||
|
end |
||||||
|
|
||||||
|
let(:no_view_member_role) do |
||||||
|
FactoryBot.create(:role, |
||||||
|
permissions: %i[manage_dashboards |
||||||
|
view_dashboards]) |
||||||
|
end |
||||||
|
let(:no_edit_member_role) do |
||||||
|
FactoryBot.create(:role, |
||||||
|
permissions: %i[manage_dashboards |
||||||
|
view_dashboards |
||||||
|
view_members]) |
||||||
|
end |
||||||
|
let(:role) do |
||||||
|
FactoryBot.create(:role, |
||||||
|
permissions: %i[manage_dashboards |
||||||
|
view_dashboards |
||||||
|
manage_members |
||||||
|
view_members]) |
||||||
|
end |
||||||
|
let(:dashboard) do |
||||||
|
Pages::Dashboard.new(project) |
||||||
|
end |
||||||
|
|
||||||
|
before do |
||||||
|
login_as manager_user |
||||||
|
|
||||||
|
dashboard.visit! |
||||||
|
end |
||||||
|
|
||||||
|
def expect_all_members_visible(area) |
||||||
|
within area do |
||||||
|
expect(page) |
||||||
|
.to have_content role.name |
||||||
|
expect(page) |
||||||
|
.to have_content manager_user.name |
||||||
|
expect(page) |
||||||
|
.to have_content no_edit_member_role |
||||||
|
expect(page) |
||||||
|
.to have_content no_edit_member_user.name |
||||||
|
expect(page) |
||||||
|
.to have_content no_view_member_role |
||||||
|
expect(page) |
||||||
|
.to have_content no_view_member_user.name |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
it 'can add the widget and see the members if the permissions suffice' do |
||||||
|
# within top-right area, add an additional widget |
||||||
|
dashboard.add_widget(1, 3, 'Members') |
||||||
|
|
||||||
|
members_area = Components::Grids::GridArea.new('.grid--area.-widgeted:nth-of-type(1)') |
||||||
|
members_area.expect_to_span(1, 3, 4, 5) |
||||||
|
|
||||||
|
members_area.resize_to(7, 4) |
||||||
|
|
||||||
|
expect_all_members_visible(members_area.area) |
||||||
|
|
||||||
|
expect(page) |
||||||
|
.not_to have_content invisible_user.name |
||||||
|
|
||||||
|
within members_area.area do |
||||||
|
expect(page) |
||||||
|
.to have_link('Member') |
||||||
|
end |
||||||
|
|
||||||
|
# A user without edit permission will see the members but cannot add one |
||||||
|
login_as no_edit_member_user |
||||||
|
|
||||||
|
visit root_path |
||||||
|
dashboard.visit! |
||||||
|
|
||||||
|
expect_all_members_visible(members_area.area) |
||||||
|
|
||||||
|
within members_area.area do |
||||||
|
expect(page) |
||||||
|
.to have_no_link('Member') |
||||||
|
end |
||||||
|
|
||||||
|
# A user without view permission will not see any members |
||||||
|
login_as no_view_member_user |
||||||
|
|
||||||
|
visit root_path |
||||||
|
|
||||||
|
dashboard.visit! |
||||||
|
|
||||||
|
within members_area.area do |
||||||
|
expect(page) |
||||||
|
.to have_no_content manager_user.name |
||||||
|
|
||||||
|
expect(page) |
||||||
|
.to have_content('No visible members') |
||||||
|
|
||||||
|
expect(page) |
||||||
|
.to have_no_link('Member') |
||||||
|
|
||||||
|
expect(page) |
||||||
|
.to have_no_link('View all members') |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue