Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix three issues around 100+ roles #2613

Merged
merged 6 commits into from
Jul 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { combineLatest, of as observableOf } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { map, switchMap, first, filter } from 'rxjs/operators';

import { IOrganization, ISpace } from '../../../../../../core/cf-api.types';
import { CurrentUserPermissions } from '../../../../../../core/current-user-permissions.config';
Expand Down Expand Up @@ -55,6 +55,8 @@ export class CfSpacePermissionCellComponent extends CfPermissionCell<SpaceUserRo
const orgNames$ = combineLatest(
orgGuids.map(orgGuid => this.store.select<APIResource<IOrganization>>(selectEntity(organizationSchemaKey, orgGuid)))
).pipe(
filter(org => !!org),
first(),
map((orgs: APIResource<IOrganization>[]) => {
const orgNames: { [orgGuid: string]: string } = {};
orgs.forEach(org => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,27 +130,53 @@ export class CfUserListConfigService extends ListConfig<APIResource<CfUser>> {
* @memberof CfUserListConfigService
*/
private assignColumnConfig = (
org$: Observable<EntityInfo<APIResource<IOrganization>>> = observableOf(null),
space$: Observable<EntityInfo<APIResource<ISpace>>> = observableOf(null)) => {
org$?: Observable<EntityInfo<APIResource<IOrganization>>>,
space$?: Observable<EntityInfo<APIResource<ISpace>>>) => {

const { safeOrg$, safeSpaces$ } = this.getSafeObservables(org$, space$);

this.columns.find(column => column.columnId === 'roles').cellConfig = {
org$: org$.pipe(map(org => org ? org.entity : null))
org$: safeOrg$
};
this.columns.find(column => column.columnId === 'space-roles').cellConfig = {
org$: org$.pipe(map(org => org ? org.entity : null)),
spaces$: combineLatest(org$, space$ || observableOf(null)).pipe(
map(([org, space]) => {
if (space) {
return [space.entity];
} else if (org && org.entity.entity.spaces && org.entity.entity.spaces.length) {
return org.entity.entity.spaces;
} else {
return null;
}
})
)
org$: safeOrg$,
spaces$: safeSpaces$
};
}

private getSafeObservables(
org$?: Observable<EntityInfo<APIResource<IOrganization>>>,
space$?: Observable<EntityInfo<APIResource<ISpace>>>
) {
if (space$ && org$) {
// List should show specific org and specific space roles
return {
safeOrg$: org$.pipe(map(org => org ? org.entity : null)),
safeSpaces$: space$.pipe(
map(space => [space.entity])
)
};
} else if (org$) {
// List should show specific org and space roles from with org
const safeOrg$ = org$.pipe(map(org => org ? org.entity : null));
return {
safeOrg$,
safeSpaces$: safeOrg$.pipe(
map((org: APIResource<IOrganization>) => org.entity.spaces),
// Important for when we fetch spaces async. This prevents the null passing through, which would mean all spaces are shown aka use
// case below
filter(spaces => !!spaces)
)
};
} else {
// List should show all org and all space roles
return {
safeOrg$: observableOf(null),
safeSpaces$: observableOf(null)
};
}
}

private createCanUpdateOrgSpaceRoles = () => canUpdateOrgSpaceRoles(
this.userPerms,
this.activeRouteCfOrgSpace.cfGuid,
Expand Down
47 changes: 17 additions & 30 deletions src/frontend/app/shared/data-services/cf-user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,44 +159,31 @@ export class CfUserService {
return res;
}

private populatedArray(array?: Array<any>): boolean {
return array && !!array.length;
}

/**
* Helper to determine if user has roles other than Org User
*/
hasRolesInOrg(user: CfUser, orgGuid: string, excludeOrgUser = true): boolean {

const orgRoles = this.getOrgRolesFromUser(user).filter(o => o.orgGuid === orgGuid);
const spaceRoles = this.getSpaceRolesFromUser(user).filter(o => o.orgGuid === orgGuid);

for (const roleKey in orgRoles) {
if (!orgRoles.hasOwnProperty(roleKey)) {
continue;
}

const permissions = orgRoles[roleKey].permissions;
if (
permissions[OrgUserRoleNames.MANAGER] ||
permissions[OrgUserRoleNames.BILLING_MANAGERS] ||
permissions[OrgUserRoleNames.AUDITOR]
) {
return true;
}
if (!excludeOrgUser && permissions[OrgUserRoleNames.USER]) {
return true;
}
// Check org roles
if (this.populatedArray(user.audited_organizations) ||
this.populatedArray(user.billing_managed_organizations) ||
this.populatedArray(user.managed_organizations) ||
(!excludeOrgUser && this.populatedArray(user.organizations))) {
return true;
}

for (const roleKey in spaceRoles) {
if (!spaceRoles.hasOwnProperty(roleKey)) {
continue;
}

const permissions = spaceRoles[roleKey].permissions;
if (permissions[SpaceUserRoleNames.MANAGER] ||
permissions[SpaceUserRoleNames.AUDITOR] ||
permissions[SpaceUserRoleNames.DEVELOPER]) {
return true;
}
// Check space roles
if (this.populatedArray(user.audited_spaces) ||
this.populatedArray(user.managed_spaces) ||
this.populatedArray(user.spaces)) {
return true;
}

return false;
}

getUserRoleInOrg = (
Expand Down
7 changes: 4 additions & 3 deletions src/frontend/app/store/helpers/entity-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export class EntitySchema extends schema.Entity {
* @param {string} entityKey As per schema.Entity ctor
* @param {Schema} [definition] As per schema.Entity ctor
* @param {schema.EntityOptions} [options] As per schema.Entity ctor
* @param {string} [relationKey] Allows multiple children of the same type within a single parent entity
* @param {string} [relationKey] Allows multiple children of the same type within a single parent entity. For instance user with developer
* spaces, manager spaces, auditor space, etc
* @memberof EntitySchema
*/
constructor(
Expand Down Expand Up @@ -339,14 +340,14 @@ const orgUserEntity = {
}
};
const OrganizationUserSchema = new EntitySchema(
organizationSchemaKey, orgUserEntity, { idAttribute: getAPIResourceGuid }, 'users_organizations');
organizationSchemaKey, orgUserEntity, { idAttribute: getAPIResourceGuid }, 'organizations');
const OrganizationAuditedSchema = new EntitySchema(
organizationSchemaKey, orgUserEntity, { idAttribute: getAPIResourceGuid }, 'audited_organizations');
const OrganizationManagedSchema = new EntitySchema(
organizationSchemaKey, orgUserEntity, { idAttribute: getAPIResourceGuid }, 'managed_organizations');
const OrganizationBillingSchema = new EntitySchema(
organizationSchemaKey, orgUserEntity, { idAttribute: getAPIResourceGuid }, 'billing_managed_organizations');
const SpaceUserSchema = new EntitySchema(spaceSchemaKey, {}, { idAttribute: getAPIResourceGuid }, 'users_spaces');
const SpaceUserSchema = new EntitySchema(spaceSchemaKey, {}, { idAttribute: getAPIResourceGuid }, 'spaces');
const SpaceManagedSchema = new EntitySchema(spaceSchemaKey, {}, { idAttribute: getAPIResourceGuid }, 'managed_spaces');
const SpaceAuditedSchema = new EntitySchema(spaceSchemaKey, {}, { idAttribute: getAPIResourceGuid }, 'audited_spaces');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ function populateParentEntity(state, successAction) {

const action: EntityInlineChildAction = successAction.apiAction as EntityInlineChildAction;
const response = successAction.response;
const entities = pathGet(`entities.${successAction.apiAction.entityKey}`, response) || {};
const entityKey = successAction.apiAction.entityKey;
const entity = successAction.apiAction.entity;
const entities = pathGet(`entities.${entityKey}`, response) || {};
if (!Object.values(entities)) {
return;
}
Expand All @@ -103,10 +105,12 @@ function populateParentEntity(state, successAction) {
type: '',
entity: action.parentEntitySchema,
entityKey: parentEntityKey,
includeRelations: [createEntityRelationKey(parentEntityKey, successAction.apiAction.entityKey)],
includeRelations: [createEntityRelationKey(parentEntityKey, entityKey)],
populateMissing: null,
});
const childRelation = parentEntityTree.rootRelation.childRelations.find(rel => rel.entityKey === successAction.apiAction.entityKey);
const relationKey = entity.length ? entity[0].relationKey : entity.relationKey;
const childRelation = parentEntityTree.rootRelation.childRelations.find(rel =>
relationKey ? rel.paramName === relationKey : rel.entityKey === entityKey);
const entityParamName = childRelation.paramName;

let newParentEntity = pathGet(`${parentEntityKey}.${parentGuid}`, state);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { Observable, of as observableOf } from 'rxjs';

import { GetAllOrgUsers } from '../store/actions/organization.actions';
import { getDefaultRequestState } from '../store/reducers/api-request-reducer/types';
import { APIResource, EntityInfo } from '../store/types/api.types';

export class CloudFoundryOrganizationServiceMock {
org$: Observable<EntityInfo<APIResource<any>>> = observableOf(
{
entity: {
entity: {
spaces: [],
status: ''
},
metadata: null
},
entityRequestInfo: getDefaultRequestState()
});
allOrgUsersAction = new GetAllOrgUsers('guid', 'guid-key', 'guid');
allOrgUsers = {};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Observable, of as observableOf } from 'rxjs';

import {of as observableOf, Observable } from 'rxjs';
import { APIResource, EntityInfo } from '../store/types/api.types';
import { ISpace } from '../core/cf-api.types';
import { GetAllSpaceUsers } from '../store/actions/space.actions';

export class CloudFoundrySpaceServiceMock {
Expand Down