From b7dcdf5706984c66d70d1f514244f93c718a6d00 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 10 Mar 2020 11:17:56 +0000 Subject: [PATCH 01/12] Fix delete org when org contains spaces --- .../src/actions/organization.actions.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts index 74b83f40d0..b6fb92a619 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts @@ -1,10 +1,9 @@ -import { HttpHeaders, HttpRequest } from '@angular/common/http'; +import { HttpParams, HttpRequest } from '@angular/common/http'; import { IUpdateOrganization } from '../../../core/src/core/cf-api.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { ICFAction } from '../../../store/src/types/request.types'; -import { CFEntityConfig } from '../cf-types'; import { cfEntityFactory } from '../cf-entity-factory'; import { cfUserEntityType, @@ -13,6 +12,7 @@ import { spaceEntityType, spaceWithOrgEntityType, } from '../cf-entity-types'; +import { CFEntityConfig } from '../cf-types'; import { createEntityRelationPaginationKey, EntityInlineChildAction, @@ -173,9 +173,11 @@ export class DeleteOrganization extends CFStartAction implements ICFAction { 'DELETE', `organizations/${guid}`, { - params: new HttpHeaders({ - recursive: 'true', - async: 'false' + params: new HttpParams({ + fromObject: { + recursive: 'true', + async: 'false' + } }) } ); From b3e03f8c9f3a6a856ff9627af75ef3e405aa274d Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 10 Mar 2020 11:18:28 +0000 Subject: [PATCH 02/12] Fix scaling related test scripts --- deploy/tools/populate-cf/create-many-apps.sh | 8 ++++---- deploy/tools/populate-cf/create-many-orgs.sh | 13 ++++++++----- deploy/tools/populate-cf/create-many-spaces.sh | 11 ++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/deploy/tools/populate-cf/create-many-apps.sh b/deploy/tools/populate-cf/create-many-apps.sh index a7246096aa..d204664132 100755 --- a/deploy/tools/populate-cf/create-many-apps.sh +++ b/deploy/tools/populate-cf/create-many-apps.sh @@ -1,5 +1,5 @@ #!/bin/bash -CREATE=true +CREATE="true" ORG=many-apps SPACE=many-apps APP_PREFIX=many-apps @@ -25,12 +25,12 @@ done echo "Creating $COUNT apps in org '$ORG' space '$SPACE'" # cf login -a https://api.local.pcfdev.io --skip-ssl-validation -if [ "$CREATE" = true ]; then +if [ "$CREATE" = "true" ]; then cf create-org $ORG fi cf target -o $ORG -if [ "$CREATE" = true ]; then +if [ "$CREATE" = "true" ]; then cf create-space $SPACE fi cf target -s $SPACE @@ -48,4 +48,4 @@ do cf bind-service $APP $SERVICE fi done -echo "Created $COUNT apps in org '$ORG' space '$SPACE'" \ No newline at end of file +echo "Created $counter apps in org '$ORG' space '$SPACE'" \ No newline at end of file diff --git a/deploy/tools/populate-cf/create-many-orgs.sh b/deploy/tools/populate-cf/create-many-orgs.sh index 5c880d0fee..344c69b64a 100755 --- a/deploy/tools/populate-cf/create-many-orgs.sh +++ b/deploy/tools/populate-cf/create-many-orgs.sh @@ -5,7 +5,7 @@ COUNT=10 SPACE_COUNT= DELETE=false -while getopts o:c:s: option +while getopts o:c:s:d: option do case "${option}" in @@ -18,22 +18,25 @@ done echo "Creating $COUNT orgs with $SPACE_COUNT spaces" -counter=0 +counter=14 COUNT=$(expr $COUNT - 1) while [ $counter -le $COUNT ] do ORG=$ORG_PREFIX-$counter if [ "$DELETE" == "true" ]; then + # echo "DELETE $ORG" cf delete-org $ORG else + # echo "CREATE $ORG" cf create-org $ORG fi - if [ "$SPACE_COUNT" == "true" ]; then + if [ "$SPACE_COUNT" != "" ]; then + # echo "$ORG SPACES: $SPACE_COUNT" cf target -o $ORG - ./create-many-spaces.sh -o "$ORG" -s "$ORG-spaces" -c $SPACE_COUNT -a 0 -r 0 + ./create-many-spaces.sh -o "$ORG" -s "$ORG-spaces" -c $SPACE_COUNT -a 0 -r 0 -p "false" fi ((counter++)) done -echo "Created $COUNT orgs with $SPACE_COUNT spaces" +echo "Created $counter orgs with $SPACE_COUNT spaces" diff --git a/deploy/tools/populate-cf/create-many-spaces.sh b/deploy/tools/populate-cf/create-many-spaces.sh index 15129749fe..a9aa915e90 100755 --- a/deploy/tools/populate-cf/create-many-spaces.sh +++ b/deploy/tools/populate-cf/create-many-spaces.sh @@ -1,5 +1,5 @@ #!/bin/bash -CREATE=true +CREATE_ORG_SPACE="true" ORG=many-spaces SPACE_PREFIX=many-spaces COUNT=10 @@ -10,11 +10,12 @@ SERVICE_COUNT=0 SERVICE= SERVICE_PLAN= -while getopts o:s:c:a:r:d:j:v:i: option +while getopts o:p:s:c:a:r:d:j:v:i: option do case "${option}" in o) ORG=${OPTARG};; + p) CREATE_ORG_SPACE=${OPTARG};; s) SPACE_PREFIX=${OPTARG};; c) COUNT=${OPTARG};; a) APP_COUNT=${OPTARG};; @@ -29,7 +30,7 @@ done echo "Creating $COUNT spaces with '$APP_COUNT' apps in org '$ORG'" -if [ "$CREATE" = true ]; then +if [ "$CREATE_ORG_SPACE" = "true" ]; then cf create-org $ORG fi cf target -o $ORG @@ -45,6 +46,6 @@ do if [ -n "$SERVICE" ]; then ./create-many-services.sh -o "$ORG" -s "$SPACE" -a "$SERVICE_INSTANCE" -c $SERVICE_COUNT -e false -v "$SERVICE" -i "$SERVICE_PLAN" fi - ./create-many-apps.sh -o "$ORG" -s "$SPACE" -a "$SPACE-app-" -c $APP_COUNT -r "false" -r $APP_ROUTES -d "$DOMAIN" -v "$SERVICE_INSTANCE-0" + ./create-many-apps.sh -o "$ORG" -s "$SPACE" -a "$SPACE-app-" -c $APP_COUNT -r "false" -r $APP_ROUTES -d "$DOMAIN" -v "$SERVICE_INSTANCE-0" -e $CREATE_ORG_SPACE done -echo "Created $COUNT spaces with '$APP_COUNT' apps in org '$ORG'" \ No newline at end of file +echo "Created $counter spaces with '$APP_COUNT' apps in org '$ORG'" \ No newline at end of file From 5e23ea71891d39826b05afde69705806f6439019 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Tue, 10 Mar 2020 15:01:36 +0000 Subject: [PATCH 03/12] Fix double fetching of missing user relations for getUser - getUser would first attempt to fetch all users via getUsers - getUsers could take a long time due to missing relations (users with more than 50 per role) - getUser wouldn't wait for this to finish and then try and make an individual request to fetch user - this user would then also be validated and missing relations fetched --- .../users/manage-users/cf-roles.service.ts | 9 ++-- .../cf-org-card/cf-org-card.component.ts | 8 +-- .../shared/data-services/cf-user.service.ts | 51 ++++++++++++++----- .../store/src/effects/request.effects.ts | 2 +- .../pagination-reducer.helper.ts | 14 ++--- 5 files changed, 55 insertions(+), 29 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts index f200c933cf..44eb751e87 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts @@ -20,9 +20,10 @@ import { import { IOrganization, ISpace } from '../../../../../../core/src/core/cf-api.types'; import { CurrentUserPermissionsChecker } from '../../../../../../core/src/core/current-user-permissions.checker'; import { CurrentUserPermissionsService } from '../../../../../../core/src/core/current-user-permissions.service'; +import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog.service'; import { EntityServiceFactory } from '../../../../../../store/src/entity-service-factory.service'; -import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { endpointSchemaKey } from '../../../../../../store/src/helpers/entity-factory'; +import { PaginationMonitorFactory } from '../../../../../../store/src/monitors/pagination-monitor.factory'; import { getPaginationObservables } from '../../../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { selectUsersRolesCf, @@ -30,17 +31,16 @@ import { selectUsersRolesRoles, } from '../../../../../../store/src/selectors/users-roles.selector'; import { APIResource, EntityInfo } from '../../../../../../store/src/types/api.types'; +import { UsersRolesSetChanges } from '../../../../actions/users-roles.actions'; import { CFAppState } from '../../../../cf-app-state'; import { cfEntityFactory } from '../../../../cf-entity-factory'; import { organizationEntityType, spaceEntityType } from '../../../../cf-entity-types'; +import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; import { CfUserService } from '../../../../shared/data-services/cf-user.service'; import { createDefaultOrgRoles, createDefaultSpaceRoles } from '../../../../store/reducers/users-roles.reducer'; import { CfUser, IUserPermissionInOrg, UserRoleInOrg, UserRoleInSpace } from '../../../../store/types/user.types'; import { CfRoleChange, CfUserRolesSelected } from '../../../../store/types/users-roles.types'; import { canUpdateOrgSpaceRoles } from '../../cf.helpers'; -import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog.service'; -import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; -import { UsersRolesSetChanges } from '../../../../actions/users-roles.actions'; @Injectable() export class CfRolesService { @@ -132,6 +132,7 @@ export class CfRolesService { const userGuids = selectedUsers.map(user => user.guid); return this.cfUserService.getUsers(cfGuid).pipe( + filter(users => !!users), map(users => { const roles = {}; // For each user (excluding those that are not selected).... diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts index 9d3085014e..f6a004f6b6 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-orgs/cf-org-card/cf-org-card.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { map, publishReplay, refCount, switchMap, tap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { organizationEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; @@ -21,10 +21,10 @@ import { MetaCardMenuItem, } from '../../../../../../../../core/src/shared/components/list/list-cards/meta-card/meta-card-base/meta-card.component'; import { CardCell } from '../../../../../../../../core/src/shared/components/list/list.types'; -import { EntityMonitorFactory } from '../../../../../../../../store/src/monitors/entity-monitor.factory.service'; -import { PaginationMonitorFactory } from '../../../../../../../../store/src/monitors/pagination-monitor.factory'; import { ComponentEntityMonitorConfig, StratosStatus } from '../../../../../../../../core/src/shared/shared.types'; import { RouterNav } from '../../../../../../../../store/src/actions/router.actions'; +import { EntityMonitorFactory } from '../../../../../../../../store/src/monitors/entity-monitor.factory.service'; +import { PaginationMonitorFactory } from '../../../../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { EndpointUser } from '../../../../../../../../store/src/types/endpoint.types'; import { IFavoriteMetadata, UserFavorite } from '../../../../../../../../store/src/types/user-favorites.types'; @@ -97,6 +97,8 @@ export class CfOrgCardComponent extends CardCell> imp return this.cfUserService.getUserRoleInOrg(u.guid, this.row.metadata.guid, this.row.entity.cfGuid); }), map(u => getOrgRolesString(u)), + publishReplay(1), + refCount() ); this.favorite = getFavoriteFromCfEntity(this.row, organizationEntityType, this.favoritesConfigMapper); diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts index ccdb1e7679..0bfe1d134b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Observable, of as observableOf } from 'rxjs'; -import { filter, first, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; +import { combineLatest, Observable, of as observableOf } from 'rxjs'; +import { filter, first, map, publishReplay, refCount, startWith, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../cloud-foundry/src/cf-app-state'; import { cfUserEntityType, organizationEntityType, spaceEntityType } from '../../../../cloud-foundry/src/cf-entity-types'; @@ -21,13 +21,14 @@ import { entityCatalog } from '../../../../store/src/entity-catalog/entity-catal import { EntityServiceFactory } from '../../../../store/src/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../store/src/monitors/pagination-monitor.factory'; import { + getCurrentPageRequestInfo, getPaginationObservables, PaginationObservables, } from '../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; import { APIResource } from '../../../../store/src/types/api.types'; import { PaginatedAction } from '../../../../store/src/types/pagination.types'; -import { CF_ENDPOINT_TYPE } from '../../cf-types'; import { cfEntityFactory } from '../../cf-entity-factory'; +import { CF_ENDPOINT_TYPE } from '../../cf-types'; import { ActiveRouteCfOrgSpace } from '../../features/cloud-foundry/cf-page.types'; import { fetchTotalResults, @@ -59,18 +60,35 @@ export class CfUserService { getUsers = (endpointGuid: string, filterEmpty = true): Observable[]> => this.getAllUsers(endpointGuid).pipe( - switchMap(paginationObservables => paginationObservables.entities$), + switchMap(paginationObservables => combineLatest( + // Entities should be subbed to so the api request is made + paginationObservables.entities$.pipe( + // In the event of maxed lists though entities never fires... so start with something + startWith([]), + ), + paginationObservables.pagination$ + )), publishReplay(1), refCount(), - filter(p => { - return filterEmpty ? !!p : true; - }), - map(users => { - return filterEmpty ? users.filter(p => p.entity.cfGuid === endpointGuid) : null; - }), - filter(p => { - return filterEmpty ? p.length > 0 : true; + filter(([users, pagination]) => { + // Filter until we have a result + const currentPage = getCurrentPageRequestInfo(pagination, null); + if (!currentPage) { + return false; + } + return !currentPage.busy && (!!users || currentPage.error || currentPage.maxed); }), + map(([users, pagination]) => { + const currentPage = getCurrentPageRequestInfo(pagination, null); + const hasFailed = currentPage.error || currentPage.maxed; + if (!currentPage || hasFailed) { + return null; + } + + // Include only the users from the required endpoint + // (Think this is now a no-op as the actions have since been fixed to return only users from a single cf but keeping for the moment) + return !!users ? users.filter(p => p.entity.cfGuid === endpointGuid) : null; + }) ) getUser = (endpointGuid: string, userGuid: string): Observable => { @@ -95,7 +113,10 @@ export class CfUserService { ); } return this.users[userGuid]; - })); + }), + publishReplay(1), + refCount() + ); } private parseOrgRole( @@ -266,7 +287,9 @@ export class CfUserService { this.store, this.paginationMonitorFactory ) : observableOf(null); - }) + }), + publishReplay(1), + refCount() ); } diff --git a/src/frontend/packages/store/src/effects/request.effects.ts b/src/frontend/packages/store/src/effects/request.effects.ts index d03cacb415..06a398ed43 100644 --- a/src/frontend/packages/store/src/effects/request.effects.ts +++ b/src/frontend/packages/store/src/effects/request.effects.ts @@ -5,7 +5,6 @@ import { catchError, first, map, mergeMap, withLatestFrom } from 'rxjs/operators import { CFAppState } from '../../../cloud-foundry/src/cf-app-state'; import { validateEntityRelations } from '../../../cloud-foundry/src/entity-relations/entity-relations'; -import { entityCatalog } from '../entity-catalog/entity-catalog.service'; import { LoggerService } from '../../../core/src/core/logger.service'; import { UtilsService } from '../../../core/src/core/utils.service'; import { ClearPaginationOfEntity, ClearPaginationOfType, SET_PAGE_BUSY } from '../actions/pagination.actions'; @@ -15,6 +14,7 @@ import { EntitiesPipelineCompleted, ValidateEntitiesStart, } from '../actions/request.actions'; +import { entityCatalog } from '../entity-catalog/entity-catalog.service'; import { completeApiRequest, getFailApiRequestActions, diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer.helper.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer.helper.ts index c5eaf28c43..700d462854 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer.helper.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer.helper.ts @@ -28,7 +28,7 @@ import { PaginationEntityState, PaginationParam, } from '../../types/pagination.types'; -import { ActionState } from '../api-request-reducer/types'; +import { ListActionState } from '../api-request-reducer/types'; export interface PaginationObservables { pagination$: Observable; @@ -298,12 +298,12 @@ export function hasError(pagination: PaginationEntityState): boolean { return pagination && getCurrentPageRequestInfo(pagination).error; } -export function getCurrentPageRequestInfo(pagination: PaginationEntityState): ActionState { - return pagination.pageRequests[pagination.currentPage] || { - busy: false, - error: false, - message: '' - }; +export function getCurrentPageRequestInfo(pagination: PaginationEntityState, valueIfMissing = { + busy: false, + error: false, + message: '' +}): ListActionState { + return pagination.pageRequests[pagination.currentPage] || valueIfMissing; } export function spreadClientPagination(pag: PaginationClientPagination): PaginationClientPagination { From 4d603129edfdd9f11ebad2b0a5f6c4bebfd32af2 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 11 Mar 2020 12:52:53 +0000 Subject: [PATCH 04/12] Fix create many services script --- deploy/tools/populate-cf/create-many-services.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/tools/populate-cf/create-many-services.sh b/deploy/tools/populate-cf/create-many-services.sh index 8183379661..fd24640340 100755 --- a/deploy/tools/populate-cf/create-many-services.sh +++ b/deploy/tools/populate-cf/create-many-services.sh @@ -1,5 +1,5 @@ #!/bin/bash -CREATE=true +CREATE="true" ORG=many-apps SPACE=many-apps SERVICE_PREFIX=many-services- @@ -26,12 +26,12 @@ echo $SERVICE_PLAN echo "Creating $COUNT service instances in org '$ORG' space '$SPACE'" -if [ "$CREATE" = true ]; then +if [ "$CREATE" = "true" ]; then cf create-org $ORG fi cf target -o $ORG -if [ "$CREATE" = true ]; then +if [ "$CREATE" = "true" ]; then cf create-space $SPACE fi cf target -s $SPACE @@ -43,4 +43,4 @@ do cf create-service "$SERVICE" "$SERVICE_PLAN" "$SERVICE_PREFIX-$counter" ((counter++)) done -echo "Created $COUNT service instances in org '$ORG' space '$SPACE'" \ No newline at end of file +echo "Created $counter service instances in org '$ORG' space '$SPACE'" \ No newline at end of file From 70885852339ae7b6723609c8d67225d5415740bd Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 11 Mar 2020 12:56:12 +0000 Subject: [PATCH 05/12] Scaling improvements - Remove org-space-route relation from orgs and org api requests - Ensure we don't fetch entity counts unless we need to --- .../src/actions/route.actions.ts | 12 +++-- .../organization.action-builders.ts | 28 +++++++++-- .../routes.action-builder.ts | 2 +- .../src/features/cloud-foundry/cf.helpers.ts | 12 ++--- .../cloud-foundry-endpoint.service.ts | 48 ++++++++++++++----- .../cloud-foundry-organization.service.ts | 30 ++++++++---- ...oundry-organization-summary.component.html | 2 +- ...-foundry-organization-summary.component.ts | 4 +- .../local-list.helpers.ts | 5 +- 9 files changed, 102 insertions(+), 41 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/route.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/route.actions.ts index e9994a8756..55d6e4e30a 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/route.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/route.actions.ts @@ -1,5 +1,7 @@ +import { HttpParams, HttpRequest } from '@angular/common/http'; + import { getActions } from '../../../store/src/actions/action.helper'; -import { PaginatedAction } from '../../../store/src/types/pagination.types'; +import { PaginatedAction, PaginationParam } from '../../../store/src/types/pagination.types'; import { ICFAction } from '../../../store/src/types/request.types'; import { cfEntityFactory } from '../cf-entity-factory'; import { applicationEntityType, domainEntityType, routeEntityType, spaceEntityType } from '../cf-entity-types'; @@ -9,7 +11,6 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; -import { HttpRequest, HttpParams } from '@angular/common/http'; export const CREATE_ROUTE = '[Route] Create start'; export const CREATE_ROUTE_SUCCESS = '[Route] Create success'; @@ -123,10 +124,11 @@ export class UnmapRoute extends BaseRouteAction { } export class GetAllRoutes extends CFStartAction implements PaginatedAction, EntityInlineParentAction, ICFAction { - paginationKey: string; endpointType = 'cf'; + paginationKey: string; constructor( public endpointGuid: string, + pKey?: string, public includeRelations = [ createEntityRelationKey(routeEntityType, applicationEntityType), createEntityRelationKey(routeEntityType, domainEntityType), @@ -139,13 +141,13 @@ export class GetAllRoutes extends CFStartAction implements PaginatedAction, Enti 'GET', 'routes' ); - this.paginationKey = createEntityRelationPaginationKey('cf', this.endpointGuid); + this.paginationKey = pKey || createEntityRelationPaginationKey('cf', this.endpointGuid); } entity = [cfEntityFactory(routeEntityType)]; entityType = routeEntityType; options: HttpRequest; actions = getActions('Routes', 'Fetch all'); - initialParams = { + initialParams: PaginationParam = { 'results-per-page': 100, page: 1, 'order-direction': 'desc', diff --git a/src/frontend/packages/cloud-foundry/src/entity-action-builders/organization.action-builders.ts b/src/frontend/packages/cloud-foundry/src/entity-action-builders/organization.action-builders.ts index 832abc388e..ed7b3c364f 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-action-builders/organization.action-builders.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-action-builders/organization.action-builders.ts @@ -1,9 +1,29 @@ -import { OrchestratedActionBuilders } from '../../../store/src/entity-catalog/action-orchestrator/action-orchestrator'; -import { GetAllOrganizations, DeleteOrganization, UpdateOrganization, GetOrganization } from '../actions/organization.actions'; import { IUpdateOrganization } from '../../../core/src/core/cf-api.types'; +import { + DeleteOrganization, + GetAllOrganizations, + GetOrganization, + UpdateOrganization, +} from '../actions/organization.actions'; import { CFBasePipelineRequestActionMeta } from '../cf-entity-generator'; +import { CFOrchestratedActionBuilders } from './cf.action-builder.types'; -export const organizationActionBuilders = { +export interface OrganizationActionBuilders extends CFOrchestratedActionBuilders { + get: ( + guid: string, + endpointGuid: string, + { includeRelations, populateMissing }?: CFBasePipelineRequestActionMeta + ) => GetOrganization; + getMultiple: ( + endpointGuid: string, + paginationKey: string, + { includeRelations, populateMissing }?: CFBasePipelineRequestActionMeta + ) => GetAllOrganizations; + remove: (guid: string, endpointGuid: string) => DeleteOrganization; + update: (guid: string, endpointGuid: string, updatedOrg: IUpdateOrganization) => UpdateOrganization; +} + +export const organizationActionBuilders: OrganizationActionBuilders = { get: ( guid, endpointGuid, @@ -20,4 +40,4 @@ export const organizationActionBuilders = { endpointGuid, updatedOrg ) -} as OrchestratedActionBuilders; +}; diff --git a/src/frontend/packages/cloud-foundry/src/entity-action-builders/routes.action-builder.ts b/src/frontend/packages/cloud-foundry/src/entity-action-builders/routes.action-builder.ts index 4b84fc4357..f1baec44f1 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-action-builders/routes.action-builder.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-action-builders/routes.action-builder.ts @@ -29,7 +29,7 @@ export const routesActionBuilders = { endpointGuid, paginationKey: string, { includeRelations, populateMissing }: CFBasePipelineRequestActionMeta = {} - ) => new GetAllRoutes(endpointGuid, includeRelations, populateMissing), + ) => new GetAllRoutes(endpointGuid, paginationKey, includeRelations, populateMissing), unmap: ( guid: string, appGuid: string, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts index b35efb9e2b..f8ef356c98 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cf.helpers.ts @@ -343,15 +343,15 @@ export function fetchTotalResults( cfEntityFactory(newAction.entityType) ) }); - // Ensure the request is made by sub'ing to the entities observable - pagObs.entities$.pipe( - first(), - ).subscribe(); - return pagObs.pagination$.pipe( + 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(pag => pag.totalResults) + map(pagination => pagination.totalResults) ); } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts index 5f932f9f0e..bee6de3e6f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts @@ -12,7 +12,6 @@ import { organizationEntityType, privateDomainsEntityType, quotaDefinitionEntityType, - routeEntityType, spaceEntityType, } from '../../../../../cloud-foundry/src/cf-entity-types'; import { @@ -22,15 +21,15 @@ import { import { CfApplicationState } from '../../../../../cloud-foundry/src/store/types/application.types'; import { IApp, ICfV2Info, IOrganization, ISpace } from '../../../../../core/src/core/cf-api.types'; import { EndpointsService } from '../../../../../core/src/core/endpoints.service'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog.service'; -import { EntityService } from '../../../../../store/src/entity-service'; -import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { MetricQueryType } from '../../../../../core/src/shared/services/metrics-range-selector.types'; import { GetAllEndpoints } from '../../../../../store/src/actions/endpoint.actions'; import { MetricQueryConfig } from '../../../../../store/src/actions/metrics.actions'; +import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog.service'; +import { IEntityMetadata } from '../../../../../store/src/entity-catalog/entity-catalog.types'; +import { EntityService } from '../../../../../store/src/entity-service'; +import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; import { endpointSchemaKey } from '../../../../../store/src/helpers/entity-factory'; -import { QParam, QParamJoiners } from '../../../shared/q-param'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { getPaginationObservables, PaginationObservables, @@ -39,11 +38,15 @@ import { APIResource, EntityInfo } from '../../../../../store/src/types/api.type import { IMetrics } from '../../../../../store/src/types/base-metric.types'; import { EndpointModel, EndpointUser } from '../../../../../store/src/types/endpoint.types'; import { PaginatedAction } from '../../../../../store/src/types/pagination.types'; -import { CF_ENDPOINT_TYPE } from '../../../cf-types'; import { FetchCFCellMetricsPaginatedAction } from '../../../actions/cf-metrics.actions'; +import { GetAllRoutes } from '../../../actions/route.actions'; +import { GetSpaceRoutes } from '../../../actions/space.actions'; import { cfEntityFactory } from '../../../cf-entity-factory'; +import { CF_ENDPOINT_TYPE } from '../../../cf-types'; import { CfInfoDefinitionActionBuilders } from '../../../entity-action-builders/cf-info.action-builders'; +import { OrganizationActionBuilders } from '../../../entity-action-builders/organization.action-builders'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; +import { QParam, QParamJoiners } from '../../../shared/q-param'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { fetchTotalResults } from '../cf.helpers'; @@ -85,7 +88,8 @@ export class CloudFoundryEndpointService { const paginationKey = cfGuid ? createEntityRelationPaginationKey(endpointSchemaKey, cfGuid) : createEntityRelationPaginationKey(endpointSchemaKey); - const organizationEntity = entityCatalog.getEntity(CF_ENDPOINT_TYPE, organizationEntityType); + const organizationEntity = entityCatalog + .getEntity(CF_ENDPOINT_TYPE, organizationEntityType); const actionBuilder = organizationEntity.actionOrchestrator.getActionBuilder('getMultiple'); const getAllOrganizationsAction = actionBuilder(cfGuid, paginationKey, { @@ -94,10 +98,8 @@ export class CloudFoundryEndpointService { createEntityRelationKey(organizationEntityType, domainEntityType), createEntityRelationKey(organizationEntityType, quotaDefinitionEntityType), createEntityRelationKey(organizationEntityType, privateDomainsEntityType), - createEntityRelationKey(spaceEntityType, routeEntityType), // Not really needed at top level, but if we drop down into an org with - // lots of spaces it saves spaces x routes requests ], populateMissing: false - }) as PaginatedAction; + }); return getAllOrganizationsAction; } static createGetAllOrganizationsLimitedSchema(cfGuid: string) { @@ -131,6 +133,30 @@ export class CloudFoundryEndpointService { return fetchTotalResults(action, store, pmf); } + public static fetchRouteCount( + store: Store, + pmf: PaginationMonitorFactory, + cfGuid: string, + orgGuid?: string, + spaceGuid?: string) + : Observable { + if (spaceGuid) { + const spaceAction = + new GetSpaceRoutes(spaceGuid, cfGuid, createEntityRelationPaginationKey(spaceEntityType, spaceGuid), [], false, false); + return fetchTotalResults(spaceAction, store, pmf); + } + + const parentSchemaKey = orgGuid ? organizationEntityType : 'cf'; + const uniqueKey = orgGuid || cfGuid; + const action = new GetAllRoutes(cfGuid, createEntityRelationPaginationKey(parentSchemaKey, uniqueKey), [], false); + action.initialParams = {}; + action.initialParams.q = []; + if (orgGuid) { + action.initialParams.q.push(new QParam('organization_guid', orgGuid, QParamJoiners.in).toString()); + } + return fetchTotalResults(action, store, pmf); + } + constructor( public activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, private store: Store, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts index 1eddb01acf..bb86f588ac 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts @@ -10,10 +10,8 @@ import { organizationEntityType, privateDomainsEntityType, quotaDefinitionEntityType, - routeEntityType, spaceEntityType, } from '../../../../../cloud-foundry/src/cf-entity-types'; -import { fetchServiceInstancesCount } from '../../../../../cloud-foundry/src/features/service-catalog/services-helper'; import { OrgUserRoleNames } from '../../../../../cloud-foundry/src/store/types/user.types'; import { IApp, @@ -24,19 +22,22 @@ import { ISpaceQuotaDefinition, } from '../../../../../core/src/core/cf-api.types'; import { getEntityFlattenedList, getStartedAppInstanceCount } from '../../../../../core/src/core/cf.helpers'; -import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; -import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { CloudFoundryUserProvidedServicesService, } from '../../../../../core/src/shared/services/cloud-foundry-user-provided-services.service'; +import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog.service'; +import { IEntityMetadata } from '../../../../../store/src/entity-catalog/entity-catalog.types'; +import { EntityServiceFactory } from '../../../../../store/src/entity-service-factory.service'; +import { PaginationMonitorFactory } from '../../../../../store/src/monitors/pagination-monitor.factory'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../cf-types'; +import { OrganizationActionBuilders } from '../../../entity-action-builders/organization.action-builders'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; +import { fetchServiceInstancesCount } from '../../service-catalog/services-helper'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getOrgRolesString } from '../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; -import { CF_ENDPOINT_TYPE } from '../../../cf-types'; -import { entityCatalog } from '../../../../../store/src/entity-catalog/entity-catalog.service'; export const createOrgQuotaDefinition = (): IOrgQuotaDefinition => ({ memory_limit: -1, @@ -76,6 +77,7 @@ export class CloudFoundryOrganizationService { routes$: Observable[]>; serviceInstancesCount$: Observable; userProvidedServiceInstancesCount$: Observable; + routesCount$: Observable; spaces$: Observable[]>; appInstances$: Observable; apps$: Observable[]>; @@ -118,7 +120,6 @@ export class CloudFoundryOrganizationService { createEntityRelationKey(organizationEntityType, domainEntityType), createEntityRelationKey(organizationEntityType, quotaDefinitionEntityType), createEntityRelationKey(organizationEntityType, privateDomainsEntityType), - createEntityRelationKey(spaceEntityType, routeEntityType), ]; if (!isAdmin) { // We're only interested in fetching org roles via the org request for non-admins. @@ -131,9 +132,12 @@ export class CloudFoundryOrganizationService { createEntityRelationKey(organizationEntityType, OrgUserRoleNames.AUDITOR), ); } - const orgEntity = entityCatalog.getEntity(CF_ENDPOINT_TYPE, organizationEntityType); + const orgEntity = entityCatalog + .getEntity(CF_ENDPOINT_TYPE, organizationEntityType); const getOrgActionBuilder = orgEntity.actionOrchestrator.getActionBuilder('get'); - const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid, relations); + // Fixing getOrgActionBuilder args will break other things. For fix see https://github.com/cloudfoundry/stratos/pull/4127 + // const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid, { includeRelations: relations }); + const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid); const orgEntityService = this.entityServiceFactory.create>( this.orgGuid, getOrgAction @@ -158,6 +162,13 @@ export class CloudFoundryOrganizationService { this.serviceInstancesCount$ = fetchServiceInstancesCount(this.cfGuid, this.orgGuid, null, this.store, this.paginationMonitorFactory); this.userProvidedServiceInstancesCount$ = this.cfUserProvidedServicesService.fetchUserProvidedServiceInstancesCount(this.cfGuid, this.orgGuid); + + this.routesCount$ = CloudFoundryEndpointService.fetchRouteCount( + this.store, + this.paginationMonitorFactory, + this.activeRouteCfOrgSpace.cfGuid, + this.activeRouteCfOrgSpace.orgGuid + ); } private initialiseSpaceObservables() { @@ -199,6 +210,7 @@ export class CloudFoundryOrganizationService { ); } + private initialiseOrgObservables() { this.spaces$ = this.org$.pipe(map(o => o.entity.entity.spaces), filter(o => !!o)); this.privateDomains$ = this.org$.pipe(map(o => o.entity.entity.private_domains)); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html index c1db16d5cf..aca6cf5016 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.html @@ -42,7 +42,7 @@ diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts index f21fce988c..d9f8b8ce43 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-summary/cloud-foundry-organization-summary.component.ts @@ -6,13 +6,13 @@ import { filter, first, map, pairwise, startWith, tap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; -import { entityCatalog } from '../../../../../../../store/src//entity-catalog/entity-catalog.service'; import { ConfirmationDialogConfig } from '../../../../../../../core/src/shared/components/confirmation-dialog.config'; import { ConfirmationDialogService } from '../../../../../../../core/src/shared/components/confirmation-dialog.service'; +import { entityCatalog } from '../../../../../../../store/src//entity-catalog/entity-catalog.service'; import { RouterNav } from '../../../../../../../store/src/actions/router.actions'; import { selectDeletionInfo } from '../../../../../../../store/src/selectors/api.selectors'; -import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { organizationEntityType } from '../../../../../cf-entity-types'; +import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { goToAppWall } from '../../../cf.helpers'; import { CloudFoundryEndpointService } from '../../../services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from '../../../services/cloud-foundry-organization.service'; diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list.helpers.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list.helpers.ts index c3dcd5d8df..98920db598 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list.helpers.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/local-list.helpers.ts @@ -1,5 +1,5 @@ -import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog.service'; +import { PaginationEntityState } from '../../../../../../store/src/types/pagination.types'; export class LocalPaginationHelpers { @@ -8,7 +8,8 @@ export class LocalPaginationHelpers { */ static isPaginationMaxed(pagination: PaginationEntityState) { if (pagination.forcedLocalPage) { - return !!pagination.pageRequests[pagination.forcedLocalPage].maxed; + const forcedPage = pagination.pageRequests[pagination.forcedLocalPage]; + return !!forcedPage.maxed; } return !!Object.values(pagination.pageRequests).find(request => request.maxed); } From 1ca5dd4ed42d466db3075c3a224637269b5fd60b Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 11 Mar 2020 13:08:08 +0000 Subject: [PATCH 06/12] Fix handling of generic errors to cf endpoints - sometimes errorResponse can be null (for instance timeout) --- .../packages/cloud-foundry/src/cf-error-helpers.ts | 7 ++++++- .../endpoint-errors.handler.ts | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/cf-error-helpers.ts b/src/frontend/packages/cloud-foundry/src/cf-error-helpers.ts index 6d2488966c..73c4b0c0d4 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-error-helpers.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-error-helpers.ts @@ -12,7 +12,12 @@ export interface CfErrorObject { export type CfErrorResponse = CfErrorObject | string | any; function isCfError(errorResponse: CfErrorResponse): CfErrorObject { - return !!errorResponse.code && !!errorResponse.description && !!errorResponse.error_code ? errorResponse as CfErrorObject : null; + return !!errorResponse && + !!errorResponse.code && + !!errorResponse.description && + !!errorResponse.error_code ? + errorResponse as CfErrorObject : + null; } export function getCfError(jetStreamErrorResponse: JetStreamErrorResponse): string { diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.ts index b3e0f23999..d493105de4 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.ts @@ -1,5 +1,5 @@ -import { StratosBaseCatalogEntity } from '../../entity-catalog/entity-catalog-entity'; import { SendEventAction } from '../../actions/internal-events.actions'; +import { StratosBaseCatalogEntity } from '../../entity-catalog/entity-catalog-entity'; import { endpointSchemaKey } from '../../helpers/entity-factory'; import { ApiRequestTypes } from '../../reducers/api-request-reducer/request-helpers'; import { InternalEventSeverity, InternalEventStateMetadata } from '../../types/internal-events.types'; @@ -35,7 +35,9 @@ export const endpointErrorsHandlerFactory = (actionDispatcher: ActionDispatcher) metadata: { url: error.url, httpMethod: action.options ? action.options.method as string : '', - errorResponse: error.jetstreamErrorResponse, + errorResponse: { + errorResponse: error.jetstreamErrorResponse + }, }, }), ); From 57f1dff54c4cb49f05017e4f37938d83c0f04d0e Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 11 Mar 2020 14:31:47 +0000 Subject: [PATCH 07/12] Fix SI Delete - Delete SI success handler was incorrectly clearing pagination instead of just removing entity from pagination --- .../reducers/service-instance.reducer.ts | 29 ++++--------------- .../store/src/effects/request.effects.ts | 6 +++- .../success-entity-request.handler.ts | 9 ++++-- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/store/reducers/service-instance.reducer.ts b/src/frontend/packages/cloud-foundry/src/store/reducers/service-instance.reducer.ts index 63f250ebc8..8e9d775159 100644 --- a/src/frontend/packages/cloud-foundry/src/store/reducers/service-instance.reducer.ts +++ b/src/frontend/packages/cloud-foundry/src/store/reducers/service-instance.reducer.ts @@ -1,15 +1,15 @@ +import { IServiceBinding, IServiceInstance, IUserProvidedServiceInstance } from '../../../../core/src/core/cf-api-svc.types'; +import { IRequestEntityTypeState } from '../../../../store/src/app-state'; +import { APIResource } from '../../../../store/src/types/api.types'; +import { APISuccessOrFailedAction } from '../../../../store/src/types/request.types'; import { CREATE_SERVICE_BINDING_ACTION_SUCCESS, CreateServiceBinding, DELETE_SERVICE_BINDING_ACTION_SUCCESS, DeleteServiceBinding, } from '../../actions/service-bindings.actions'; -import { serviceBindingEntityType } from '../../cf-entity-types'; import { getCFEntityKey } from '../../cf-entity-helpers'; -import { IServiceBinding, IServiceInstance, IUserProvidedServiceInstance } from '../../../../core/src/core/cf-api-svc.types'; -import { IRequestEntityTypeState } from '../../../../store/src/app-state'; -import { APIResource } from '../../../../store/src/types/api.types'; -import { APISuccessOrFailedAction } from '../../../../store/src/types/request.types'; +import { serviceBindingEntityType } from '../../cf-entity-types'; export function serviceInstanceReducer( state: IRequestEntityTypeState>, @@ -18,25 +18,6 @@ export function serviceInstanceReducer new ClearPaginationOfType(apiAction))); + actions.push(...apiAction.clearPaginationEntityKeys.map(key => { + const entityConfig = entityCatalog.getEntity(CF_ENDPOINT_TYPE, key); + return new ClearPaginationOfType(entityConfig.getSchema()); + })); } } } diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts index c39c98e070..3483ae485e 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts @@ -1,6 +1,8 @@ -import { StratosBaseCatalogEntity } from '../../entity-catalog/entity-catalog-entity'; +import { CF_ENDPOINT_TYPE } from '../../../../cloud-foundry/src/cf-types'; import { ClearPaginationOfEntity, ClearPaginationOfType } from '../../actions/pagination.actions'; import { RecursiveDeleteComplete } from '../../effects/recursive-entity-delete.effect'; +import { StratosBaseCatalogEntity } from '../../entity-catalog/entity-catalog-entity'; +import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; import { WrapperRequestActionSuccess } from '../../types/request.types'; export function successEntityHandler( @@ -29,7 +31,10 @@ export function successEntityHandler( if (Array.isArray(action.clearPaginationEntityKeys)) { // If clearPaginationEntityKeys is an array then clear the pagination sections regardless of removeEntityOnDelete - action.clearPaginationEntityKeys.map(key => actionDispatcher(new ClearPaginationOfType(action))); + action.clearPaginationEntityKeys.forEach(key => { + const entityConfig = entityCatalog.getEntity(CF_ENDPOINT_TYPE, key); + actionDispatcher(new ClearPaginationOfType(entityConfig.getSchema())); + }); } } actionDispatcher(entityAction); From c62fa1ec79c91c4c0b08c3b80510f62f4a3083fd Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 11 Mar 2020 14:44:45 +0000 Subject: [PATCH 08/12] Fix UPSI Delete - Delete of entity was fine, however it was not removed from list - With bug entity was still not visible in list afterwards only due to nulls filtered out in list --- .../success-entity-request.handler.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts index 3483ae485e..bf0d187b9e 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/success-entity-request.handler.ts @@ -18,15 +18,12 @@ export function successEntityHandler( !action.updatingKey && (requestType === 'create' || requestType === 'delete') ) { + const proxySafeEntityConfig = action.proxyPaginationEntityConfig ? action.proxyPaginationEntityConfig : action; // FIXME: Look at using entity config instead of actions in these actions ctors #3975 if (action.removeEntityOnDelete) { - actionDispatcher(new ClearPaginationOfEntity(action, action.guid)); + actionDispatcher(new ClearPaginationOfEntity(proxySafeEntityConfig, action.guid)); } else { - if (action.proxyPaginationEntityConfig) { - actionDispatcher(new ClearPaginationOfType(action.proxyPaginationEntityConfig)); - } else { - actionDispatcher(new ClearPaginationOfType(action)); - } + actionDispatcher(new ClearPaginationOfType(proxySafeEntityConfig)); } if (Array.isArray(action.clearPaginationEntityKeys)) { From b04a775cb176873c363ba908b57d995cc10dc2fb Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 11 Mar 2020 15:46:40 +0000 Subject: [PATCH 09/12] Handle error message being in error.status OR as a string in jetstreamErrorResponse --- .../entity-request-base-handlers/endpoint-errors.handler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.ts index d493105de4..6ff1efc269 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.ts @@ -18,8 +18,8 @@ export const endpointErrorsHandlerFactory = (actionDispatcher: ActionDispatcher) // Dispatch a error action for the specific endpoint that's failed const fakedAction = { ...action, endpointGuid: error.guid }; const errorMessage = error.jetstreamErrorResponse - ? error.jetstreamErrorResponse.error.status || error.errorCode - : error.errorCode; + ? error.jetstreamErrorResponse.error.status || 'API request error' + : 'API request error'; actionDispatcher( new APISuccessOrFailedAction( entityErrorAction.type, @@ -31,7 +31,7 @@ export const endpointErrorsHandlerFactory = (actionDispatcher: ActionDispatcher) new SendEventAction(endpointSchemaKey, error.guid, { eventCode: error.errorCode, severity: InternalEventSeverity.ERROR, - message: 'API request error', + message: errorMessage, metadata: { url: error.url, httpMethod: action.options ? action.options.method as string : '', From 25ae44a5f827c076f863f011222580d6fabd9c07 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Wed, 11 Mar 2020 15:47:16 +0000 Subject: [PATCH 10/12] Block displaying of service wall and cf routes lists until cf/org/space filters have loaded --- .../cf-routes/cf-routes-list-config.service.ts | 14 ++++++++++++-- .../service-instances-wall-list-config.service.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts index d32af4f97c..f56ff40006 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config.service.ts @@ -1,8 +1,8 @@ import { DatePipe } from '@angular/common'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Observable, of as observableOf } from 'rxjs'; -import { publishReplay, refCount, switchMap } from 'rxjs/operators'; +import { combineLatest, Observable, of as observableOf } from 'rxjs'; +import { map, publishReplay, refCount, startWith, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { CurrentUserPermissions } from '../../../../../../../core/src/core/current-user-permissions.config'; @@ -30,6 +30,7 @@ export class CfRoutesListConfigService extends CfRoutesListConfigBase implements getDataSource: () => CfRoutesDataSource; getMultiFiltersConfigs: () => IListMultiFilterConfig[]; + getInitialised: () => Observable; constructor( store: Store, @@ -76,5 +77,14 @@ export class CfRoutesListConfigService extends CfRoutesListConfigBase implements this.dataSource.masterAction.entityType, this.dataSource.masterAction.paginationKey).subscribe(); cfOrgSpaceService.cf.select.next(cfService.cfGuid); + + this.getInitialised = () => combineLatest( + cfOrgSpaceService.cf.list$, + cfOrgSpaceService.org.list$, + cfOrgSpaceService.space.list$, + ).pipe( + map(loading => !loading), + startWith(true) + ); } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts index 8e5b1b2d54..592f40f654 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-list-config.service.ts @@ -1,6 +1,8 @@ import { DatePipe } from '@angular/common'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; +import { combineLatest, Observable } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { getCFEntityKey } from '../../../../../../../cloud-foundry/src/cf-entity-helpers'; @@ -49,6 +51,7 @@ export class ServiceInstancesWallListConfigService extends CfServiceInstancesLis }); viewType = ListViewTypes.BOTH; pageSizeOptions = defaultPaginationPageSizeOptionsCards; + getInitialised: () => Observable; constructor( store: Store, @@ -71,6 +74,15 @@ export class ServiceInstancesWallListConfigService extends CfServiceInstancesLis this.serviceInstanceColumns.find(column => column.columnId === 'attachedApps').cellConfig = { breadcrumbs: 'service-wall' }; + + this.getInitialised = () => combineLatest( + cfOrgSpaceService.cf.list$, + cfOrgSpaceService.org.list$, + cfOrgSpaceService.space.list$, + ).pipe( + map(loading => !loading), + startWith(true) + ); } getDataSource = () => this.dataSource; From 6845aaaf90cd3cd8f91188776d599441db77d4e7 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Thu, 12 Mar 2020 17:52:08 +0000 Subject: [PATCH 11/12] Fix failing test --- .../endpoint-errors.handler.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.spec.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.spec.ts index d896001d9b..2e39ebde17 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.spec.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/endpoint-errors.handler.spec.ts @@ -1,7 +1,7 @@ import { Action } from '@ngrx/store'; -import { StratosBaseCatalogEntity } from '../../entity-catalog/entity-catalog-entity'; import { SendEventAction } from '../../actions/internal-events.actions'; +import { StratosBaseCatalogEntity } from '../../entity-catalog/entity-catalog-entity'; import { EntitySchema } from '../../helpers/entity-schema'; import { InternalEventSeverity } from '../../types/internal-events.types'; import { APISuccessOrFailedAction, EntityRequestAction } from '../../types/request.types'; @@ -53,9 +53,9 @@ describe('endpoint-error-handler', () => { expect(eventAction instanceof SendEventAction).toBe(true); expect(eventAction.eventState.eventCode).toBe(error.errorCode); expect(eventAction.eventState.severity).toBe(InternalEventSeverity.ERROR); - expect(eventAction.eventState.message).toBe('API request error'); + expect(eventAction.eventState.message).toBe('test'); expect(eventAction.eventState.metadata.url).toBe(error.url); - expect(eventAction.eventState.metadata.errorResponse).toBe(error.jetstreamErrorResponse); + expect(eventAction.eventState.metadata.errorResponse.errorResponse).toEqual(error.jetstreamErrorResponse); done(); } }; From 7e2de04bfdaf5e98ca085c0bd74fa5022764bee5 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 13 Mar 2020 14:53:13 +0000 Subject: [PATCH 12/12] Fix cf-user getUsers --- .../packages/cloud-foundry/src/actions/users.actions.ts | 2 +- .../cloud-foundry/users/manage-users/cf-roles.service.ts | 1 - .../manage-users-confirm/manage-users-confirm.component.ts | 7 ++++--- .../src/shared/data-services/cf-user.service.ts | 5 +++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts index 0e698669f4..410ba01559 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts @@ -106,7 +106,7 @@ export class ChangeUserRole extends CFStartAction implements EntityRequestAction this.entity = cfEntityFactory(this.entityType); } - guid: string; you + guid: string; entity: EntitySchema; entityType: string; options: HttpRequest; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts index 6e84be4e57..b97b09abf0 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/cf-roles.service.ts @@ -132,7 +132,6 @@ export class CfRolesService { const userGuids = selectedUsers.map(user => user.guid); return this.cfUserService.getUsers(cfGuid).pipe( - filter(users => !!users), map(users => { const roles = {}; // For each user (excluding those that are not selected).... diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts index 3d5689592d..1adf58b90c 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/manage-users/manage-users-confirm/manage-users-confirm.component.ts @@ -4,7 +4,6 @@ import { Store } from '@ngrx/store'; import { Observable, Subject } from 'rxjs'; import { distinctUntilChanged, filter, first, map, mergeMap, withLatestFrom } from 'rxjs/operators'; -import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog.service'; import { AppMonitorComponentTypes, } from '../../../../../../../core/src/shared/components/app-action-monitor-icon/app-action-monitor-icon.component'; @@ -12,17 +11,18 @@ import { ITableCellRequestMonitorIconConfig, } from '../../../../../../../core/src/shared/components/list/list-table/table-cell-request-monitor-icon/table-cell-request-monitor-icon.component'; import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; +import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog.service'; import { selectUsersRoles, selectUsersRolesChangedRoles, } from '../../../../../../../store/src/selectors/users-roles.selector'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { UsersRolesClearUpdateState } from '../../../../../actions/users-roles.actions'; import { ChangeUserRole } from '../../../../../actions/users.actions'; import { CFAppState } from '../../../../../cf-app-state'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; import { cfUserEntityType, organizationEntityType, spaceEntityType } from '../../../../../cf-entity-types'; +import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { TableCellConfirmOrgSpaceComponent, } from '../../../../../shared/components/list/list-types/cf-confirm-roles/table-cell-confirm-org-space/table-cell-confirm-org-space.component'; @@ -35,6 +35,7 @@ import { CfRoleChangeWithNames, UserRoleLabels } from '../../../../../store/type + /* tslint:enable:max-line-length */ @Component({ @@ -155,7 +156,7 @@ export class UsersRolesConfirmComponent implements OnInit, AfterContentInit { private createChangesObs() { this.changes$ = this.updateChanges.pipe( withLatestFrom(this.cfGuid$), - mergeMap(([changed, cfGuid]) => this.cfUserService.getUsers(cfGuid)), + mergeMap(([, cfGuid]) => this.cfUserService.getUsers(cfGuid)), withLatestFrom(this.store.select(selectUsersRolesChangedRoles)), map(([users, changes]) => { return changes diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts index 27bdef04ab..6473550d99 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-user.service.ts @@ -64,7 +64,7 @@ export class CfUserService { // Entities should be subbed to so the api request is made paginationObservables.entities$.pipe( // In the event of maxed lists though entities never fires... so start with something - startWith([]), + startWith(null), ), paginationObservables.pagination$ )), @@ -88,7 +88,8 @@ export class CfUserService { // Include only the users from the required endpoint // (Think this is now a no-op as the actions have since been fixed to return only users from a single cf but keeping for the moment) return !!users ? users.filter(p => p.entity.cfGuid === endpointGuid) : null; - }) + }), + filter(users => filterEmpty ? !!users : true) ) getUser = (endpointGuid: string, userGuid: string): Observable => {