src/frontend/packages/cloud-foundry/src/features/cf/cf.helpers.ts
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import { combineLatest, Observable } from 'rxjs';
import { filter, first, map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators';
import { PermissionConfig } from '../../../../core/src/core/permissions/current-user-permissions.config';
import { CurrentUserPermissionsService } from '../../../../core/src/core/permissions/current-user-permissions.service';
import { getIdFromRoute, pathGet } from '../../../../core/src/core/utils.service';
import {
extractActualListEntity,
} from '../../../../core/src/shared/components/list/data-sources-controllers/local-filtering-sorting';
import { SetClientFilter } from '../../../../store/src/actions/pagination.actions';
import { RouterNav } from '../../../../store/src/actions/router.actions';
import { AppState } from '../../../../store/src/app-state';
import { PaginationMonitorFactory } from '../../../../store/src/monitors/pagination-monitor.factory';
import { getPaginationObservables } from '../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper';
import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors';
import { selectPaginationState } from '../../../../store/src/selectors/pagination.selectors';
import { APIResource } from '../../../../store/src/types/api.types';
import { EndpointModel } from '../../../../store/src/types/endpoint.types';
import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types';
import { IServiceInstance, IUserProvidedServiceInstance } from '../../cf-api-svc.types';
import { CFFeatureFlagTypes, IApp, ISpace } from '../../cf-api.types';
import { CFAppState } from '../../cf-app-state';
import { cfEntityFactory } from '../../cf-entity-factory';
import { getCFEntityKey } from '../../cf-entity-helpers';
import { applicationEntityType } from '../../cf-entity-types';
import { CFEntityConfig } from '../../cf-types';
import { ListCfRoute } from '../../shared/components/list/list-types/cf-routes/cf-routes-data-source-base';
import { getCurrentUserCFEndpointRolesState } from '../../store/selectors/cf-current-user-role.selectors';
import { ICfRolesState } from '../../store/types/cf-current-user-roles.types';
import {
CfUser,
CfUserRoleParams,
OrgUserRoleNames,
SpaceUserRoleNames,
UserRoleInOrg,
UserRoleInSpace,
} from '../../store/types/cf-user.types';
import { UserRoleLabels } from '../../store/types/users-roles.types';
import { CfCurrentUserPermissions, CfPermissionTypes } from '../../user-permissions/cf-user-permissions-checkers';
import { ActiveRouteCfCell, ActiveRouteCfOrgSpace } from './cf-page.types';
export interface IUserRole<T> {
string: string;
key: T;
}
export function getOrgRolesString(userRolesInOrg: UserRoleInOrg): string {
let roles = null;
if (userRolesInOrg[OrgUserRoleNames.MANAGER]) {
roles = UserRoleLabels.org.short[OrgUserRoleNames.MANAGER];
}
if (userRolesInOrg[OrgUserRoleNames.BILLING_MANAGERS]) {
roles = assignRole(roles, UserRoleLabels.org.short[OrgUserRoleNames.BILLING_MANAGERS]);
}
if (userRolesInOrg[OrgUserRoleNames.AUDITOR]) {
roles = assignRole(roles, UserRoleLabels.org.short[OrgUserRoleNames.AUDITOR]);
}
if (userRolesInOrg[OrgUserRoleNames.USER] && !userRolesInOrg[OrgUserRoleNames.MANAGER]) {
roles = assignRole(roles, UserRoleLabels.org.short[OrgUserRoleNames.USER]);
}
return roles ? roles : 'None';
}
export function getSpaceRolesString(userRolesInSpace: UserRoleInSpace): string {
let roles = null;
if (userRolesInSpace[SpaceUserRoleNames.MANAGER]) {
roles = UserRoleLabels.space.short[SpaceUserRoleNames.MANAGER];
}
if (userRolesInSpace[SpaceUserRoleNames.AUDITOR]) {
roles = assignRole(roles, UserRoleLabels.space.short[SpaceUserRoleNames.AUDITOR]);
}
if (userRolesInSpace[SpaceUserRoleNames.DEVELOPER]) {
roles = assignRole(roles, UserRoleLabels.space.short[SpaceUserRoleNames.DEVELOPER]);
}
return roles ? roles : 'None';
}
export function getOrgRoles(userRolesInOrg: UserRoleInOrg): IUserRole<OrgUserRoleNames>[] {
const roles = [];
if (userRolesInOrg[OrgUserRoleNames.MANAGER]) {
roles.push({
string: UserRoleLabels.org.short[OrgUserRoleNames.MANAGER],
key: OrgUserRoleNames.MANAGER
});
}
if (userRolesInOrg[OrgUserRoleNames.BILLING_MANAGERS]) {
roles.push({
string: UserRoleLabels.org.short[OrgUserRoleNames.BILLING_MANAGERS],
key: OrgUserRoleNames.BILLING_MANAGERS
});
}
if (userRolesInOrg[OrgUserRoleNames.AUDITOR]) {
roles.push({
string: UserRoleLabels.org.short[OrgUserRoleNames.AUDITOR],
key: OrgUserRoleNames.AUDITOR
});
}
if (userRolesInOrg[OrgUserRoleNames.USER]) {
roles.push({
string: UserRoleLabels.org.short[OrgUserRoleNames.USER],
key: OrgUserRoleNames.USER
});
}
return roles;
}
export function getSpaceRoles(userRolesInSpace: UserRoleInSpace): IUserRole<SpaceUserRoleNames>[] {
const roles = [];
if (userRolesInSpace[SpaceUserRoleNames.MANAGER]) {
roles.push({
string: UserRoleLabels.space.short[SpaceUserRoleNames.MANAGER],
key: SpaceUserRoleNames.MANAGER
});
}
if (userRolesInSpace[SpaceUserRoleNames.AUDITOR]) {
roles.push({
string: UserRoleLabels.space.short[SpaceUserRoleNames.AUDITOR],
key: SpaceUserRoleNames.AUDITOR
});
}
if (userRolesInSpace[SpaceUserRoleNames.DEVELOPER]) {
roles.push({
string: UserRoleLabels.space.short[SpaceUserRoleNames.DEVELOPER],
key: SpaceUserRoleNames.DEVELOPER
});
}
return roles;
}
function assignRole(currentRoles: string, role: string) {
const newRoles = currentRoles ? `${currentRoles}, ${role}` : role;
return newRoles;
}
export function isOrgManager(user: CfUser, guid: string): boolean {
return hasRole(user, guid, CfUserRoleParams.MANAGED_ORGS);
}
export function isOrgBillingManager(user: CfUser, guid: string): boolean {
return hasRole(user, guid, CfUserRoleParams.BILLING_MANAGER_ORGS);
}
export function isOrgAuditor(user: CfUser, guid: string): boolean {
return hasRole(user, guid, CfUserRoleParams.AUDITED_ORGS);
}
export function isOrgUser(user: CfUser, guid: string): boolean {
return hasRole(user, guid, CfUserRoleParams.ORGANIZATIONS);
}
export function isSpaceManager(user: CfUser, spaceGuid: string): boolean {
return hasRole(user, spaceGuid, CfUserRoleParams.MANAGED_SPACES);
}
export function isSpaceAuditor(user: CfUser, spaceGuid: string): boolean {
return hasRole(user, spaceGuid, CfUserRoleParams.AUDITED_SPACES);
}
export function isSpaceDeveloper(user: CfUser, spaceGuid: string): boolean {
return hasRole(user, spaceGuid, CfUserRoleParams.SPACES);
}
export function hasRoleWithinOrg(user: CfUser, orgGuid: string): boolean {
return isOrgManager(user, orgGuid) ||
isOrgBillingManager(user, orgGuid) ||
isOrgAuditor(user, orgGuid) ||
isOrgUser(user, orgGuid);
}
export function hasRoleWithinSpace(user: CfUser, spaceGuid: string): boolean {
return isSpaceManager(user, spaceGuid) ||
isSpaceAuditor(user, spaceGuid) ||
isSpaceDeveloper(user, spaceGuid);
}
export function hasRoleWithin(user: CfUser, orgGuid?: string, spaceGuid?: string): boolean {
return hasRoleWithinOrg(user, orgGuid) || hasRoleWithinSpace(user, spaceGuid);
}
export function hasSpaceRoleWithinOrg(user: CfUser, orgGuid: string): boolean {
const roles = [
CfUserRoleParams.MANAGED_SPACES,
CfUserRoleParams.AUDITED_SPACES,
CfUserRoleParams.SPACES
];
const orgSpaces = [];
for (const role of roles) {
const roleSpaces = user[role] as APIResource<ISpace>[];
orgSpaces.push(...roleSpaces.filter((space) => {
return space.entity.organization_guid === orgGuid;
}));
}
return orgSpaces.some((space) => hasRoleWithinSpace(user, space.metadata.guid));
}
function hasRole(user: CfUser, guid: string, roleType: string) {
if (user[roleType]) {
const roles = user[roleType] as APIResource[];
return !!roles.find(o => o ? o.metadata.guid === guid : false);
}
return false;
}
export function getActiveRouteCfOrgSpace(activatedRoute: ActivatedRoute) {
return ({
cfGuid: getIdFromRoute(activatedRoute, 'endpointId'),
orgGuid: getIdFromRoute(activatedRoute, 'orgId'),
spaceGuid: getIdFromRoute(activatedRoute, 'spaceId'),
});
}
export function getActiveRouteCfCell(activatedRoute: ActivatedRoute) {
return ({
cfGuid: getIdFromRoute(activatedRoute, 'endpointId'),
cellId: getIdFromRoute(activatedRoute, 'cellId'),
});
}
export const getActiveRouteCfOrgSpaceProvider = {
provide: ActiveRouteCfOrgSpace,
useFactory: getActiveRouteCfOrgSpace,
deps: [
ActivatedRoute,
]
};
export const getActiveRouteCfCellProvider = {
provide: ActiveRouteCfCell,
useFactory: getActiveRouteCfCell,
deps: [
ActivatedRoute,
]
};
export function goToAppWall(store: Store<CFAppState>, cfGuid: string, orgGuid?: string, spaceGuid?: string) {
const appWallPagKey = 'applicationWall';
const entityKey = getCFEntityKey(applicationEntityType);
store.dispatch(new SetClientFilter(new CFEntityConfig(applicationEntityType), appWallPagKey,
{
string: '',
items: {
cf: cfGuid,
org: orgGuid,
space: spaceGuid
}
}
));
store.select(selectPaginationState(entityKey, appWallPagKey)).pipe(
filter((state: PaginationEntityState) => {
const items = pathGet('clientPagination.filter.items', state);
return items ? items.cf === cfGuid && items.org === orgGuid && items.space === spaceGuid : false;
}),
first(),
tap(() => {
store.dispatch(new RouterNav({ path: ['applications'] }));
})
).subscribe();
}
export function canUpdateOrgSpaceRoles(
perms: CurrentUserPermissionsService,
cfGuid: string,
orgGuid?: string,
spaceGuid?: string): Observable<boolean> {
return combineLatest(
perms.can(CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, cfGuid, orgGuid),
perms.can(CfCurrentUserPermissions.SPACE_CHANGE_ROLES, cfGuid, orgGuid, spaceGuid)
).pipe(
map((checks: boolean[]) => checks.some(check => check))
);
}
export function canUpdateOrgRoles(
perms: CurrentUserPermissionsService,
cfGuid: string,
orgGuid?: string): Observable<boolean> {
return perms.can(CfCurrentUserPermissions.ORGANIZATION_CHANGE_ROLES, cfGuid, orgGuid);
}
export function waitForCFPermissions(store: Store<AppState>, cfGuid: string): Observable<ICfRolesState> {
return store.select<ICfRolesState>(getCurrentUserCFEndpointRolesState(cfGuid)).pipe(
filter(cf => cf && cf.state.initialised),
first(),
publishReplay(1),
refCount(),
);
}
export function selectConnectedCfs(store: Store<AppState>): Observable<EndpointModel[]> {
return store.select(endpointEntitiesSelector).pipe(
map(endpoints => Object.values(endpoints)),
map(endpoints => endpoints.filter(endpoint => endpoint.cnsi_type === 'cf' && endpoint.connectionStatus === 'connected')),
);
}
export function haveMultiConnectedCfs(store: Store<AppState>): Observable<boolean> {
return selectConnectedCfs(store).pipe(
map(connectedCfs => connectedCfs.length > 1)
);
}
export function filterEntitiesByGuid<T>(guid: string, array?: Array<APIResource<T>>): Array<APIResource<T>> {
return array ? array.filter(entity => entity.metadata.guid === guid) : null;
}
export function createFetchTotalResultsPagKey(standardActionKey: string): string {
return standardActionKey + '-totalResults';
}
export function fetchTotalResults(
action: PaginatedAction,
store: Store<AppState>,
paginationMonitorFactory: PaginationMonitorFactory
): Observable<number> {
const newAction = {
...action,
paginationKey: createFetchTotalResultsPagKey(action.paginationKey),
flattenPagination: false,
includeRelations: []
};
newAction.initialParams['results-per-page'] = 1;
const pagObs = getPaginationObservables({
store,
action: newAction,
paginationMonitor: paginationMonitorFactory.create(
newAction.paginationKey,
cfEntityFactory(newAction.entityType),
newAction.flattenPagination
)
}, newAction.flattenPagination);
return combineLatest(
pagObs.entities$, // Ensure the request is made by sub'ing to the entities observable
pagObs.pagination$
).pipe(
map(([, pagination]) => pagination),
filter(pagination => !!pagination && !!pagination.pageRequests && !!pagination.pageRequests[1] && !pagination.pageRequests[1].busy),
first(),
map(pagination => pagination.totalResults)
);
}
type CfOrgSpaceFilterTypes = IApp | ListCfRoute | IServiceInstance;
export const cfOrgSpaceFilter = (entities: APIResource[], paginationState: PaginationEntityState) => {
// Filtering is done remotely when maxedResults are hit (see `setMultiFilter`)
if (!!paginationState.maxedState.isMaxedMode && !paginationState.maxedState.ignoreMaxed) {
return entities;
}
const fetchOrgGuid = (e: APIResource<CfOrgSpaceFilterTypes>): string => {
return e.entity.space ? e.entity.space.entity.organization_guid : null;
};
// Filter by cf/org/space
const cfGuid = paginationState.clientPagination.filter.items.cf;
const orgGuid = paginationState.clientPagination.filter.items.org;
const spaceGuid = paginationState.clientPagination.filter.items.space;
return !cfGuid && !orgGuid && !spaceGuid ? entities : entities.filter(e => {
e = extractActualListEntity(e);
const validCF = !(cfGuid && cfGuid !== e.entity.cfGuid);
const validOrg = !(orgGuid && orgGuid !== fetchOrgGuid(e));
const validSpace = !(spaceGuid && spaceGuid !== e.entity.space_guid);
return validCF && validOrg && validSpace;
});
};
export function createCfOrgSpaceSteppersUrl(
cfGuid: string,
stepperPath: string = `/users/manage`,
orgGuid?: string,
spaceGuid?: string,
): string {
let route = `/cloud-foundry/${cfGuid}`;
if (orgGuid) {
route += `/organizations/${orgGuid}`;
if (spaceGuid) {
route += `/spaces/${spaceGuid}`;
}
}
route += stepperPath;
return route;
}
export function createCfOrgSpaceUserRemovalUrl(
cfGuid: string,
orgGuid?: string,
spaceGuid?: string,
): string {
let route = `/cloud-foundry/${cfGuid}`;
if (orgGuid) {
route += `/organizations/${orgGuid}`;
if (spaceGuid) {
route += `/spaces/${spaceGuid}`;
}
}
route += '/users/remove';
return route;
}
export function isServiceInstance(obj: any): IServiceInstance {
return !!obj && !!obj.service_plan_url ? obj as IServiceInstance : null;
}
export function isUserProvidedServiceInstance(obj: any): IUserProvidedServiceInstance {
return !!obj && (obj.route_service_url !== null && obj.route_service_url !== undefined) ? obj as IUserProvidedServiceInstance : null;
}
export function someFeatureFlags(
ff: CFFeatureFlagTypes[],
cfGuid: string,
store: Store<AppState>,
userPerms: CurrentUserPermissionsService,
): Observable<boolean> {
return waitForCFPermissions(store, cfGuid).pipe(
switchMap(() => combineLatest(ff.map(flag => {
const permConfig = new PermissionConfig(CfPermissionTypes.FEATURE_FLAG, flag);
return userPerms.can(permConfig, cfGuid);
}))),
map(results => results.some(result => !!result))
);
}