Skip to content

Commit

Permalink
Merge pull request #2402 from cloudfoundry-incubator/fix-users-table
Browse files Browse the repository at this point in the history
Users Table: Restrict org/space roles and prefix space name with org... depending on depth
  • Loading branch information
nwmac authored Jun 20, 2018
2 parents dca1d2d + 3edcac0 commit 1cba9f1
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/frontend/app/core/current-user-permissions.checker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Store } from '@ngrx/store';
import { combineLatest, Observable, of as observableOf } from 'rxjs';
import { distinctUntilChanged, filter, map, switchMap, tap } from 'rxjs/operators';
import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';

import { CFFeatureFlagTypes } from '../shared/components/cf-auth/cf-auth.types';
import {
Expand Down
5 changes: 2 additions & 3 deletions src/frontend/app/core/current-user-permissions.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

import { of as observableOf, Observable, combineLatest } from 'rxjs';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { combineLatest, Observable, of as observableOf } from 'rxjs';
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';

import { AppState } from '../store/app-state';
import {
CHECKER_GROUPS,
Expand All @@ -19,7 +19,6 @@ import {
PermissionTypes,
} from './current-user-permissions.config';


interface ICheckCombiner {
checks: Observable<boolean>[];
combineType?: '&&';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DatePipe } from '@angular/common';
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable , combineLatest , ReplaySubject } from 'rxjs';
import { combineLatest, Observable, ReplaySubject } from 'rxjs';
import { filter, first, map, pairwise, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';

import { IServiceBinding } from '../../../core/cf-api-svc.types';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ export class CloudFoundryOrganizationBaseComponent {
{
link: 'users',
label: 'Users',
// Hide the users tab unless we are in development
hidden: observableOf(environment.production)
}
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ export class CloudFoundrySpaceBaseComponent implements OnDestroy {
{
link: 'users',
label: 'Users',
// Hide the users tab unless we are in development
hidden: observableOf(environment.production)
}
];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { BaseTestModules } from '../../../../../../../test-framework/cloud-foundry-endpoint-service.helper';
import { CloudFoundrySpaceUsersComponent } from './cloud-foundry-space-users.component';
import { CloudFoundrySpaceService } from '../../../../../services/cloud-foundry-space.service';
import {
CloudFoundryOrganizationServiceMock,
} from '../../../../../../../test-framework/cloud-foundry-organization.service.mock';
import { CloudFoundrySpaceServiceMock } from '../../../../../../../test-framework/cloud-foundry-space.service.mock';
import { ActiveRouteCfOrgSpace } from '../../../../../cf-page.types';
import { CloudFoundryOrganizationService } from '../../../../../services/cloud-foundry-organization.service';
import { CloudFoundrySpaceService } from '../../../../../services/cloud-foundry-space.service';
import { CloudFoundrySpaceUsersComponent } from './cloud-foundry-space-users.component';

describe('CloudFoundrySpaceUsersComponent', () => {
let component: CloudFoundrySpaceUsersComponent;
Expand All @@ -16,7 +20,8 @@ describe('CloudFoundrySpaceUsersComponent', () => {
imports: [...BaseTestModules],
providers: [
{ provide: CloudFoundrySpaceService, useClass: CloudFoundrySpaceServiceMock },
ActiveRouteCfOrgSpace
{ provide: CloudFoundryOrganizationService, useClass: CloudFoundryOrganizationServiceMock },
ActiveRouteCfOrgSpace,
]
})
.compileComponents();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { CfUserListConfigService } from './../../../../shared/components/list/list-types/cf-users/cf-user-list-config.service';
import { ListConfig } from './../../../../shared/components/list/list.component.types';
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { CfUserService } from '../../../../shared/data-services/cf-user.service';
import { Router } from '@angular/router';
import { ActiveRouteCfOrgSpace } from '../../cf-page.types';
import { CurrentUserPermissionsService } from '../../../../core/current-user-permissions.service';
import { AppState } from '../../../../store/app-state';

@Component({
selector: 'app-cloud-foundry-users',
templateUrl: './cloud-foundry-users.component.html',
styleUrls: ['./cloud-foundry-users.component.scss'],
providers: [{
provide: ListConfig,
useClass: CfUserListConfigService
useFactory: (
store: Store<AppState>,
cfUserService: CfUserService,
router: Router,
activeRouteCfOrgSpace: ActiveRouteCfOrgSpace,
userPerms: CurrentUserPermissionsService,
) => new CfUserListConfigService(store, cfUserService, router, activeRouteCfOrgSpace, userPerms),
deps: [Store, CfUserService, Router, ActiveRouteCfOrgSpace, CurrentUserPermissionsService]
}]
})
export class CloudFoundryUsersComponent { }
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service';
import { AppState } from '../../../../../store/app-state';
import { CfUserService } from '../../../../data-services/cf-user.service';
import { CfUserDataSourceService } from '../cf-users/cf-user-data-source.service';
import { CfUserListConfigService } from '../cf-users/cf-user-list-config.service';

@Injectable()
Expand All @@ -22,8 +21,7 @@ export class CfOrgUsersListConfigService extends CfUserListConfigService {
router: Router,
activeRouteCfOrgSpace: ActiveRouteCfOrgSpace,
userPerms: CurrentUserPermissionsService) {
super(store, cfUserService, router, activeRouteCfOrgSpace, userPerms);
this.dataSource = new CfUserDataSourceService(store, cfOrgService.allOrgUsersAction, this);
super(store, cfUserService, router, activeRouteCfOrgSpace, userPerms, cfOrgService.org$);
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { inject, TestBed } from '@angular/core/testing';

import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types';
import {
CloudFoundryOrganizationService,
} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service';
import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service';
import { BaseTestModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper';
import { CloudFoundryOrganizationServiceMock } from '../../../../../test-framework/cloud-foundry-organization.service.mock';
import { CloudFoundrySpaceServiceMock } from '../../../../../test-framework/cloud-foundry-space.service.mock';
import { CfSpaceUsersListConfigService } from './cf-space-users-list-config.service';
import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types';

describe('CfSpaceUsersListConfigService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
CfSpaceUsersListConfigService,
{ provide: CloudFoundrySpaceService, useClass: CloudFoundrySpaceServiceMock },
{ provide: CloudFoundryOrganizationService, useClass: CloudFoundryOrganizationServiceMock },
ActiveRouteCfOrgSpace

],
imports: [...BaseTestModules]
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ import { Store } from '@ngrx/store';

import { CurrentUserPermissionsService } from '../../../../../core/current-user-permissions.service';
import { ActiveRouteCfOrgSpace } from '../../../../../features/cloud-foundry/cf-page.types';
import {
CloudFoundryOrganizationService,
} from '../../../../../features/cloud-foundry/services/cloud-foundry-organization.service';
import { CloudFoundrySpaceService } from '../../../../../features/cloud-foundry/services/cloud-foundry-space.service';
import { AppState } from '../../../../../store/app-state';
import { CfUserService } from '../../../../data-services/cf-user.service';
import { CfUserDataSourceService } from '../cf-users/cf-user-data-source.service';
import { CfUserListConfigService } from '../cf-users/cf-user-list-config.service';

@Injectable()
export class CfSpaceUsersListConfigService extends CfUserListConfigService {
constructor(
store: Store<AppState>,
cfSpaceService: CloudFoundrySpaceService,
cfOrgService: CloudFoundryOrganizationService,
cfUserService: CfUserService,
router: Router,
activeRouteCfOrgSpace: ActiveRouteCfOrgSpace,
userPerms: CurrentUserPermissionsService) {
super(store, cfUserService, router, activeRouteCfOrgSpace, userPerms);
this.dataSource = new CfUserDataSourceService(store, cfSpaceService.allSpaceUsersAction, this);
super(store, cfUserService, router, activeRouteCfOrgSpace, userPerms, cfOrgService.org$, cfSpaceService.space$);
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<app-chips [chips]="chipsConfig"></app-chips>
<app-chips [chips]="chipsConfig$ | async"></app-chips>
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { map } from 'rxjs/operators';
import { combineLatest } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';

import { IOrganization } from '../../../../../../core/cf-api.types';
import { CurrentUserPermissions } from '../../../../../../core/current-user-permissions.config';
import { CurrentUserPermissionsService } from '../../../../../../core/current-user-permissions.service';
import { arrayHelper } from '../../../../../../core/helper-classes/array.helper';
Expand All @@ -13,9 +15,11 @@ import { APIResource } from '../../../../../../store/types/api.types';
import { CfUser, IUserPermissionInOrg, OrgUserRoleNames } from '../../../../../../store/types/user.types';
import { CfUserService } from '../../../../../data-services/cf-user.service';
import { EntityMonitor } from '../../../../../monitors/entity-monitor';
import { AppChip } from '../../../../chips/chips.component';
import { ConfirmationDialogService } from '../../../../confirmation-dialog.service';
import { CfPermissionCell, ICellPermissionList } from '../cf-permission-cell';


@Component({
selector: 'app-org-user-permission-cell',
templateUrl: './cf-org-permission-cell.component.html',
Expand All @@ -30,14 +34,20 @@ export class CfOrgPermissionCellComponent extends CfPermissionCell<OrgUserRoleNa
confirmDialog: ConfirmationDialogService
) {
super(confirmDialog);
this.chipsConfig$ = combineLatest(
this.rowSubject.asObservable(),
this.configSubject.asObservable().pipe(switchMap(config => config.org$))
).pipe(
map(([user, org]: [APIResource<CfUser>, APIResource<IOrganization>]) => this.setChipConfig(user, org))
);
}

protected setChipConfig(row: APIResource<CfUser>) {
const userRoles = this.cfUserService.getOrgRolesFromUser(row.entity);
private setChipConfig(row: APIResource<CfUser>, org: APIResource<IOrganization>): AppChip<ICellPermissionList<OrgUserRoleNames>>[] {
const userRoles = this.cfUserService.getOrgRolesFromUser(row.entity, org);
const userOrgPermInfo = arrayHelper.flatten<ICellPermissionList<OrgUserRoleNames>>(
userRoles.map(orgPerms => this.getOrgPermissions(orgPerms, row))
);
this.chipsConfig = this.getChipConfig(userOrgPermInfo);
return this.getChipConfig(userOrgPermInfo);
}

private getOrgPermissions(orgPerms: IUserPermissionInOrg, row: APIResource<CfUser>): ICellPermissionList<OrgUserRoleNames>[] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Input } from '@angular/core';
import { Observable, of as observableOf } from 'rxjs';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { map } from 'rxjs/operators';

import { IUserRole } from '../../../../../features/cloud-foundry/cf.helpers';
import { APIResource } from '../../../../../store/types/api.types';
import { CfUser } from '../../../../../store/types/user.types';
import { AppChip } from '../../../chips/chips.component';
import { TableCellCustom } from '../../list.types';
import { ConfirmationDialogService } from '../../../confirmation-dialog.service';
import { ConfirmationDialogConfig } from '../../../confirmation-dialog.config';
import { ConfirmationDialogService } from '../../../confirmation-dialog.service';
import { TableCellCustom } from '../../list.types';


export interface ICellPermissionList<T> extends IUserRole<T> {
Expand All @@ -26,24 +26,29 @@ interface ICellPermissionUpdates {
[key: string]: Observable<boolean>;
}

export abstract class CfPermissionCell<T> extends TableCellCustom<APIResource<CfUser>> {
export abstract class CfPermissionCell<T> extends TableCellCustom<APIResource<CfUser>> {

@Input('row')
set row(row: APIResource<CfUser>) {
this.setChipConfig(row);
this.rowSubject.next(row);
this.guid = row.metadata.guid;
}
public chipsConfig: AppChip<ICellPermissionList<T>>[];

@Input('config')
set config(config: any) {
this.configSubject.next(config);
}

public chipsConfig$: Observable<AppChip<ICellPermissionList<T>>[]>;
protected guid: string;

protected rowSubject = new BehaviorSubject<APIResource<CfUser>>(null);
protected configSubject = new BehaviorSubject<any>(null);

constructor(private confirmDialog: ConfirmationDialogService) {
super();
}

protected setChipConfig(user: APIResource<CfUser>) {

}

protected getChipConfig(cellPermissionList: ICellPermissionList<T>[]) {
return cellPermissionList.map(perm => {
const chipConfig = new AppChip<ICellPermissionList<T>>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<app-chips [chips]="chipsConfig"></app-chips>
<app-chips [chips]="chipsConfig$ | async"></app-chips>
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { map } from 'rxjs/operators';
import { combineLatest, of as observableOf } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';

import { IOrganization, ISpace } from '../../../../../../core/cf-api.types';
import { CurrentUserPermissions } from '../../../../../../core/current-user-permissions.config';
import { CurrentUserPermissionsService } from '../../../../../../core/current-user-permissions.service';
import { arrayHelper } from '../../../../../../core/helper-classes/array.helper';
import { getSpaceRoles } from '../../../../../../features/cloud-foundry/cf.helpers';
import { RemoveUserPermission } from '../../../../../../store/actions/users.actions';
import { AppState } from '../../../../../../store/app-state';
import { entityFactory, spaceSchemaKey } from '../../../../../../store/helpers/entity-factory';
import { entityFactory, organizationSchemaKey, spaceSchemaKey } from '../../../../../../store/helpers/entity-factory';
import { selectEntity } from '../../../../../../store/selectors/api.selectors';
import { APIResource } from '../../../../../../store/types/api.types';
import { CfUser, IUserPermissionInSpace, SpaceUserRoleNames } from '../../../../../../store/types/user.types';
import { CfUserService } from '../../../../../data-services/cf-user.service';
import { EntityMonitor } from '../../../../../monitors/entity-monitor';
import { ConfirmationDialogService } from '../../../../confirmation-dialog.service';
import { CfPermissionCell, ICellPermissionList } from '../cf-permission-cell';


@Component({
selector: 'app-cf-space-permission-cell',
templateUrl: './cf-space-permission-cell.component.html',
Expand All @@ -31,14 +35,53 @@ export class CfSpacePermissionCellComponent extends CfPermissionCell<SpaceUserRo
confirmDialog: ConfirmationDialogService
) {
super(confirmDialog);
this.chipsConfig$ = combineLatest(
this.rowSubject.asObservable(),
this.configSubject.asObservable().pipe(switchMap(config => config.org$)),
this.configSubject.asObservable().pipe(switchMap(config => config.spaces$))
).pipe(
switchMap(([user, org, spaces]: [APIResource<CfUser>, APIResource<IOrganization>, APIResource<ISpace>[]]) => {
const permissionList = this.createPermissions(user, spaces && spaces.length ? spaces : null);
// If we're showing spaces from multiple orgs prefix the org name to the space name
return org ? observableOf(this.getChipConfig(permissionList)) : this.prefixOrgName(permissionList);
})
);
}

private prefixOrgName(permissionList) {
// Find all unique org guids
const orgGuids = permissionList.map(permission => permission.orgGuid).filter((value, index, self) => self.indexOf(value) === index);
// Find names of all orgs
const orgNames$ = combineLatest(
orgGuids.map(orgGuid => this.store.select<APIResource<IOrganization>>(selectEntity(organizationSchemaKey, orgGuid)))
).pipe(
map((orgs: APIResource<IOrganization>[]) => {
const orgNames: { [orgGuid: string]: string } = {};
orgs.forEach(org => {
orgNames[org.metadata.guid] = org.entity.name;
});
return orgNames;
})
);
return combineLatest(
observableOf(permissionList),
orgNames$
).pipe(
map(([permissions, orgNames]) => {
// Prefix permission name with org name
permissions.forEach(permission => {
permission.name = `${orgNames[permission.orgGuid]}: ${permission.name}`;
});
return this.getChipConfig(permissions);
})
);
}

protected setChipConfig(row: APIResource<CfUser>) {
const userRoles = this.cfUserService.getSpaceRolesFromUser(row.entity);
const userPermInfo = arrayHelper.flatten<ICellPermissionList<SpaceUserRoleNames>>(
private createPermissions(row: APIResource<CfUser>, spaces: APIResource<ISpace>[]): ICellPermissionList<SpaceUserRoleNames>[] {
const userRoles = this.cfUserService.getSpaceRolesFromUser(row.entity, spaces);
return arrayHelper.flatten<ICellPermissionList<SpaceUserRoleNames>>(
userRoles.map(spacePerms => this.getSpacePermissions(spacePerms, row))
);
this.chipsConfig = this.getChipConfig(userPermInfo);
}

private getSpacePermissions(spacePerms: IUserPermissionInSpace, row: APIResource<CfUser>) {
Expand Down
Loading

0 comments on commit 1cba9f1

Please sign in to comment.