diff --git a/src/frontend/packages/cloud-foundry/src/actions/application.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/application.actions.ts index bf60cce1d9..a7574dedc8 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/application.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/application.actions.ts @@ -68,7 +68,7 @@ export class GetAllApplications extends CFStartAction implements PaginatedAction 'results-per-page': 100, }; flattenPagination = true; - flattenPaginationMax = 600; + flattenPaginationMax = true; } export class GetApplication extends CFStartAction implements ICFAction, EntityInlineParentAction { 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 b6fb92a619..ad7d9b7fa1 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/organization.actions.ts @@ -256,7 +256,7 @@ export class GetAllOrgUsers extends CFStartAction implements PaginatedAction, En 'order-direction-field': 'username', }; flattenPagination = true; - flattenPaginationMax = 600; + flattenPaginationMax = true; skipValidation: boolean; populateMissing: boolean; } 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 55d6e4e30a..28d845fb4f 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/route.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/route.actions.ts @@ -59,10 +59,10 @@ export class CreateRoute extends BaseRouteAction { ...route, port: generatePort ? undefined : route.port }, { - params: new HttpParams(generatePort ? { - fromObject: { generate_port: 'true' } - } : {}) - } + params: new HttpParams(generatePort ? { + fromObject: { generate_port: 'true' } + } : {}) + } ); } actions = [CREATE_ROUTE, CREATE_ROUTE_SUCCESS, CREATE_ROUTE_ERROR]; @@ -153,6 +153,6 @@ export class GetAllRoutes extends CFStartAction implements PaginatedAction, Enti 'order-direction': 'desc', 'order-direction-field': 'route', }; - flattenPaginationMax = 800; + flattenPaginationMax = true; flattenPagination = true; } diff --git a/src/frontend/packages/cloud-foundry/src/actions/service-instances.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/service-instances.actions.ts index 00ae672c4a..a0b111c079 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/service-instances.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/service-instances.actions.ts @@ -60,6 +60,7 @@ export class GetServiceInstances q: [] }; flattenPagination = true; + flattenPaginationMax = true; } export class GetServiceInstance diff --git a/src/frontend/packages/cloud-foundry/src/actions/service.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/service.actions.ts index d3a1297501..fe68c1700c 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/service.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/service.actions.ts @@ -1,12 +1,13 @@ -import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog.service'; +import { HttpRequest } from '@angular/common/http'; + import { getActions } from '../../../store/src/actions/action.helper'; +import { entityCatalog } from '../../../store/src/entity-catalog/entity-catalog.service'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; -import { CF_ENDPOINT_TYPE } from '../cf-types'; import { cfEntityFactory } from '../cf-entity-factory'; import { serviceEntityType, servicePlanEntityType } from '../cf-entity-types'; +import { CF_ENDPOINT_TYPE } from '../cf-types'; import { createEntityRelationKey, EntityInlineParentAction } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; -import { HttpRequest } from '@angular/common/http'; export class GetAllServices extends CFStartAction implements PaginatedAction, EntityInlineParentAction { constructor( @@ -34,6 +35,7 @@ export class GetAllServices extends CFStartAction implements PaginatedAction, En 'order-direction-field': 'label', }; flattenPagination = true; + flattenPaginationMax = true; } export class GetService extends CFStartAction implements EntityInlineParentAction { constructor( diff --git a/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts index cba172c5cb..0080cf6962 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/user-provided-service.actions.ts @@ -1,7 +1,8 @@ -import { EntityCatalogEntityConfig } from '../../../store/src/entity-catalog/entity-catalog.types'; +import { HttpRequest } from '@angular/common/http'; + import { getActions } from '../../../store/src/actions/action.helper'; +import { EntityCatalogEntityConfig } from '../../../store/src/entity-catalog/entity-catalog.types'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; -import { QParam, QParamJoiners } from '../shared/q-param'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { ICFAction } from '../../../store/src/types/request.types'; import { cfEntityFactory } from '../cf-entity-factory'; @@ -18,8 +19,8 @@ import { createEntityRelationPaginationKey, EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; +import { QParam, QParamJoiners } from '../shared/q-param'; import { CFStartAction } from './cf-action.types'; -import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), @@ -59,6 +60,7 @@ export class GetAllUserProvidedServices extends CFStartAction implements Paginat q: [] }; flattenPagination = true; + flattenPaginationMax = true; paginationKey: string; } 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 f637e38ee5..8aa7e4be31 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/users.actions.ts @@ -74,7 +74,7 @@ export class GetAllUsersAsAdmin extends CFStartAction implements PaginatedAction 'order-direction-field': 'username', }; flattenPagination = true; - flattenPaginationMax = 600; + flattenPaginationMax = true; static is(action: any): boolean { return !!action.isGetAllUsersAsAdmin; } diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts index 3cbf61fd20..8bd4634a96 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts @@ -1,4 +1,7 @@ +import { Store } from '@ngrx/store'; import * as moment from 'moment'; +import { combineLatest, of } from 'rxjs'; +import { map } from 'rxjs/operators'; import { IService, @@ -26,6 +29,7 @@ import { } from '../../core/src/core/cf-api.types'; import { urlValidationExpression } from '../../core/src/core/utils.service'; import { BaseEndpointAuth } from '../../core/src/features/endpoints/endpoint-auth'; +import { AppState } from '../../store/src/app-state'; import { StratosBaseCatalogEntity, StratosCatalogEndpointEntity, @@ -41,8 +45,10 @@ import { } from '../../store/src/entity-request-pipeline/entity-request-base-handlers/handle-multi-endpoints.pipe'; import { JetstreamResponse } from '../../store/src/entity-request-pipeline/entity-request-pipeline.types'; import { EntitySchema } from '../../store/src/helpers/entity-schema'; +import { selectSessionData } from '../../store/src/reducers/auth.reducer'; import { endpointDisconnectRemoveEntitiesReducer } from '../../store/src/reducers/endpoint-disconnect-application.reducer'; import { APIResource } from '../../store/src/types/api.types'; +import { PaginatedAction } from '../../store/src/types/pagination.types'; import { IFavoriteMetadata } from '../../store/src/types/user-favorites.types'; import { cfEntityFactory } from './cf-entity-factory'; import { addCfQParams, addCfRelationParams } from './cf-entity-relations.getters'; @@ -199,7 +205,45 @@ export function generateCFEntities(): StratosBaseCatalogEntity[] { Object.values(responseWithPages).reduce((all, response: CFResponse | CFResponse[]) => { return all + (response[0] || response).total_results; }, 0), - getPaginationParameters: (page: number) => ({ page: page + '' }) + getPaginationParameters: (page: number) => ({ page: page + '' }), + canIgnoreMaxedState: (store: Store) => { + // Does entity type support? Yes + // Does BE support ignore? + return store.select(selectSessionData()).pipe( + map(sessionData => !!sessionData.config.listAllowLoadMaxed) + ); + }, + maxedStateStartAt: (store: Store, action: PaginatedAction) => { + // Disable via the action? + // Only allowed maxed process if enabled by action. This will be removed via #4204 + if (!action.flattenPaginationMax) { + return of(null); + } + + // Maxed Count from Backend? + const beValue$ = store.select(selectSessionData()).pipe( + map(sessionData => sessionData.config.listMaxSize) + ); + + // TODO: See #4205 + // Maxed count as per user config + const userOverride$ = of(null); + // const userOverride$ = store.select(selectSessionData()).pipe( + // // Check that the user is allowed to load all, if so they can set their own max number + // map(sessionData => !!sessionData.config.listAllowLoadMaxed ? null : null) + // ); + + // Maxed count from entity type + const entityTypeDefault = 600; + + // Choose in order of priority + return combineLatest([ + beValue$, + userOverride$ + ]).pipe( + map(([beValue, userOverride]) => userOverride || beValue || entityTypeDefault) + ); + }, } }; return [ @@ -259,7 +303,9 @@ function generateCFAppEnvVarEntity(endpointDefinition: StratosEndpointExtensionD getEntitiesFromResponse: (response) => response, getTotalPages: (responses: JetstreamResponse) => Object.values(responses).length, getTotalEntities: (responses: JetstreamResponse) => 1, - getPaginationParameters: (page: number) => ({ page: '1' }) + getPaginationParameters: (page: number) => ({ page: '1' }), + canIgnoreMaxedState: () => of(false), + maxedStateStartAt: () => of(null), }, successfulRequestDataMapper: (data, endpointGuid, guid, entityType, endpointType, action) => { return { @@ -409,7 +455,9 @@ function generateCFAppStatsEntity(endpointDefinition: StratosEndpointExtensionDe getTotalEntities: (responses: JetstreamResponse) => Object.values(responses).reduce((count, response) => { return count + Object.keys(response).length; }, 0), - getPaginationParameters: (page: number) => ({ page: page + '' }) + getPaginationParameters: (page: number) => ({ page: page + '' }), + canIgnoreMaxedState: () => of(false), + maxedStateStartAt: () => of(null), }, successfulRequestDataMapper: (data, endpointGuid, guid, entityType, endpointType, action) => { if (data) { @@ -837,7 +885,9 @@ function generateFeatureFlagEntity(endpointDefinition: StratosEndpointExtensionD }, getTotalPages: (responses: JetstreamResponse) => 1, getTotalEntities: (responses: JetstreamResponse) => responses.length, - getPaginationParameters: (page: number) => ({ page: page + '' }) + getPaginationParameters: (page: number) => ({ page: page + '' }), + canIgnoreMaxedState: () => of(false), + maxedStateStartAt: () => of(null), } }; return new StratosCatalogEntity( diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-relations.getters.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-relations.getters.ts index 5e2aba4592..8dd4a54f6d 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-relations.getters.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-relations.getters.ts @@ -1,18 +1,18 @@ import { HttpParams, HttpRequest } from '@angular/common/http'; +import { InternalAppState } from '../../store/src/app-state'; import { StratosBaseCatalogEntity } from '../../store/src/entity-catalog/entity-catalog-entity'; import { entityCatalog } from '../../store/src/entity-catalog/entity-catalog.service'; import { EntityCatalogEntityConfig } from '../../store/src/entity-catalog/entity-catalog.types'; -import { InternalAppState } from '../../store/src/app-state'; import { getPaginationParams, } from '../../store/src/entity-request-pipeline/pagination-request-base-handlers/get-params.pipe'; -import { QParam } from './shared/q-param'; import { selectPaginationState } from '../../store/src/selectors/pagination.selectors'; import { isPaginatedAction, PaginatedAction, PaginationParam } from '../../store/src/types/pagination.types'; import { EntityRequestAction } from '../../store/src/types/request.types'; import { listEntityRelations } from './entity-relations/entity-relations'; import { EntityInlineParentAction, isEntityInlineParentAction } from './entity-relations/entity-relations.types'; +import { QParam } from './shared/q-param'; export function getEntityRelationsForEntityRequest(action: EntityInlineParentAction & EntityRequestAction) { return listEntityRelations( @@ -101,8 +101,12 @@ export function addCfQParams( // Add initial params from action const newParamsFromAction = setQParams(newParams, action.initialParams); + // If __forcedPageEntityConfig__ has been set we're in a multi action list scenario and params should come from the base action. In this + // instance it's the entity details from the action, not the catalogueEntity that's passed in (type of entity that we're requesting) + const entityKey = action.__forcedPageEntityConfig__ ? entityCatalog.getEntityKey(action) : catalogEntity.entityKey; + // Overwrite initial params with params from store - const paginationState = selectPaginationState(catalogEntity.entityKey, action.paginationKey)(appState); + const paginationState = selectPaginationState(entityKey, action.paginationKey)(appState); const paginationParams = getPaginationParams(paginationState); const paramsFromPagination = setQParams(newParamsFromAction, paginationParams); diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html index b3df129c51..67660b76fd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application-wall/application-wall.component.html @@ -22,12 +22,6 @@

Applications

}"> - - - \ No newline at end of file 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 94935fae87..96d7f27b3f 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 @@ -363,7 +363,7 @@ export function fetchTotalResults( export const cfOrgSpaceFilter = (entities: APIResource[], paginationState: PaginationEntityState) => { // Filtering is done remotely when maxedResults are hit (see `setMultiFilter`) - if (!!paginationState.maxedMode) { + if (!!paginationState.maxedState.isMaxedMode && !paginationState.maxedState.ignoreMaxed) { return entities; } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html index dff297884a..88362c3ef6 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-users/cloud-foundry-space-users.component.html @@ -18,11 +18,6 @@ }"> - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.html index 2ee68212b3..8431b461f1 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-users/cloud-foundry-organization-users.component.html @@ -18,13 +18,6 @@ }"> - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.html index b56a13b627..dd2b6cb351 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-routes/cloud-foundry-routes.component.html @@ -1,6 +1 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.html index 960139d371..179ad01b1f 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-users/cloud-foundry-users.component.html @@ -10,11 +10,4 @@ }"> - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-app-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-app-config.service.ts index 0382e5db2c..bc314a9aa7 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-app-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-app-config.service.ts @@ -11,7 +11,7 @@ import { UtilsService } from '../../../../../../../core/src/core/utils.service'; import { createTableColumnFavorite, } from '../../../../../../../core/src/shared/components/list/list-table/table-cell-favorite/table-cell-favorite.component'; -import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; +import { ITableColumn, ITableText } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { IListConfig, IListMultiFilterConfig, @@ -136,10 +136,16 @@ export class CfAppConfigService extends ListConfig implements IList }), ]; viewType = ListViewTypes.BOTH; - text = { + text: ITableText = { title: '', filter: 'Search by name', - noEntries: 'There are no applications' + noEntries: 'There are no applications', + maxedResults: { + icon: 'apps', + canIgnoreMaxFirstLine: 'Fetching all applications might take a long time', + cannotIgnoreMaxFirstLine: 'There are too many applications to fetch', + filterLine: 'Please use the Cloud Foundry, Organization or Space filters' + } }; enableTextFilter = true; cardComponent = CardAppComponent; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts index 647bf4a809..f20011ad2b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app/cf-apps-data-source.ts @@ -33,14 +33,6 @@ import { cfOrgSpaceFilter, getRowMetadata } from '../../../../../features/cloud- import { CFListDataSource } from '../../../../cf-list-data-source'; import { createCfOrSpaceMultipleFilterFn } from '../../../../data-services/cf-org-space-service.service'; -// export function createGetAllAppAction(paginationKey): GetAllApplications { -// return new GetAllApplications(paginationKey, null, [ -// createEntityRelationKey(applicationEntityType, spaceEntityType), -// createEntityRelationKey(spaceEntityType, organizationEntityType), -// createEntityRelationKey(applicationEntityType, routeEntityType), -// ]); -// } - export class CfAppsDataSource extends CFListDataSource { public static paginationKey = 'applicationWall'; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts index 3a3eae92d3..7d53361cbb 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-org-users/cf-org-users-list-config.service.ts @@ -32,5 +32,6 @@ export class CfOrgUsersListConfigService extends CfUserListConfigService { (user: CfUser): boolean => cfUserService.hasRolesInOrg(user, activeRouteCfOrgSpace.orgGuid, false), cfOrgService.org$ ); + this.text.maxedResults.filterLine = 'Please navigate to a Space Users list'; } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts index ffd6c07637..ba099a47b8 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-routes/cf-routes-list-config-base.ts @@ -3,10 +3,8 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { routeEntityType } from '../../../../../../../cloud-foundry/src/cf-entity-types'; -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 { ITableColumn, ITableText } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; @@ -18,7 +16,9 @@ import { IMultiListAction, ListViewTypes, } from '../../../../../../../core/src/shared/components/list/list.component.types'; +import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog.service'; import { APIResource } from '../../../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { TableCellRouteAppsAttachedComponent, } from '../cf-routes/table-cell-route-apps-attached/table-cell-route-apps-attached.component'; @@ -79,6 +79,12 @@ export abstract class CfRoutesListConfigBase implements IListConfig title: null, noEntries: 'There are no routes', filter: 'Search by Route', + maxedResults: { + icon: 'network_route', + iconFont: 'stratos-icons', + canIgnoreMaxFirstLine: 'Fetching all routes might take a long time', + cannotIgnoreMaxFirstLine: 'There are too many routes to fetch' + } }; enableTextFilter = true; 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 f56ff40006..813c1d3e3f 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 @@ -54,6 +54,8 @@ export class CfRoutesListConfigService extends CfRoutesListConfigBase implements super(store, confirmDialog, cfService.cfGuid, datePipe, true, true, canEditRoute, observableOf(false)); this.setupList(store, cfService, cfOrgSpaceService); + + this.text.maxedResults.filterLine = 'Please use the Organization filter'; } private setupList( @@ -70,7 +72,6 @@ export class CfRoutesListConfigService extends CfRoutesListConfigBase implements // Show drop down filters for org and space const multiFilterConfigs = [ createCfOrgSpaceFilterConfig('org', 'Organization', cfOrgSpaceService.org), - createCfOrgSpaceFilterConfig('space', 'Space', cfOrgSpaceService.space), ]; this.getMultiFiltersConfigs = () => multiFilterConfigs; initCfOrgSpaceService(store, cfOrgSpaceService, diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts index c3f1822fb7..8ae65a4138 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts @@ -11,7 +11,7 @@ import { CurrentUserPermissionsService } from '../../../../../../../core/src/cor import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; -import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; +import { ITableColumn, ITableText } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { TableCellServiceLastOpComponent, } from '../../../../../../../core/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service-last-op/table-cell-service-last-op.component'; @@ -49,7 +49,7 @@ export class CfServiceInstancesListConfigBase implements IListConfig; defaultView = 'table' as ListView; - text = { + text: ITableText = { title: null, filter: null, noEntries: 'There are no service instances' diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts index 4be15adbb2..b8c4e53ff4 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-services-list-config.service.ts @@ -2,10 +2,10 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { endpointsCfEntitiesConnectedSelector } from 'frontend/packages/store/src/selectors/endpoint.selectors'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; -import { first, map } from 'rxjs/operators'; +import { filter, first, map } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; -import { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; +import { ITableColumn, ITableText } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { IListConfig, IListMultiFilterConfig, @@ -70,10 +70,15 @@ export class CfServicesListConfigService implements IListConfig { cardComponent = CfServiceCardComponent; defaultView = 'cards' as ListView; multiFilterConfigs: IListMultiFilterConfig[] = []; - text = { + text: ITableText = { title: null, filter: 'Search by name', - noEntries: 'There are no services' + noEntries: 'There are no services', + maxedResults: { + icon: 'store', + canIgnoreMaxFirstLine: 'Fetching all services might take a long time', + cannotIgnoreMaxFirstLine: 'There are too many services to fetch', + } }; columns: ITableColumn[] = [{ diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.ts index cfd880600c..fc9a0200fd 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-space-users/cf-space-users-list-config.service.ts @@ -33,5 +33,6 @@ export class CfSpaceUsersListConfigService extends CfUserListConfigService { cfOrgService.org$, cfSpaceService.space$, ); + this.text.maxedResults.filterLine = ''; } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts index dae824ae11..4eab25e436 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-users/cf-user-list-config.service.ts @@ -10,7 +10,7 @@ import { CfUser } from '../../../../../../../cloud-foundry/src/store/types/user. 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 { ITableColumn } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; +import { ITableColumn, ITableText } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { IListAction, IListMultiFilterConfig, @@ -89,10 +89,16 @@ export class CfUserListConfigService extends ListConfig> { }, ]; enableTextFilter = true; - text = { + text: ITableText = { title: null, filter: 'Search by username', - noEntries: 'There are no users' + noEntries: 'There are no users', + maxedResults: { + icon: 'people', + canIgnoreMaxFirstLine: 'Fetching all users might take a long time', + cannotIgnoreMaxFirstLine: 'There are too many users to fetch', + filterLine: 'Please navigate to an Organization or Space users list' + } }; private initialised: Observable; private multiFilterConfigs: IListMultiFilterConfig[]; diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-data-source.ts index 1e071f3561..57ffd72bc7 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instances-wall-data-source.ts @@ -6,22 +6,25 @@ import { serviceInstancesEntityType } from '../../../../../../../cloud-foundry/s import { createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { - ListDataSource, -} from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; import { ActionSchemaConfig, MultiActionConfig, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-config'; +import { + ListPaginationMultiFilterChange, +} from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source-types'; import { IListConfig } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { entityCatalog } from '../../../../../../../store/src/entity-catalog/entity-catalog.service'; import { IEntityMetadata } from '../../../../../../../store/src/entity-catalog/entity-catalog.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; +import { PaginatedAction, PaginationParam } from '../../../../../../../store/src/types/pagination.types'; import { CF_ENDPOINT_TYPE } from '../../../../../cf-types'; import { ServiceInstanceActionBuilders } from '../../../../../entity-action-builders/service-instance.action.builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; +import { CFListDataSource } from '../../../../cf-list-data-source'; +import { createCfOrSpaceMultipleFilterFn } from '../../../../data-services/cf-org-space-service.service'; -export class ServiceInstancesWallDataSource extends ListDataSource { +export class ServiceInstancesWallDataSource extends CFListDataSource { constructor(store: Store, transformEntities: any[], listConfig?: IListConfig) { const paginationKey = createEntityRelationPaginationKey(serviceInstancesEntityType); const serviceInstanceEntity = entityCatalog @@ -51,5 +54,16 @@ export class ServiceInstancesWallDataSource extends ListDataSource transformEntities, listConfig }); + + this.setMultiFilter = (changes: ListPaginationMultiFilterChange[], params: PaginationParam) => { + // Org and Space params are set in the pagination object + // Cf Guid is applied directly to the action that, by reference, is dispatched when we fetch the list (nasty) + // For multi action lists like this one patch each action with the correct cf guid. + const preResetUpdate = () => { + const paginationActions = this.action as PaginatedAction[]; + paginationActions.forEach(action => action.endpointGuid = this.masterAction.endpointGuid); + }; + return createCfOrSpaceMultipleFilterFn(store, this.masterAction, this.setQParam, preResetUpdate)(changes, params); + }; } } 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 592f40f654..d03eb82ab7 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 @@ -14,6 +14,7 @@ import { CurrentUserPermissionsService } from '../../../../../../../core/src/cor import { CardMultiActionComponents, } from '../../../../../../../core/src/shared/components/list/list-cards/card.component.types'; +import { ITableText } from '../../../../../../../core/src/shared/components/list/list-table/table.types'; import { defaultPaginationPageSizeOptionsCards, ListViewTypes, @@ -38,10 +39,17 @@ import { @Injectable() export class ServiceInstancesWallListConfigService extends CfServiceInstancesListConfigBase { endpointType = 'cf'; - text = { + text: ITableText = { title: null, filter: 'Search by name', - noEntries: 'There are no service instances' + noEntries: 'There are no service instances', + maxedResults: { + icon: 'service', + iconFont: 'stratos-icons', + canIgnoreMaxFirstLine: 'Fetching all service instances might take a long time', + cannotIgnoreMaxFirstLine: 'There are too many service instances to fetch', + filterLine: 'Please use the Cloud Foundry, Organization or Space filters' + } }; enableTextFilter = true; defaultView = 'cards' as ListView; diff --git a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-org-space-service.service.ts b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-org-space-service.service.ts index e116190014..b24ccb35a1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-org-space-service.service.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/data-services/cf-org-space-service.service.ts @@ -106,7 +106,8 @@ export const initCfOrgSpaceService = ( export const createCfOrSpaceMultipleFilterFn = ( store: Store, action: PaginatedAction, - setQParam: (setQ: QParam, qs: QParam[]) => boolean + setQParam: (setQ: QParam, qs: QParam[]) => boolean, + preResetUpdate?: () => void ) => { return (changes: ListPaginationMultiFilterChange[], params: PaginationParam) => { if (!changes.length) { @@ -140,6 +141,10 @@ export const createCfOrSpaceMultipleFilterFn = ( const orgChanged = startingOrgGuid !== valueOrCommonFalsy(qChanges.find((q: QParam) => q.key === 'organization_guid'), {}).value; const spaceChanged = startingSpaceGuid !== valueOrCommonFalsy(qChanges.find((q: QParam) => q.key === 'space_guid'), {}).value; + if (preResetUpdate) { + preResetUpdate(); + } + // Changes of org or space will reset pagination and start a new request. Changes of only cf require a punt if (cfGuidChanged && !orgChanged && !spaceChanged) { store.dispatch(new ResetPagination(action, action.paginationKey)); 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 40a131199c..706d2dfa56 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,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { combineLatest, Observable, of as observableOf, ReplaySubject } from 'rxjs'; +import { combineLatest, Observable, of as observableOf, of, ReplaySubject } from 'rxjs'; import { filter, first, map, multicast, publishReplay, refCount, startWith, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../cloud-foundry/src/cf-app-state'; @@ -17,6 +17,9 @@ import { UserRoleInSpace, } from '../../../../cloud-foundry/src/store/types/user.types'; import { IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; +import { + LocalPaginationHelpers, +} from '../../../../core/src/shared/components/list/data-sources-controllers/local-list.helpers'; 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'; @@ -47,6 +50,7 @@ import { isSpaceManager, waitForCFPermissions, } from '../../features/cloud-foundry/cf.helpers'; +import { selectCfPaginationState } from '../../store/selectors/pagination.selectors'; @Injectable() export class CfUserService { @@ -81,11 +85,13 @@ export class CfUserService { if (!currentPage) { return false; } - return !currentPage.busy && (!!users || currentPage.error || currentPage.maxed); + const isMaxed = LocalPaginationHelpers.isPaginationMaxed(pagination); + return !currentPage.busy && (!!users || currentPage.error || isMaxed); }), map(([users, pagination]) => { const currentPage = getCurrentPageRequestInfo(pagination, null); - const hasFailed = currentPage.error || currentPage.maxed; + const isMaxed = LocalPaginationHelpers.isPaginationMaxed(pagination); + const hasFailed = currentPage.error || isMaxed; if (!currentPage || hasFailed) { return null; } @@ -362,29 +368,67 @@ export class CfUserService { } /** - * Create a paginated action that will fetch a list of users. For admins attempt to fetch all users regardless of cf/org/space level if - * there's not too many, otherwise fetch list with respect to cf/org/level + * Create a paginated action that will fetch a list of users. + * Admins - Aim to fetch all CF users regardless of cf/org/space level. If this is not possible (haven't previously fetched OR there's + * too many to fetch) fall back to either org or space users lists depending on level + * Non-admins - Show org or space users depending on level * @param orgGuid Populated if user is at org level * @param spaceGuid Populated if user is at space level */ public createPaginationAction(isAdmin: boolean, cfGuid: string, orgGuid?: string, spaceGuid?: string): Observable { if (isAdmin) { + const allCfUsersAction = this.createCfGetAllUsersAction(cfGuid); - const action = this.createCfGetUsersAction(cfGuid); if (!orgGuid) { - return observableOf(action); + return observableOf(allCfUsersAction); } - return this.fetchTotalUsers(cfGuid).pipe( + + const cfUserEntityConfig = entityCatalog.getEntity(allCfUsersAction); + + // Do we have the list already and it's not maxed (user has previously loaded all users, possibly forcing this load)? + const hasAllUsers$ = this.store.select(selectCfPaginationState(cfUserEntityConfig.type, allCfUsersAction.paginationKey)).pipe( + filter(paginationState => + !paginationState || // No pagination state... list has never loaded + (paginationState.pageRequests && + paginationState.pageRequests[paginationState.currentPage] && + !paginationState.pageRequests[paginationState.currentPage].busy) || // Have list, not loading + !paginationState.pageRequests[paginationState.currentPage] // Had list, not loading + ), + map(paginationState => paginationState ? !LocalPaginationHelpers.isPaginationMaxed(paginationState) : false), + first() + ); + // Will we max out if attempting to fetch all users? + const willBeMaxed$ = this.fetchTotalUsers(cfGuid).pipe( + first(), + switchMap(totalResults => combineLatest([ + of(totalResults), + cfUserEntityConfig.getPaginationConfig().maxedStateStartAt(this.store, allCfUsersAction) + ]) + ), + map(([totalResults, maxEntities]) => { + return maxEntities && totalResults >= maxEntities; + }) + ); + + + return hasAllUsers$.pipe( first(), - map(count => { - if (count < action.flattenPaginationMax) { - // We can safely show all users regardless of what cf/org/level list we're showing - return action; + switchMap(hasAllUsers => { + if (hasAllUsers) { + return of(false); } - // We can't fetch all users, fall back on org or space lists - return !spaceGuid ? - this.createOrgGetUsersAction(isAdmin, cfGuid, orgGuid) : - this.createSpaceGetUsersAction(isAdmin, cfGuid, spaceGuid); + return willBeMaxed$; + }), + map((cannotShowAllUsers) => { + if (cannotShowAllUsers) { + // Either show list of users from org or space, depending on level + return !spaceGuid ? + this.createOrgGetUsersAction(isAdmin, cfGuid, orgGuid) : + this.createSpaceGetUsersAction(isAdmin, cfGuid, spaceGuid); + } + // We can safely show all users regardless of what cf/org/level list we're showing + return allCfUsersAction; + }) ); } @@ -400,7 +444,7 @@ export class CfUserService { if (!orgGuid) { // Create an action to fetch all users across the entire cf if (isAdmin) { - return this.createCfGetUsersAction(cfGuid); + return this.createCfGetAllUsersAction(cfGuid); } // Non-admins at cf level should never reach here, this is a genuine issue that if we hit the extra feed back will help throw new Error('Unsupported: Cloud Foundry non-administrators cannot access all users list'); @@ -413,7 +457,7 @@ export class CfUserService { return this.createSpaceGetUsersAction(isAdmin, cfGuid, spaceGuid); } - private createCfGetUsersAction = (cfGuid: string): PaginatedAction => { + private createCfGetAllUsersAction = (cfGuid: string): PaginatedAction => { return this.userCatalogEntity.actionOrchestrator.getActionBuilder('getMultiple')(cfGuid, null); } diff --git a/src/frontend/packages/core/src/core/current-user-permissions.service.spec.ts b/src/frontend/packages/core/src/core/current-user-permissions.service.spec.ts index 13387610c3..a9b23771ca 100644 --- a/src/frontend/packages/core/src/core/current-user-permissions.service.spec.ts +++ b/src/frontend/packages/core/src/core/current-user-permissions.service.spec.ts @@ -519,6 +519,7 @@ describe('CurrentUserPermissionsService', () => { }, totalResults: 2 }, + maxedState: {} } }, cfFeatureFlag: { @@ -545,7 +546,8 @@ describe('CurrentUserPermissionsService', () => { items: {} }, totalResults: 13 - } + }, + maxedState: {} }, 'endpoint-c80420ca-204b-4879-bf69-b6b7a202ad87': { pageCount: 1, @@ -570,7 +572,8 @@ describe('CurrentUserPermissionsService', () => { items: {} }, totalResults: 13 - } + }, + maxedState: {} } }, }; diff --git a/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.html b/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.html index adf551a8a7..c5eb9caea0 100644 --- a/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.html +++ b/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.html @@ -8,8 +8,17 @@

Diagnostics

{{ buildDate }}
{{ session.user.name }} - {{ session.user.name }} (You have administrative privileges) - Tech Preview features are enabled + + {{ session.user.name }} (You have administrative privileges) + Tech Preview features are enabled + +
+ List max size: {{session.config.listMaxSize || 'Not Set'}} + Allow fetch of all entities of a maxed list: {{!!session.config.listAllowLoadMaxed}} +
+
@@ -17,32 +26,40 @@

Diagnostics

- {{ session.diagnostics.gitClientVersion || 'N/A' }} + + {{ session.diagnostics.gitClientVersion || 'N/A' }}
- {{ session.diagnostics.deploymentType }} + {{ session.diagnostics.deploymentType }} +
- {{ session.diagnostics.helmChartVersion || 'N/A' }} - {{ session.diagnostics.helmName || 'N/A' }} - {{ session.diagnostics.helmRevision || 'N/A' }} - {{ helmLastModified$ | async | date:'medium' }} + {{ session.diagnostics.helmChartVersion || 'N/A' }} + + {{ session.diagnostics.helmName || 'N/A' }} + + {{ session.diagnostics.helmRevision || 'N/A' }} + + {{ helmLastModified$ | async | date:'medium' }} +
- {{ session.diagnostics.databaseBackend || 'N/A' }} + {{ session.diagnostics.databaseBackend || 'N/A' }} + @@ -67,4 +84,4 @@

Diagnostics

- + \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.scss b/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.scss index 48c13aacfc..47d2313f55 100644 --- a/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.scss +++ b/src/frontend/packages/core/src/features/about/diagnostics-page/diagnostics-page.component.scss @@ -23,9 +23,6 @@ display: flex; flex-direction: row; } - &__warning-icon { - margin-right: 8px; - } &__migrations { padding-left: 36px; width: 100%; @@ -37,4 +34,10 @@ &__two-column { flex: 1; } + &__maxed { + &__content { + display: flex; + flex-direction: column; + } + } } diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source-types.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source-types.ts index 453ab5c46e..4b1af3bd34 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source-types.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source-types.ts @@ -90,6 +90,7 @@ export interface IListDataSource extends ICoreListDataSource, EntityCatalo isLoadingPage$: Observable; maxedResults$: Observable; + maxedStateStartAt$: Observable; filter$: Observable; sort$: Observable; @@ -123,7 +124,10 @@ export interface IListDataSource extends ICoreListDataSource, EntityCatalo refresh(); updateMetricsAction(newAction: MetricsAction); - + /** + * Ensure that list maxed status is ignored. This will result in all results being shown when previously ignored + */ + showAllAfterMax(); } export type getRowUniqueId = (T) => string; diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts index 68b8bb789c..762a4df7c4 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-data-source.ts @@ -6,6 +6,7 @@ import { combineLatest, Observable, of as observableOf, + of, OperatorFunction, ReplaySubject, Subscription, @@ -26,7 +27,7 @@ import { import { ListFilter, ListSort } from '../../../../../../store/src/actions/list.actions'; import { MetricsAction } from '../../../../../../store/src/actions/metrics.actions'; -import { SetResultCount } from '../../../../../../store/src/actions/pagination.actions'; +import { IgnorePaginationMaxedState, SetResultCount } from '../../../../../../store/src/actions/pagination.actions'; import { AppState } from '../../../../../../store/src/app-state'; import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog.service'; import { EntitySchema } from '../../../../../../store/src/helpers/entity-schema'; @@ -100,7 +101,10 @@ export abstract class ListDataSource extends DataSource implements // Misc public isLoadingPage$: Observable = observableOf(false); public rowsState: Observable; + + // Maxed Collection public maxedResults$: Observable = observableOf(false); + public maxedStateStartAt$: Observable = observableOf(null); public filter$: Observable; public sort$: Observable; @@ -200,11 +204,19 @@ export abstract class ListDataSource extends DataSource implements this.filter$ = this.createFilterObservable(); - this.maxedResults$ = !!this.masterAction.flattenPaginationMax ? - this.pagination$.pipe( - map(LocalPaginationHelpers.isPaginationMaxed), - distinctUntilChanged(), - ) : observableOf(false); + this.maxedResults$ = this.pagination$.pipe( + map(LocalPaginationHelpers.isPaginationMaxed), + distinctUntilChanged(), + ); + + const catalogEntity = entityCatalog.getEntity( + this.masterAction.endpointType, + this.masterAction.entityType + ); + const paginationConfig = catalogEntity.getPaginationConfig(); + this.maxedStateStartAt$ = paginationConfig ? + paginationConfig.maxedStateStartAt(this.store, this.masterAction) : + of(null); } init(config: IListDataSourceConfig) { @@ -225,7 +237,7 @@ export abstract class ListDataSource extends DataSource implements this.entityKey = this.sourceScheme.key; this.entityType = this.action.entityType; this.endpointType = this.action.endpointType; - this.masterAction = this.action as PaginatedAction; + this.masterAction = this.action; this.setupAction(config); if (!this.isLocal && this.config.listConfig) { // This is a non-local data source so the results-per-page should match the initial page size. This will avoid making two calls @@ -237,7 +249,7 @@ export abstract class ListDataSource extends DataSource implements private setupAction(config: IListDataSourceConfig) { if (config.schema instanceof MultiActionConfig) { if (!config.isLocal) { - // We cannot do multi action lists for none local lists + // We cannot do multi action lists for non-local lists this.action = config.schema[0].paginationAction; this.masterAction = this.action as PaginatedAction; } else { @@ -402,7 +414,7 @@ export abstract class ListDataSource extends DataSource implements trackBy = (index: number, item: T) => this.getRowUniqueId(item) || item; - attachTransformEntity(entities$, entityLettable): Observable { + private attachTransformEntity(entities$, entityLettable): Observable { if (entityLettable) { return entities$.pipe( this.transformEntity @@ -440,6 +452,14 @@ export abstract class ListDataSource extends DataSource implements } } + public showAllAfterMax() { + this.store.dispatch(new IgnorePaginationMaxedState( + this.masterAction.entityType, + this.masterAction.endpointType, + this.masterAction.paginationKey + )); + } + private createSortObservable(): Observable { return this.pagination$.pipe( map(pag => ({ diff --git a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-pagination-controller.ts b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-pagination-controller.ts index c9be42d714..0cdf9bd632 100644 --- a/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-pagination-controller.ts +++ b/src/frontend/packages/core/src/shared/components/list/data-sources-controllers/list-pagination-controller.ts @@ -167,7 +167,7 @@ export class ListPaginationController implements IListPaginationController )); } - if (paginationEntityState.maxedMode) { + if (paginationEntityState.maxedState.isMaxedMode && !paginationEntityState.maxedState.ignoreMaxed) { this.dataSource.setMultiFilter(changes, paginationEntityState.params); } 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 98920db598..663ef63e5e 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 @@ -9,7 +9,8 @@ export class LocalPaginationHelpers { static isPaginationMaxed(pagination: PaginationEntityState) { if (pagination.forcedLocalPage) { const forcedPage = pagination.pageRequests[pagination.forcedLocalPage]; - return !!forcedPage.maxed; + // SI Wall, 2 CFs, Select SI only, Filter to Org, Switch CFs, pagination has been reset so no page + return forcedPage && forcedPage.maxed; } return !!Object.values(pagination.pageRequests).find(request => request.maxed); } diff --git a/src/frontend/packages/core/src/shared/components/list/list-table/table.types.ts b/src/frontend/packages/core/src/shared/components/list/list-table/table.types.ts index 938e2b0291..aedda59f79 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-table/table.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-table/table.types.ts @@ -56,11 +56,19 @@ export interface ITableColumn { cellAlignSelf?: string; } +export interface ITableTextMaxed { + icon: string; + iconFont?: string; + canIgnoreMaxFirstLine: string; + cannotIgnoreMaxFirstLine: string; + filterLine?: string; +} + export interface ITableText { title?: string; filter?: string; noEntries?: string; - maxedResults?: string; + maxedResults?: ITableTextMaxed; } export const listTableComponents = [ diff --git a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts index c2f3113a55..e8a189b97a 100644 --- a/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list-types/endpoint/endpoint-card/endpoint-card.component.ts @@ -9,6 +9,8 @@ import { ViewContainerRef, } from '@angular/core'; import { Store } from '@ngrx/store'; +import { CurrentUserPermissions } from 'frontend/packages/core/src/core/current-user-permissions.config'; +import { CurrentUserPermissionsService } from 'frontend/packages/core/src/core/current-user-permissions.service'; import { AppState } from 'frontend/packages/store/src/app-state'; import { Observable, of, ReplaySubject, Subscription } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; @@ -24,14 +26,15 @@ import { } from '../../../../../../features/endpoints/endpoint-helpers'; import { StratosStatus } from '../../../../../shared.types'; import { FavoritesConfigMapper } from '../../../../favorites-meta-card/favorite-config-mapper'; -import { MetaCardMenuItem, createMetaCardMenuItemSeparator } from '../../../list-cards/meta-card/meta-card-base/meta-card.component'; +import { + createMetaCardMenuItemSeparator, + MetaCardMenuItem, +} from '../../../list-cards/meta-card/meta-card-base/meta-card.component'; import { CardCell } from '../../../list.types'; import { BaseEndpointsDataSource } from '../base-endpoints-data-source'; import { EndpointListDetailsComponent, EndpointListHelper } from '../endpoint-list.helpers'; -import { CopyToClipboardComponent } from './../../../../copy-to-clipboard/copy-to-clipboard.component'; import { RouterNav } from './../../../../../../../../store/src/actions/router.actions'; -import { CurrentUserPermissions } from 'frontend/packages/core/src/core/current-user-permissions.config'; -import { CurrentUserPermissionsService } from 'frontend/packages/core/src/core/current-user-permissions.service'; +import { CopyToClipboardComponent } from './../../../../copy-to-clipboard/copy-to-clipboard.component'; @Component({ selector: 'app-endpoint-card', diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.html b/src/frontend/packages/core/src/shared/components/list/list.component.html index bcbde5192e..28ee1e36ef 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.html +++ b/src/frontend/packages/core/src/shared/components/list/list.component.html @@ -61,7 +61,7 @@
-
+
@@ -193,19 +193,14 @@ - - - -
{{config.text?.maxedResults || 'There are too many results. If available please use the - filters to reduce the number of results.'}}
-
-
-
- - + + diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.ts b/src/frontend/packages/core/src/shared/components/list/list.component.ts index ee00ac9187..557bf779ab 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.ts @@ -110,8 +110,6 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView @Input() noEntriesForCurrentFilter: TemplateRef; - @Input() noEntriesMaxedResults: TemplateRef; - // List config when supplied as an attribute rather than a dependency @Input() listConfig: ListConfig; initialEntitySelection$: Observable; @@ -713,4 +711,7 @@ export class ListComponent implements OnInit, OnChanges, OnDestroy, AfterView private getRowStateFromRowsState = (row: T): Observable => this.dataSource.rowsState.pipe(map(state => state[this.dataSource.getRowUniqueId(row)] || getDefaultRowState())) + public showAllAfterMax() { + this.dataSource.showAllAfterMax(); + } } diff --git a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts index a0f08a4282..a8321b6fc5 100644 --- a/src/frontend/packages/core/src/shared/components/list/list.component.types.ts +++ b/src/frontend/packages/core/src/shared/components/list/list.component.types.ts @@ -155,7 +155,7 @@ export class ListConfig implements IListConfig { isLocal = false; pageSizeOptions = defaultPaginationPageSizeOptionsCards; viewType = ListViewTypes.BOTH; - text = null; + text: ITableText = null; enableTextFilter = false; cardComponent = null; defaultView = 'table' as ListView; diff --git a/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.html b/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.html new file mode 100644 index 0000000000..8d6021f0e3 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.html @@ -0,0 +1,18 @@ +
+ + + + + +
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.scss b/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.scss new file mode 100644 index 0000000000..ccf5bfbf05 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.scss @@ -0,0 +1,11 @@ +.maxed { + align-items: center; + display: flex; + flex-direction: column; + + &__load { + align-items: center; + display: flex; + flex-direction: column; + } +} diff --git a/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.spec.ts b/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.spec.ts new file mode 100644 index 0000000000..abb3e9ef77 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.spec.ts @@ -0,0 +1,37 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatIconModule } from '@angular/material'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { createBasicStoreModule } from '../../../../../../store/testing/public-api'; +import { NoContentMessageComponent } from '../../no-content-message/no-content-message.component'; +import { MaxListMessageComponent } from './max-list-message.component'; + +describe('MaxListMessageComponent', () => { + let component: MaxListMessageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + MaxListMessageComponent, + NoContentMessageComponent + ], + imports: [ + MatIconModule, + RouterTestingModule, + createBasicStoreModule() + ], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MaxListMessageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.ts b/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.ts new file mode 100644 index 0000000000..1e82d3bd24 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/list/max-list-message/max-list-message.component.ts @@ -0,0 +1,123 @@ +import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; + +import { AppState } from '../../../../../../store/src/app-state'; +import { entityCatalog } from '../../../../../../store/src/entity-catalog/entity-catalog.service'; +import { EntityCatalogEntityConfig } from '../../../../../../store/src/entity-catalog/entity-catalog.types'; +import { + PaginationPageIteratorConfig, +} from '../../../../../../store/src/entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe'; +import { safeUnsubscribe } from '../../../../core/utils.service'; +import { NoContentMessageLine } from '../../no-content-message/no-content-message.component'; +import { ITableTextMaxed } from '../list-table/table.types'; + + +@Component({ + selector: 'app-max-list-message', + templateUrl: './max-list-message.component.html', + styleUrls: ['./max-list-message.component.scss'] +}) +export class MaxListMessageComponent implements OnDestroy { + + @Input() + set config(config: ITableTextMaxed) { + const safeConfig: ITableTextMaxed = config || { + icon: '', + canIgnoreMaxFirstLine: '', + cannotIgnoreMaxFirstLine: '', + }; + this.configSubject.next({ + icon: safeConfig.icon || MaxListMessageComponent.defaultConfig.icon, + iconFont: safeConfig.iconFont || MaxListMessageComponent.defaultConfig.iconFont, + canIgnoreMaxFirstLine: safeConfig.canIgnoreMaxFirstLine || MaxListMessageComponent.defaultConfig.canIgnoreMaxFirstLine, + cannotIgnoreMaxFirstLine: safeConfig.cannotIgnoreMaxFirstLine || MaxListMessageComponent.defaultConfig.cannotIgnoreMaxFirstLine, + filterLine: safeConfig.filterLine || MaxListMessageComponent.defaultConfig.filterLine + }); + } + + @Input() + set catalogueEntity(entityConfig: EntityCatalogEntityConfig) { + if (this.canIgnoreMaxedStatePipeSub) { + this.canIgnoreMaxedStatePipeSub.unsubscribe(); + } + + if (!entityConfig) { + return; + } + + const catalogueEntity = entityCatalog.getEntity(entityConfig); + const paginationConfig: PaginationPageIteratorConfig = catalogueEntity.getPaginationConfig(); + if (paginationConfig) { + this.canIgnoreMaxedStatePipeSub = paginationConfig.canIgnoreMaxedState(this.store).subscribe( + canIgnoreMaxedState => this.canIgnoreMaxedState.next(canIgnoreMaxedState) + ); + } else { + this.canIgnoreMaxedState.next(false); + } + } + + constructor(private store: Store) { } + + static defaultConfig: ITableTextMaxed = { + icon: 'apps', + canIgnoreMaxFirstLine: 'Fetching all entities might take a long time', + cannotIgnoreMaxFirstLine: 'There are too many entities to fetch', + }; + + private canIgnoreMaxedStatePipeSub: Subscription; + private canIgnoreMaxedState = new BehaviorSubject(null); + private canIgnoreMaxedState$ = this.canIgnoreMaxedState.asObservable(); + + private configSubject = new BehaviorSubject(null); + private config$ = this.configSubject.asObservable(); + + public state$: Observable<{ + icon: string; + iconFont: string; + firstLine: string; + otherLines: NoContentMessageLine[]; + canIgnoreMaxedState: boolean; + }> = combineLatest([ + this.canIgnoreMaxedState$, + this.config$ + ]).pipe( + filter(([canIgnoreMaxedState, config]) => canIgnoreMaxedState != null && config != null), + map(([canIgnoreMaxedState, config]) => { + const otherLines = []; + if (config.filterLine) { + otherLines.push( + { text: config.filterLine }, + ); + + if (canIgnoreMaxedState) { + otherLines.push( + { text: 'or' }, + ); + } + } + + return { + icon: config.icon, + iconFont: config.iconFont, + firstLine: canIgnoreMaxedState ? config.canIgnoreMaxFirstLine : config.cannotIgnoreMaxFirstLine, + otherLines, + canIgnoreMaxedState + }; + }), + ); + + @Input() count = 0; + @Input() maxCount = 0; + + @Output() showAllAfterMax = new EventEmitter(); + + showAll() { + this.showAllAfterMax.emit(); + } + + ngOnDestroy() { + safeUnsubscribe(this.canIgnoreMaxedStatePipeSub); + } +} diff --git a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.html b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.html index fb561baf1c..efe500ff02 100644 --- a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.html +++ b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.html @@ -6,5 +6,10 @@ {{icon}}
{{firstLine}}
- {{secondLine?.linkText}}{{secondLine?.text}}
-
+ {{secondLine?.linkText}}{{secondLine?.text}} +
+
+ {{line?.linkText}}{{line?.text}} +
+ +
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.ts b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.ts index dcb09ca95e..f3626e8f69 100644 --- a/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.ts +++ b/src/frontend/packages/core/src/shared/components/no-content-message/no-content-message.component.ts @@ -1,5 +1,10 @@ -import { Component, Input, OnInit, ViewChild, ElementRef, AfterViewInit, Renderer2 } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, Input, Renderer2, ViewChild } from '@angular/core'; +export interface NoContentMessageLine { + link?: string; + linkText?: string; + text: string; +} @Component({ selector: 'app-no-content-message', templateUrl: './no-content-message.component.html', @@ -10,11 +15,8 @@ export class NoContentMessageComponent implements AfterViewInit { @Input() icon: string; @Input() iconFont: string; @Input() firstLine: string; - @Input() secondLine: { - link?: string; - linkText?: string; - text: string; - }; + @Input() secondLine: NoContentMessageLine; + @Input() otherLines: NoContentMessageLine[]; @Input() toolbarLink: { text: string; }; @@ -22,7 +24,7 @@ export class NoContentMessageComponent implements AfterViewInit { @Input() mode: string; - @ViewChild('toolBarLinkElement', {static: false}) toolBarLinkElement: ElementRef; + @ViewChild('toolBarLinkElement', { static: false }) toolBarLinkElement: ElementRef; constructor(private renderer: Renderer2) { } diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 7fac344dc1..c380a0c102 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -24,6 +24,7 @@ import { ApplicationStateService } from './components/application-state/applicat import { BlurDirective } from './components/blur.directive'; import { BooleanIndicatorComponent } from './components/boolean-indicator/boolean-indicator.component'; import { BreadcrumbsComponent } from './components/breadcrumbs/breadcrumbs.component'; +import { CardProgressOverlayComponent } from './components/card-progress-overlay/card-progress-overlay.component'; import { CardBooleanMetricComponent } from './components/cards/card-boolean-metric/card-boolean-metric.component'; import { CardNumberMetricComponent } from './components/cards/card-number-metric/card-number-metric.component'; import { CardStatusComponent } from './components/cards/card-status/card-status.component'; @@ -59,6 +60,9 @@ import { MetaCardValueComponent } from './components/list/list-cards/meta-card/m import { TableCellRequestMonitorIconComponent, } from './components/list/list-table/table-cell-request-monitor-icon/table-cell-request-monitor-icon.component'; +import { + TableCellSidePanelComponent, +} from './components/list/list-table/table-cell-side-panel/table-cell-side-panel.component'; import { TableCellStatusDirective } from './components/list/list-table/table-cell-status.directive'; import { TableComponent } from './components/list/list-table/table.component'; import { listTableComponents } from './components/list/list-table/table.types'; @@ -70,6 +74,7 @@ import { } from './components/list/list-types/endpoint/table-cell-endpoint-name/table-cell-endpoint-name.component'; import { ListComponent } from './components/list/list.component'; import { ListConfig } from './components/list/list.component.types'; +import { MaxListMessageComponent } from './components/list/max-list-message/max-list-message.component'; import { ListHostDirective } from './components/list/simple-list/list-host.directive'; import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; import { LoadingPageComponent } from './components/loading-page/loading-page.component'; @@ -124,8 +129,6 @@ import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundr import { LongRunningOperationsService } from './services/long-running-op.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; import { UserPermissionDirective } from './user-permission.directive'; -import { TableCellSidePanelComponent } from './components/list/list-table/table-cell-side-panel/table-cell-side-panel.component'; -import { CardProgressOverlayComponent } from './components/card-progress-overlay/card-progress-overlay.component'; @NgModule({ @@ -234,6 +237,7 @@ import { CardProgressOverlayComponent } from './components/card-progress-overlay SidepanelPreviewComponent, TableCellSidePanelComponent, CardProgressOverlayComponent, + MaxListMessageComponent, ], exports: [ ApplicationStateIconPipe, @@ -330,6 +334,7 @@ import { CardProgressOverlayComponent } from './components/card-progress-overlay SidepanelPreviewComponent, TableCellEndpointNameComponent, CardProgressOverlayComponent, + MaxListMessageComponent ], entryComponents: [ DialogConfirmComponent, diff --git a/src/frontend/packages/store/src/actions/pagination.actions.ts b/src/frontend/packages/store/src/actions/pagination.actions.ts index 13f9298e8d..b36603d1e6 100644 --- a/src/frontend/packages/store/src/actions/pagination.actions.ts +++ b/src/frontend/packages/store/src/actions/pagination.actions.ts @@ -1,9 +1,6 @@ import { Action } from '@ngrx/store'; -import { - EntityCatalogEntityConfig, - extractEntityCatalogEntityConfig, -} from '../entity-catalog/entity-catalog.types'; +import { EntityCatalogEntityConfig, extractEntityCatalogEntityConfig } from '../entity-catalog/entity-catalog.types'; import { PaginationClientFilter, PaginationParam } from '../types/pagination.types'; export const CLEAR_PAGINATION_OF_TYPE = '[Pagination] Clear all pages of type'; @@ -24,6 +21,7 @@ export const REMOVE_PARAMS = '[Pagination] Remove Params'; export const SET_PAGE_BUSY = '[Pagination] Set Page Busy'; export const REMOVE_ID_FROM_PAGINATION = '[Pagination] Remove id from pagination'; export const UPDATE_MAXED_STATE = '[Pagination] Update maxed state'; +export const IGNORE_MAXED_STATE = '[Pagination] Ignore maxed state'; export function getPaginationKey(type: string, id: string, endpointGuid?: string) { const key = `${type}-${id}`; @@ -210,3 +208,13 @@ export class UpdatePaginationMaxedState implements Action, EntityCatalogEntityCo public forcedEntityKey?: string ) { } } + +export class IgnorePaginationMaxedState implements Action, EntityCatalogEntityConfig { + type = IGNORE_MAXED_STATE; + constructor( + public entityType: string, + public endpointType: string, + public paginationKey: string, + public forcedEntityKey?: string + ) { } +} diff --git a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity.ts b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity.ts index 901d9b4245..12d4cb43b9 100644 --- a/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity.ts +++ b/src/frontend/packages/store/src/entity-catalog/entity-catalog-entity.ts @@ -3,6 +3,9 @@ import { ActionReducer, Store } from '@ngrx/store'; import { endpointEntitySchema, STRATOS_ENDPOINT_TYPE } from '../../../core/src/base-entity-schemas'; import { getFullEndpointApiUrl } from '../../../core/src/features/endpoints/endpoint-helpers'; import { AppState, IRequestEntityTypeState } from '../app-state'; +import { + PaginationPageIteratorConfig, +} from '../entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe'; import { EntityPipelineEntity, stratosEndpointGuidKey } from '../entity-request-pipeline/pipeline.types'; import { EntitySchema } from '../helpers/entity-schema'; import { EntityMonitor } from '../monitors/entity-monitor'; @@ -215,6 +218,11 @@ export class StratosBaseCatalogEntity< } + public getPaginationConfig(): PaginationPageIteratorConfig { + return this.definition.paginationConfig ? + this.definition.paginationConfig : + null; + } } export class StratosCatalogEntity< @@ -230,6 +238,12 @@ export class StratosCatalogEntity< ) { super(entity, config); } + + public getPaginationConfig(): PaginationPageIteratorConfig { + return this.definition.paginationConfig ? + this.definition.paginationConfig : + this.definition.endpoint ? this.definition.endpoint.paginationConfig : null; + } } export class StratosCatalogEndpointEntity extends StratosBaseCatalogEntity { diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts index 8ce626fd35..a2bbe9bb6b 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-pagination-request-pipeline.ts @@ -3,13 +3,10 @@ import { Action, Store } from '@ngrx/store'; import { isObservable, Observable, of } from 'rxjs'; import { first, map, switchMap } from 'rxjs/operators'; -import { - StratosBaseCatalogEntity, - StratosCatalogEntity, -} from '../entity-catalog/entity-catalog-entity'; +import { AppState, InternalAppState } from '../app-state'; +import { StratosBaseCatalogEntity, StratosCatalogEntity } from '../entity-catalog/entity-catalog-entity'; import { entityCatalog } from '../entity-catalog/entity-catalog.service'; import { IStratosEntityDefinition } from '../entity-catalog/entity-catalog.types'; -import { AppState, InternalAppState } from '../app-state'; import { PaginationFlattenerConfig } from '../helpers/paginated-request-helpers'; import { selectPaginationState } from '../selectors/pagination.selectors'; import { PaginatedAction, PaginationEntityState } from '../types/pagination.types'; @@ -79,9 +76,7 @@ export const basePaginatedRequestPipeline: EntityRequestPipeline = ( const prePaginatedRequestFunction = getPrePaginatedRequestFunction(catalogEntity); const actionDispatcher = (actionToDispatch: Action) => store.dispatch(actionToDispatch); const entity = catalogEntity as StratosCatalogEntity; - const flattenerConfig = entity.definition.paginationConfig ? - entity.definition.paginationConfig : - entity.definition.endpoint ? entity.definition.endpoint.paginationConfig : null; + const flattenerConfig = entity.getPaginationConfig(); // Get pagination state from the store const paginationState = selectPaginationState( @@ -117,7 +112,16 @@ export const basePaginatedRequestPipeline: EntityRequestPipeline = ( first(), switchMap(requestObject => { const pageIterator = flattenerConfig ? - new PaginationPageIterator(httpClient, requestObject, completePaginationAction, actionDispatcher, flattenerConfig) : null; + new PaginationPageIterator( + store, + httpClient, + requestObject, + completePaginationAction, + actionDispatcher, + flattenerConfig, + paginationState ? paginationState.maxedState : null + ) : + null; return getRequestObservable( httpClient, completePaginationAction, diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/handle-multi-endpoints.pipe.spec.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/handle-multi-endpoints.pipe.spec.ts index ff4aa871bc..59e448d735 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/handle-multi-endpoints.pipe.spec.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/handle-multi-endpoints.pipe.spec.ts @@ -1,6 +1,8 @@ -import { handleJetstreamResponsePipeFactory, JetstreamError } from './handle-multi-endpoints.pipe'; -import { JetstreamResponse } from '../entity-request-pipeline.types'; +import { of } from 'rxjs'; + import { JetStreamErrorResponse } from '../../../../core/src/jetstream.helpers'; +import { JetstreamResponse } from '../entity-request-pipeline.types'; +import { handleJetstreamResponsePipeFactory, JetstreamError } from './handle-multi-endpoints.pipe'; describe('handle-multi-endpoint-pipe', () => { it(' should handle error and success', () => { @@ -73,7 +75,9 @@ describe('handle-multi-endpoint-pipe', () => { return total + pages[0].total; }, 0), getPaginationParameters: () => ({ page: '1' }), - getTotalPages: () => 4 + getTotalPages: () => 4, + canIgnoreMaxedState: () => of(false), + maxedStateStartAt: () => null })(resData); expect(handled.successes.length).toBe(2); expect(handled.successes[0].entities[0].data1).toBe(endpoint2Res.entities[0].data1); diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-pipeline.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-pipeline.ts index 4389a1017e..fb7ef8483e 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-pipeline.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-pipeline.ts @@ -2,11 +2,11 @@ import { Action, Store } from '@ngrx/store'; import { of } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; -import { StratosBaseCatalogEntity } from '../entity-catalog/entity-catalog-entity'; -import { entityCatalog } from '../entity-catalog/entity-catalog.service'; import { isHttpErrorResponse } from '../../../core/src/jetstream.helpers'; import { AppState, InternalAppState } from '../app-state'; import { RecursiveDelete } from '../effects/recursive-entity-delete.effect'; +import { StratosBaseCatalogEntity } from '../entity-catalog/entity-catalog-entity'; +import { entityCatalog } from '../entity-catalog/entity-catalog.service'; import { ApiRequestTypes, getRequestTypeFromMethod } from '../reducers/api-request-reducer/request-helpers'; import { PaginatedAction } from '../types/pagination.types'; import { EntityRequestAction } from '../types/request.types'; diff --git a/src/frontend/packages/store/src/entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe.ts b/src/frontend/packages/store/src/entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe.ts index 9245da298d..1038a7e569 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe.ts @@ -1,16 +1,13 @@ import { HttpRequest } from '@angular/common/http'; +import { Store } from '@ngrx/store'; import { combineLatest, Observable, of, range } from 'rxjs'; -import { map, mergeMap, reduce } from 'rxjs/operators'; +import { map, mergeMap, reduce, switchMap } from 'rxjs/operators'; import { UpdatePaginationMaxedState } from '../../actions/pagination.actions'; +import { AppState } from '../../app-state'; import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; -import { PaginatedAction } from '../../types/pagination.types'; -import { - ActionDispatcher, - JetstreamResponse, - PagedJetstreamResponse, - SuccessfulApiResponseDataMapper, -} from '../entity-request-pipeline.types'; +import { PaginatedAction, PaginationMaxedState } from '../../types/pagination.types'; +import { ActionDispatcher, JetstreamResponse, PagedJetstreamResponse } from '../entity-request-pipeline.types'; import { PipelineHttpClient } from '../pipline-http-client.service'; @@ -20,16 +17,27 @@ export interface PaginationPageIteratorConfig { getTotalPages: (initialResponses: JetstreamResponse) => number; getTotalEntities: (initialResponses: JetstreamResponse) => number; getEntitiesFromResponse: (responses: R) => E[]; + /** + * After fetching the first page check that the total number of entities does not exceed this number. + * If so do not fetch other pages and enter 'maxed' error mode + * Only applicable to 'local' collections (everything is fetch up front and paginated locally) + */ + maxedStateStartAt: (store: Store, action: PaginatedAction) => Observable; + /** + * If the collection has entered 'maxed' error mode, can the user ignore and fetch all results regardless (see `maxedStateStartAt`)? + */ + canIgnoreMaxedState: (store: Store) => Observable; } export class PaginationPageIterator { constructor( + private store: Store, private httpClient: PipelineHttpClient, public baseHttpRequest: HttpRequest>, public action: PaginatedAction, public actionDispatcher: ActionDispatcher, public config: PaginationPageIteratorConfig, - public postSuccessDataMapper?: SuccessfulApiResponseDataMapper + private paginationMaxedState?: PaginationMaxedState ) { } private makeRequest(httpRequest: HttpRequest>) { @@ -48,7 +56,7 @@ export class PaginationPageIterator { return of([]); } return range(2, count + 1).pipe( - mergeMap(currentPage => this.makeRequest(this.addPageToRequest(currentPage)), 5), + mergeMap(currentPage => this.makeRequest(this.addPageToRequest(currentPage)), 10), reduce((acc, res: JetstreamResponse) => { acc.push(res); return acc; @@ -86,19 +94,29 @@ export class PaginationPageIterator { private handleRequests(initialResponse: JetstreamResponse, action: PaginatedAction, totalPages: number, totalResults: number): Observable<[JetstreamResponse, JetstreamResponse[]]> { - if (totalResults > 0) { - const maxCount = action.flattenPaginationMax; - // We're maxed so only respond with the first page of results. - if (maxCount < totalResults) { - const { entityType, endpointType, paginationKey, __forcedPageEntityConfig__ } = action; - const forcedEntityKey = entityCatalog.getEntityKey(__forcedPageEntityConfig__); - this.actionDispatcher( - new UpdatePaginationMaxedState(maxCount, totalResults, entityType, endpointType, paginationKey, forcedEntityKey) - ); - return of([initialResponse, []]); - } + + const allResults = combineLatest(of(initialResponse), this.getAllOtherPageRequests(totalPages)); + + if (totalResults === 0 || (this.paginationMaxedState && this.paginationMaxedState.ignoreMaxed)) { + return allResults; } - return combineLatest(of(initialResponse), this.getAllOtherPageRequests(totalPages)); + + return this.config.maxedStateStartAt(this.store, action).pipe( + switchMap(maxEntities => { + if (maxEntities && maxEntities <= totalResults) { + // We've entered 'maxed' mode. Only respond with the first page of results. + const { entityType, endpointType, paginationKey } = action; + const entityKey = entityCatalog.getEntityKey(action); + // The entity type should always match the pagination key. If in a multi-action list this means that of the action rather than + // the forced entity + this.actionDispatcher( + new UpdatePaginationMaxedState(maxEntities, totalResults, entityType, endpointType, paginationKey, entityKey) + ); + return combineLatest([of(initialResponse), of([])]); + } + return allResults; + }) + ); } private getValidNumber(num: number) { diff --git a/src/frontend/packages/store/src/helpers/paginated-request-helpers.ts b/src/frontend/packages/store/src/helpers/paginated-request-helpers.ts index fbbe553fc5..2a55648abc 100644 --- a/src/frontend/packages/store/src/helpers/paginated-request-helpers.ts +++ b/src/frontend/packages/store/src/helpers/paginated-request-helpers.ts @@ -2,10 +2,13 @@ import { HttpClient, HttpRequest } from '@angular/common/http'; import { forkJoin, Observable, of as observableOf } from 'rxjs'; import { first, map, mergeMap } from 'rxjs/operators'; -import { CFResponse } from '../../../cloud-foundry/src/store/types/cf-api.types'; import { UpdatePaginationMaxedState } from '../actions/pagination.actions'; import { ActionDispatcher } from '../entity-request-pipeline/entity-request-pipeline.types'; + +// TODO: See #4208. This should be replaced with +// src/frontend/packages/store/src/entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe.ts + export interface PaginationFlattenerConfig extends Pick< PaginationFlattener, 'getTotalPages' | 'getTotalResults' | 'mergePages' | 'clearResults' @@ -63,51 +66,6 @@ export class BaseHttpFetcher { } } -export class CfAPIFlattener extends BaseHttpFetcher implements PaginationFlattener { - - constructor(http: HttpClient, requestOptions: HttpRequest) { - super(http, requestOptions, 'page'); - } - - public getTotalPages = res => - Object.keys(res).reduce((max, endpointGuid) => { - const endpoint = res[endpointGuid]; - return max < endpoint.total_pages ? endpoint.total_pages : max; - }, 0) - - public mergePages = (responses: CFResponse[]) => { - // Merge all responses into the first page - const newResData = responses[0]; - const endpointGuids = Object.keys(newResData); - for (let i = 1; i < responses.length; i++) { - // Make any additional page requests - const endpointResponse = responses[i]; - endpointGuids.forEach(endpointGuid => { - const endpoint = endpointResponse[endpointGuid]; - if (endpoint && endpoint.resources && endpoint.resources.length) { - newResData[endpointGuid].resources = newResData[ - endpointGuid - ].resources.concat(endpoint.resources); - } - }); - } - return newResData; - } - public getTotalResults = res => { - return Object.keys(res).reduce((count, endpointGuid) => { - const endpoint: CFResponse = res[endpointGuid]; - return count + endpoint.total_results; - }, 0); - } - public clearResults = (res: { [cfGuid: string]: CFResponse }, allResults: number): Observable => { - Object.keys(res).forEach(endpointKey => { - const endpoint = res[endpointKey]; - endpoint.total_pages = 1; - }); - return observableOf(res); - } -} - export function flattenPagination( actionDispatcher: ActionDispatcher, firstRequest: Observable, @@ -123,6 +81,7 @@ export function flattenPagination( mergeMap(firstResData => { const allResults = flattener.getTotalResults(firstResData); if (maxCount) { + // Note - This isn't ever being used, maxCount is always undefined. See #4208 actionDispatcher( new UpdatePaginationMaxedState(maxCount, allResults, entityType, endpointType, paginationKey, forcedEntityKey) ); diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-clear-pagination-type.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-clear-pagination-type.ts index c194e45ae4..25cede1fdd 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-clear-pagination-type.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-clear-pagination-type.ts @@ -1,12 +1,14 @@ -import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; import { EndpointAction } from '../../actions/endpoint.actions'; -import { PaginationEntityState, PaginationState } from '../../types/pagination.types'; +import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; +import { PaginationState } from '../../types/pagination.types'; +import { getDefaultPaginationEntityState } from './pagination-reducer-reset-pagination'; -export function paginationClearAllTypes(state: PaginationState, entityKeys: string[], defaultPaginationEntityState: PaginationEntityState) { +export function paginationClearAllTypes(state: PaginationState, entityKeys: string[]) { return entityKeys.reduce((prevState, entityKey) => { if (prevState[entityKey]) { const entityState = state[entityKey]; const clearedEntity = Object.keys(entityState).reduce((prevEntityState, key) => { + const defaultPaginationEntityState = getDefaultPaginationEntityState(entityState[key].maxedState.ignoreMaxed); return { ...prevEntityState, [key]: defaultPaginationEntityState @@ -21,13 +23,12 @@ export function paginationClearAllTypes(state: PaginationState, entityKeys: stri }, state); } -export function clearEndpointEntities(state: PaginationState, action: EndpointAction, defaultPaginationEntityState: PaginationEntityState) { +export function clearEndpointEntities(state: PaginationState, action: EndpointAction) { const entityKeys = entityCatalog.getAllEntitiesForEndpointType(action.endpointType).map(entity => entity.entityKey); if (entityKeys.length > 0) { return paginationClearAllTypes( state, - entityKeys, - defaultPaginationEntityState + entityKeys ); } return state; diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-max-reached.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-max-reached.ts index adf0af62c1..b24dc25f4e 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-max-reached.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-max-reached.ts @@ -1,9 +1,10 @@ -import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; import { LocalPaginationHelpers, } from '../../../../core/src/shared/components/list/data-sources-controllers/local-list.helpers'; -import { UpdatePaginationMaxedState } from '../../actions/pagination.actions'; +import { IgnorePaginationMaxedState, UpdatePaginationMaxedState } from '../../actions/pagination.actions'; +import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; import { PaginationEntityTypeState, PaginationState } from '../../types/pagination.types'; +import { getDefaultPaginationEntityState } from './pagination-reducer-reset-pagination'; export function paginationMaxReached(state: PaginationState, action: UpdatePaginationMaxedState): PaginationState { const entityKey = entityCatalog.getEntityKey(action); @@ -14,7 +15,11 @@ export function paginationMaxReached(state: PaginationState, action: UpdatePagin state[entityKey][action.paginationKey], action.forcedEntityKey || entityKey ); - const { maxedMode: oldMaxedMode } = state[entityKey][action.paginationKey]; + const { maxedState: oldMaxedState } = state[entityKey][action.paginationKey]; + if (oldMaxedState.ignoreMaxed) { + return state; + } + const oldMaxedMode = oldMaxedState.isMaxedMode; const { pageNumber, pageRequest } = requestSection; const { maxed: oldCurrentlyMaxed = false } = pageRequest; const newCurrentlyMaxed = action.allEntities >= action.max; @@ -33,7 +38,6 @@ export function paginationMaxReached(state: PaginationState, action: UpdatePagin ...state[entityKey], [action.paginationKey]: { ...state[entityKey][action.paginationKey], - // currentlyMaxed: newCurrentlyMaxed, pageRequests: { ...state[entityKey][action.paginationKey].pageRequests, [pageNumber]: { @@ -42,7 +46,10 @@ export function paginationMaxReached(state: PaginationState, action: UpdatePagin } }, // Once a list is maxed it can never go back, so can't set true to false - maxedMode: oldMaxedMode || newMaxedMode + maxedState: { + ...state[entityKey][action.paginationKey].maxedState, + isMaxedMode: oldMaxedMode || newMaxedMode + } } }; return { @@ -50,3 +57,35 @@ export function paginationMaxReached(state: PaginationState, action: UpdatePagin [entityKey]: entityState }; } + +export function paginationIgnoreMaxed(state: PaginationState, ignoreAction: IgnorePaginationMaxedState): PaginationState { + // Reset the pagination back to default and set the ignoreMaxed flag + const entityKey = entityCatalog.getEntityKey(ignoreAction); + const defaultPaginationEntityState = getDefaultPaginationEntityState(); + // Retain the page size, order, etc. We may need to look at this again when applying max to other entity types + const { q, ...params } = state[entityKey][ignoreAction.paginationKey].params; + const entityState: PaginationEntityTypeState = { + ...state[entityKey], + [ignoreAction.paginationKey]: { + ...defaultPaginationEntityState, + clientPagination: { + ...defaultPaginationEntityState.clientPagination, + filter: { + // Retain the original filter. Losing this would leave the list controls in an odd way (see cf users table) + ...state[entityKey][ignoreAction.paginationKey].clientPagination.filter + } + }, + params, + maxedState: { + // Retain the original maxed state. This will be true, but is ignored anyway + ...state[entityKey][ignoreAction.paginationKey].maxedState, + ignoreMaxed: true, + } + } + }; + return { + ...state, + [entityKey]: entityState + }; + +} diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts index 2c6ca68e97..1509f943c2 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-reset-pagination.ts @@ -1,5 +1,5 @@ -import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; import { ResetPagination } from '../../actions/pagination.actions'; +import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; import { PaginationEntityState, PaginationEntityTypeState, PaginationState } from '../../types/pagination.types'; export const defaultClientPaginationPageSize = 9; @@ -21,12 +21,19 @@ const defaultPaginationEntityState: PaginationEntityState = { items: {} }, totalResults: 0 + }, + maxedState: { + isMaxedMode: false } }; -export function getDefaultPaginationEntityState(): PaginationEntityState { +export function getDefaultPaginationEntityState(ignoreMaxed?: boolean): PaginationEntityState { return { - ...defaultPaginationEntityState + ...defaultPaginationEntityState, + maxedState: { + ...defaultPaginationEntityState.maxedState, + ignoreMaxed + } }; } diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-start.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-start.ts index 96ce6e82c9..cc05b9abef 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-start.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination-reducer-start.ts @@ -1,7 +1,7 @@ import { EntityCatalogEntityConfig } from '../../entity-catalog/entity-catalog.types'; import { PaginationEntityState } from '../../types/pagination.types'; -export function paginationStart(state, action): PaginationEntityState { +export function paginationStart(state: PaginationEntityState, action): PaginationEntityState { const page = action.apiAction.__forcedPageNumber__ || action.apiAction.pageNumber || state.currentPage; const entityConfig = action.apiAction.__forcedPageEntityConfig__ as EntityCatalogEntityConfig; 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 9a80a887d2..7cbe24d032 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 @@ -149,8 +149,15 @@ function shouldFetchLocalList( return true; } - // Should a maxed local list be refetched? - if (pagination.maxedMode) { + // Have we just reset pagination after choosing to ignore maxed? + if (prevPagination && !prevPagination.maxedState.ignoreMaxed && + pagination.maxedState.ignoreMaxed && + invalidOrMissingPage) { + return true; + } + + // Should a maxed local list be re-fetched? + if (pagination.maxedState.isMaxedMode && !pagination.maxedState.ignoreMaxed) { const paramsChanged = prevPagination && paginationParamsString(prevPagination.params) !== paginationParamsString(pagination.params); return invalidOrMissingPage || paramsChanged; } diff --git a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts index 5500e205ec..0784503537 100644 --- a/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts +++ b/src/frontend/packages/store/src/reducers/pagination-reducer/pagination.reducer.ts @@ -1,6 +1,3 @@ -import { InitCatalogEntitiesAction } from '../../entity-catalog.actions'; -import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; -import { getDefaultStateFromEntityCatalog } from '../../entity-catalog/entity-catalog.store-setup'; import { CONNECT_ENDPOINTS_SUCCESS, DISCONNECT_ENDPOINTS_SUCCESS, @@ -13,6 +10,8 @@ import { CLEAR_PAGINATION_OF_TYPE, ClearPaginationOfType, CREATE_PAGINATION, + IGNORE_MAXED_STATE, + IgnorePaginationMaxedState, REMOVE_PARAMS, RESET_PAGINATION, SET_CLIENT_FILTER, @@ -27,6 +26,9 @@ import { UPDATE_MAXED_STATE, } from '../../actions/pagination.actions'; import { ApiActionTypes } from '../../actions/request.actions'; +import { InitCatalogEntitiesAction } from '../../entity-catalog.actions'; +import { entityCatalog } from '../../entity-catalog/entity-catalog.service'; +import { getDefaultStateFromEntityCatalog } from '../../entity-catalog/entity-catalog.store-setup'; import { mergeState } from '../../helpers/reducer.helper'; import { PaginationEntityState, PaginationState } from '../../types/pagination.types'; import { UpdatePaginationMaxedState } from './../../actions/pagination.actions'; @@ -35,7 +37,7 @@ import { paginationClearPages } from './pagination-reducer-clear-pages'; import { paginationClearOfEntity } from './pagination-reducer-clear-pagination-of-entity'; import { clearEndpointEntities, paginationClearAllTypes } from './pagination-reducer-clear-pagination-type'; import { createNewPaginationSection } from './pagination-reducer-create-pagination'; -import { paginationMaxReached } from './pagination-reducer-max-reached'; +import { paginationIgnoreMaxed, paginationMaxReached } from './pagination-reducer-max-reached'; import { paginationRemoveParams } from './pagination-reducer-remove-params'; import { getDefaultPaginationEntityState, paginationResetPagination } from './pagination-reducer-reset-pagination'; import { paginationSetClientFilter } from './pagination-reducer-set-client-filter'; @@ -122,7 +124,7 @@ function paginate(action, state = {}, updatePagination) { if (action.type === CLEAR_PAGINATION_OF_TYPE) { const clearAction = action as ClearPaginationOfType; const clearEntityType = entityCatalog.getEntityKey(clearAction.entityConfig.endpointType, clearAction.entityConfig.entityType); - return paginationClearAllTypes(state, [clearEntityType], getDefaultPaginationEntityState()); + return paginationClearAllTypes(state, [clearEntityType]); } if (action.type === CLEAR_PAGINATION_OF_ENTITY) { @@ -130,13 +132,17 @@ function paginate(action, state = {}, updatePagination) { } if (isEndpointAction(action)) { - return clearEndpointEntities(state, action, getDefaultPaginationEntityState()); + return clearEndpointEntities(state, action); } if (action.type === UPDATE_MAXED_STATE) { return paginationMaxReached(state, action as UpdatePaginationMaxedState); } + if (action.type === IGNORE_MAXED_STATE) { + return paginationIgnoreMaxed(state, action as IgnorePaginationMaxedState); + } + return enterPaginationReducer(state, action, updatePagination); } diff --git a/src/frontend/packages/store/src/types/auth.types.ts b/src/frontend/packages/store/src/types/auth.types.ts index 586b64e8fe..de78d33feb 100644 --- a/src/frontend/packages/store/src/types/auth.types.ts +++ b/src/frontend/packages/store/src/types/auth.types.ts @@ -31,6 +31,8 @@ export interface SessionEndpoint { } export interface SessionDataConfig { enableTechPreview?: boolean; + listMaxSize?: number; + listAllowLoadMaxed?: boolean; } export interface SessionData { endpoints?: SessionEndpoints; diff --git a/src/frontend/packages/store/src/types/pagination.types.ts b/src/frontend/packages/store/src/types/pagination.types.ts index 5a465e16c8..6662b4ac9e 100644 --- a/src/frontend/packages/store/src/types/pagination.types.ts +++ b/src/frontend/packages/store/src/types/pagination.types.ts @@ -26,9 +26,27 @@ export interface PaginationClientPagination { totalResults: number; } +export interface PaginationMaxedState { + /** + * Is the pagination in maxed mode? + * - flattenPagination and flattenPaginationMax is true + * - Initial fetch of entities brought back a total above the allowed IStratosBaseEntityDefinition paginationConfig maxedStateStartAt + * value + * - Pagination notionally now changes from local (has all entities & filtering locally) to non-local (has a single page & + * filtering remotely) + */ + isMaxedMode?: boolean; + /** + * Disregard flattenPaginationMax and ignore isMaxedMode true + */ + ignoreMaxed?: boolean; +} + + export class PaginationEntityState { /** - * For multi action lists, this is used to force a particular entity type. + * For multi action lists, this is used to force a particular entity type. For instance in the service instance wall selecting the option + * to only show user provided service instances */ forcedLocalPage?: number; currentPage = 0; @@ -44,11 +62,7 @@ export class PaginationEntityState { * The pagination key from where we share our values. */ seed?: string; - /** - * Is the pagination state in maxed mode. This means the initial collection contained too many entities too handle, see PaginatedAction - * flattenPagination & flattenPaginationMax - */ - maxedMode?: boolean; + maxedState: PaginationMaxedState; } export function isPaginatedAction(obj: any): PaginatedAction { @@ -66,9 +80,9 @@ export interface PaginatedAction extends BasePaginatedAction, EntityRequestActio */ flattenPagination?: boolean; /* - * The maximum number of entities to fetch. Note - Should be equal or higher than the page size + * When fetching all pages, abort if they exceed the maximum allowed. See IStratosBaseEntityDefinition paginationConfig maxedStateStartAt */ - flattenPaginationMax?: number; + flattenPaginationMax?: boolean; initialParams?: PaginationParam; pageNumber?: number; options?: HttpRequest; diff --git a/src/frontend/packages/store/testing/src/store-test-helper.ts b/src/frontend/packages/store/testing/src/store-test-helper.ts index 9aad5b8ae2..8d4ce58cd0 100644 --- a/src/frontend/packages/store/testing/src/store-test-helper.ts +++ b/src/frontend/packages/store/testing/src/store-test-helper.ts @@ -234,7 +234,8 @@ function getDefaultInitialTestStoreState(): AppState { string: '', items: {} }, - } + }, + maxedState: {} } }, metrics: {}, diff --git a/src/jetstream/default.config.properties b/src/jetstream/default.config.properties index f944a86fb5..c5058a354a 100644 --- a/src/jetstream/default.config.properties +++ b/src/jetstream/default.config.properties @@ -29,6 +29,10 @@ SSO_WHITELIST= # Enable feature in tech preview ENABLE_TECH_PREVIEW=false +# Override the default max list size. When hit we won't fetch all results for the given list +#UI_LIST_MAX_SIZE=600 +# If the max list size is hit allow the user to load all results anyway. Defaults to false +#UI_LIST_ALLOW_LOAD_MAXED=false # User Invites SMTP_FROM_ADDRESS=Stratos diff --git a/src/jetstream/info.go b/src/jetstream/info.go index ecf3d6b7b2..2000460cc5 100644 --- a/src/jetstream/info.go +++ b/src/jetstream/info.go @@ -57,6 +57,8 @@ func (p *portalProxy) getInfo(c echo.Context) (*interfaces.Info, error) { } s.Configuration.TechPreview = p.Config.EnableTechPreview + s.Configuration.ListMaxSize = p.Config.UIListMaxSize + s.Configuration.ListAllowLoadMaxed = p.Config.UIListAllowLoadMaxed // Only add diagnostics information if the user is an admin if uaaUser.Admin { diff --git a/src/jetstream/repository/interfaces/structs.go b/src/jetstream/repository/interfaces/structs.go index 041f2ebea0..ae4d45dcc5 100644 --- a/src/jetstream/repository/interfaces/structs.go +++ b/src/jetstream/repository/interfaces/structs.go @@ -206,7 +206,9 @@ type Info struct { PluginConfig map[string]string `json:"plugin-config,omitempty"` Diagnostics *Diagnostics `json:"diagnostics,omitempty"` Configuration struct { - TechPreview bool `json:"enableTechPreview"` + TechPreview bool `json:"enableTechPreview"` + ListMaxSize int64 `json:"listMaxSize,omitempty"` + ListAllowLoadMaxed bool `json:"listAllowLoadMaxed,omitempty"` } `json:"config"` } @@ -344,6 +346,8 @@ type PortalConfig struct { AuthEndpointType string `configName:"AUTH_ENDPOINT_TYPE"` CookieDomain string `configName:"COOKIE_DOMAIN"` LogLevel string `configName:"LOG_LEVEL"` + UIListMaxSize int64 `configName:"UI_LIST_MAX_SIZE"` + UIListAllowLoadMaxed bool `configName:"UI_LIST_ALLOW_LOAD_MAXED"` CFAdminIdentifier string CloudFoundryInfo *CFInfo HTTPS bool diff --git a/src/test-e2e/application/application-deploy-e2e.spec.ts b/src/test-e2e/application/application-deploy-e2e.spec.ts index ae241e4fa8..994fa95c1b 100644 --- a/src/test-e2e/application/application-deploy-e2e.spec.ts +++ b/src/test-e2e/application/application-deploy-e2e.spec.ts @@ -343,7 +343,7 @@ describe('Application Deploy -', () => { }); it('Should be source step with correct values', () => { - expect(deployApp.header.getTitleText()).toBe(`Redeploy`); + deployApp.header.waitForTitleText('Redeploy'); deployApp.stepper.getStepNames().then(steps => { expect(steps.length).toBe(3);