SUSE/stratos

View on GitHub
src/frontend/packages/cloud-foundry/src/cf-entity-generator.ts

Summary

Maintainability
F
6 days
Test Coverage
import { Compiler, Injector } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { entityFetchedWithoutError } from '@stratosui/store';
import { combineLatest, Observable, of } from 'rxjs';
import { first, map } from 'rxjs/operators';

import { BaseEndpointAuth } from '../../core/src/core/endpoint-auth';
import { urlValidationExpression } from '../../core/src/core/utils.service';
import { AppState, GeneralEntityAppState } from '../../store/src/app-state';
import {
  StratosBaseCatalogEntity,
  StratosCatalogEndpointEntity,
  StratosCatalogEntity,
} from '../../store/src/entity-catalog/entity-catalog-entity/entity-catalog-entity';
import {
  EndpointHealthCheck,
  IStratosEntityDefinition,
  StratosEndpointExtensionDefinition,
} from '../../store/src/entity-catalog/entity-catalog.types';
import {
  JetstreamError,
} from '../../store/src/entity-request-pipeline/entity-request-base-handlers/handle-multi-endpoints.pipe';
import { ActionDispatcher, JetstreamResponse } from '../../store/src/entity-request-pipeline/entity-request-pipeline.types';
import { EntitySchema } from '../../store/src/helpers/entity-schema';
import { metricEntityType } from '../../store/src/helpers/stratos-entity-factory';
import { RequestInfoState } from '../../store/src/reducers/api-request-reducer/types';
import { selectSessionData } from '../../store/src/reducers/auth.reducer';
import { APIResource, EntityInfo } from '../../store/src/types/api.types';
import { PaginatedAction, PaginationEntityState } from '../../store/src/types/pagination.types';
import { ICFAction } from '../../store/src/types/request.types';
import { IFavoriteMetadata } from '../../store/src/types/user-favorites.types';
import { CfValidateEntitiesStart } from './actions/relations-actions';
import {
  IService,
  IServiceBinding,
  IServiceBroker,
  IServiceInstance,
  IServicePlan,
  IServicePlanVisibility,
  IUserProvidedServiceInstance,
} from './cf-api-svc.types';
import {
  CfEvent,
  IApp,
  IAppSummary,
  IBuildpack,
  ICfV2Info,
  IDomain,
  IFeatureFlag,
  IOrganization,
  IOrgQuotaDefinition,
  IPrivateDomain,
  IRoute,
  ISecurityGroup,
  ISpace,
  ISpaceQuotaDefinition,
  IStack,
} from './cf-api.types';
import { cfEntityCatalog } from './cf-entity-catalog';
import { cfEntityFactory } from './cf-entity-factory';
import {
  appEnvVarsEntityType,
  applicationEntityType,
  appStatsEntityType,
  appSummaryEntityType,
  buildpackEntityType,
  cfEventEntityType,
  cfInfoEntityType,
  cfUserEntityType,
  domainEntityType,
  featureFlagEntityType,
  organizationEntityType,
  privateDomainsEntityType,
  quotaDefinitionEntityType,
  routeEntityType,
  securityGroupEntityType,
  serviceBindingEntityType,
  serviceBindingNoBindingsEntityType,
  serviceBrokerEntityType,
  serviceEntityType,
  serviceInstancesEntityType,
  serviceInstancesWithNoBindingsEntityType,
  serviceInstancesWithSpaceEntityType,
  servicePlanEntityType,
  servicePlanVisibilityEntityType,
  spaceEntityType,
  spaceQuotaEntityType,
  spaceWithOrgEntityType,
  stackEntityType,
  userProvidedServiceInstanceEntityType,
} from './cf-entity-types';
import { CfErrorResponse, getCfError } from './cf-error-helpers';
import { ISpaceFavMetadata } from './cf-metadata-types';
import { CF_ENDPOINT_TYPE } from './cf-types';
import {
  AppEnvVarActionBuilders,
  appEnvVarActionBuilders,
} from './entity-action-builders/application-env-var.action-builders';
import { AppStatsActionBuilders, appStatsActionBuilders } from './entity-action-builders/application-stats.action-builders';
import {
  AppSummaryActionBuilders,
  appSummaryActionBuilders,
} from './entity-action-builders/application-summary.action-builders';
import { applicationActionBuilder, ApplicationActionBuilders } from './entity-action-builders/application.action-builders';
import { BuildpackActionBuilders, buildpackActionBuilders } from './entity-action-builders/buildpack.action-builders';
import { CfEventActionBuilders, cfEventActionBuilders } from './entity-action-builders/cf-event.action-builders';
import {
  CfInfoDefinitionActionBuilders,
  cfInfoDefinitionActionBuilders,
} from './entity-action-builders/cf-info.action-builders';
import { DomainActionBuilders, domainActionBuilders } from './entity-action-builders/domin.action-builder';
import { FeatureFlagActionBuilders, featureFlagActionBuilders } from './entity-action-builders/feature-flag.action-builder';
import {
  OrganizationActionBuilders,
  organizationActionBuilders,
} from './entity-action-builders/organization.action-builders';
import {
  QuotaDefinitionActionBuilder,
  quotaDefinitionActionBuilder,
} from './entity-action-builders/quota-definition.action-builders';
import { RoutesActionBuilders, routesActionBuilders } from './entity-action-builders/routes.action-builder';
import { SecurityGroupBuilders, securityGroupBuilders } from './entity-action-builders/security-groups.action-builder';
import {
  ServiceBindingActionBuilders,
  serviceBindingActionBuilders,
} from './entity-action-builders/service-binding.action-builders';
import {
  ServiceBrokerActionBuilders,
  serviceBrokerActionBuilders,
} from './entity-action-builders/service-broker.entity-builders';
import {
  ServiceInstanceActionBuilders,
  serviceInstanceActionBuilders,
} from './entity-action-builders/service-instance.action.builders';
import {
  ServicePlanVisibilityActionBuilders,
  servicePlanVisibilityActionBuilders,
} from './entity-action-builders/service-plan-visibility.action-builders';
import { ServicePlanActionBuilders, servicePlanActionBuilders } from './entity-action-builders/service-plan.action-builders';
import { ServiceActionBuilders, serviceActionBuilders } from './entity-action-builders/service.entity-builders';
import {
  SpaceQuotaDefinitionActionBuilders,
  spaceQuotaDefinitionActionBuilders,
} from './entity-action-builders/space-quota.action-builders';
import { SpaceActionBuilders, spaceActionBuilders } from './entity-action-builders/space.action-builders';
import { StackActionBuilders, stackActionBuilders } from './entity-action-builders/stack-action-builders';
import {
  UserProvidedServiceActionBuilder,
  userProvidedServiceActionBuilder,
} from './entity-action-builders/user-provided-service.action-builders';
import { UserActionBuilders, userActionBuilders } from './entity-action-builders/user.action-builders';
import { addCfQParams, addCfRelationParams } from './entity-relations/cf-entity-relations.getters';
import { populatePaginationFromParent } from './entity-relations/entity-relations';
import { isEntityInlineParentAction } from './entity-relations/entity-relations.types';
import { CfEndpointDetailsComponent } from './shared/components/cf-endpoint-details/cf-endpoint-details.component';
import { updateApplicationRoutesReducer } from './store/reducers/application-route.reducer';
import { cfUserReducer, endpointDisconnectUserReducer, userSpaceOrgReducer } from './store/reducers/cf-users.reducer';
import { currentCfUserRolesReducer } from './store/reducers/current-cf-user-roles-reducer/current-cf-user-roles.reducer';
import { endpointDisconnectRemoveEntitiesReducer } from './store/reducers/endpoint-disconnect-application.reducer';
import { updateOrganizationQuotaReducer } from './store/reducers/organization-quota.reducer';
import { updateOrganizationSpaceReducer } from './store/reducers/organization-space.reducer';
import { routeReducer, updateAppSummaryRoutesReducer } from './store/reducers/routes.reducer';
import { serviceInstanceReducer } from './store/reducers/service-instance.reducer';
import { updateSpaceQuotaReducer } from './store/reducers/space-quota.reducer';
import { AppStat } from './store/types/app-metadata.types';
import { CfAPIResource, CFResponse } from './store/types/cf-api.types';
import { CfUser } from './store/types/cf-user.types';
import { cfUserRolesFetch } from './user-permissions/cf-user-roles-fetch';

function safePopulatePaginationFromParent(store: Store<GeneralEntityAppState>, action: PaginatedAction): Observable<Action> {
  return populatePaginationFromParent(store, action).pipe(
    map(newAction => newAction || action)
  );
}

function getPaginationCompareString(paginationEntity: PaginationEntityState) {
  if (!paginationEntity) {
    return '';
  }
  let params = '';
  if (paginationEntity.params) {
    params = JSON.stringify(paginationEntity.params);
  }
  // paginationEntity.totalResults included to ensure we cover the 'ResetPagination' case, for instance after AddParam
  return paginationEntity.totalResults + paginationEntity.currentPage + params + paginationEntity.pageCount;
}

function shouldValidate(action: ICFAction, isValidated: boolean, entityInfo: RequestInfoState) {
  // Validate if..
  // 1) The action is the correct type
  const parentAction = isEntityInlineParentAction(action);
  if (!parentAction) {
    return false;
  }
  // 2) We have basic request info
  // 3) The action states it should not be skipped
  // 4) It's already been validated
  // 5) There are actual relations to validate
  if (!entityInfo || action.skipValidation || isValidated || parentAction.includeRelations.length === 0) {
    return false;
  }
  // 6) The entity isn't in the process of being updated
  return !entityInfo.fetching &&
    !entityInfo.error &&
    !entityInfo.deleting.busy &&
    !entityInfo.deleting.deleted &&
    // This is required to ensure that we don't continue trying to fetch missing relations when we're already fetching missing relations
    !Object.keys(entityInfo.updating).find(key => entityInfo.updating[key].busy);
}

export interface CFBasePipelineRequestActionMeta {
  /**
   * Define a set of children that a cf entity should have, for instance organisation --> space, application --> space --> organisation
   */
  includeRelations?: string[];
  /**
   * If relations, as described in `includeRelations` are missing, should they be fetched?
   */
  populateMissing?: boolean;
  /**
   * Only applicable to collections
   */
  flatten?: boolean;
}

function cfShortcuts(id: string) {
  return [
    {
      title: 'View Organizations',
      link: ['/cloud-foundry', id, 'organizations'],
      icon: 'organization',
      iconFont: 'stratos-icons'
    },
    {
      title: 'View Applications',
      link: ['/applications', id],
      icon: 'apps'
    },
    {
      title: 'Deploy an Application',
      link: ['/applications', 'new', id],
      icon: 'publish'
    },
    {
      title: 'View Cloud Foundry Info',
      link: ['/cloud-foundry', id],
      icon: 'cloud_foundry',
      iconFont: 'stratos-icons'
    },
  ];
}

export function generateCFEntities(): StratosBaseCatalogEntity[] {
  const endpointDefinition: StratosEndpointExtensionDefinition = {
    urlValidationRegexString: urlValidationExpression,
    type: CF_ENDPOINT_TYPE,
    label: 'Cloud Foundry',
    labelPlural: 'Cloud Foundry',
    icon: 'cloud_foundry',
    iconFont: 'stratos-icons',
    logoUrl: '/core/assets/endpoint-icons/cloudfoundry.png',
    authTypes: [BaseEndpointAuth.UsernamePassword, BaseEndpointAuth.SSO],
    homeCard: {
      component: (compiler: Compiler, injector: Injector) => import('./features/home/cfhome-card/cfhome-card.module').then(m => {
        return compiler.compileModuleAndAllComponentsAsync(m.CFHomeCardModule).then(cm => {
          const mod = cm.ngModuleFactory.create(injector);
          return mod.instance.createHomeCard(mod.componentFactoryResolver);
        });
      }),
      shortcuts: cfShortcuts,
      fullView: false,
    },
    listDetailsComponent: CfEndpointDetailsComponent,
    renderPriority: 1,
    healthCheck: new EndpointHealthCheck(CF_ENDPOINT_TYPE, (endpoint) => cfEntityCatalog.cfInfo.api.get(endpoint.guid)),
    getEndpointIdFromEntity: (entity: CfAPIResource) => entity.entity.cfGuid,
    globalPreRequest: (request, action) => {
      return addCfRelationParams(request, action);
    },
    globalPrePaginationRequest: (request, action, catalogEntity, appState) => {
      const rWithRelations = addCfRelationParams(request, action);
      return addCfQParams(rWithRelations, action, catalogEntity, appState);
    },
    globalSuccessfulRequestDataMapper: (data, endpointGuid, guid) => {
      if (data) {
        if (data.entity) {
          data.entity.cfGuid = endpointGuid;
          data.entity.guid = guid;
        } else {
          data.cfGuid = endpointGuid;
          data.guid = guid;
        }
      }
      return data;
    },
    globalErrorMessageHandler: (errors: JetstreamError<CfErrorResponse>[]) => {
      if (!errors || errors.length === 0) {
        return 'No errors in response';
      }

      if (errors.length === 1) {
        return getCfError(errors[0].jetstreamErrorResponse);
      }

      return errors.reduce((message, error) => {
        message += `\n${getCfError(error.jetstreamErrorResponse)}`;
        return message;
      }, 'Multiple Cloud Foundry Errors. ');
    },
    entityEmitHandler: (action: ICFAction, dispatcher: ActionDispatcher) => {
      let validated = false;
      return (entityInfo: EntityInfo) => {
        if (!entityInfo || entityInfo.entity) {
          if (shouldValidate(action, validated, entityInfo.entityRequestInfo)) {
            validated = true;
            dispatcher(new CfValidateEntitiesStart(
              action,
              [action.guid]
            ));
          }
        }
      };
    },
    entitiesEmitHandler: (action: PaginatedAction | PaginatedAction[], dispatcher: ActionDispatcher) => {
      let lastValidationFootprint: string;
      const actionsArray = Array.isArray(action) ? action : [action];
      return (state: PaginationEntityState) => {
        const newValidationFootprint = getPaginationCompareString(state);
        if (lastValidationFootprint !== newValidationFootprint) {
          lastValidationFootprint = newValidationFootprint;
          actionsArray.forEach(actionFromArray => dispatcher(new CfValidateEntitiesStart(
            actionFromArray,
            state.ids[actionFromArray.__forcedPageNumber__ || state.currentPage]
          )));
        }
      };
    },
    entitiesFetchHandler: (store: Store<GeneralEntityAppState>, actions: PaginatedAction[]) => () => {
      combineLatest(actions.map(action => safePopulatePaginationFromParent(store, action))).pipe(
        first(),
      ).subscribe(newActions => newActions.forEach(newAction => store.dispatch(newAction)));
    },
    paginationConfig: {
      getEntitiesFromResponse: (response: CFResponse) => response.resources,
      getTotalPages: (responseWithPages: JetstreamResponse<CFResponse | CFResponse[]>) =>
        // Input is keyed per endpoint. Value per endpoint can either be a response or a number of responses (one per page)
        Object.values(responseWithPages).reduce((max, response: CFResponse | CFResponse[]) => {
          const resp = (response[0] || response);
          return max > resp.total_pages ? max : resp.total_pages;
        }, 0),
      getTotalEntities: (responseWithPages: JetstreamResponse<CFResponse | CFResponse[]>) =>
        Object.values(responseWithPages).reduce((all, response: CFResponse | CFResponse[]) => {
          return all + (response[0] || response).total_results;
        }, 0),
      getPaginationParameters: (page: number) => ({ page: page + '' }),
      canIgnoreMaxedState: (store: Store<AppState>) => {
        // Does entity type support? Yes
        // Does BE support ignore?
        return store.select(selectSessionData()).pipe(
          map(sessionData => !!sessionData.config.listAllowLoadMaxed)
        );
      },
      maxedStateStartAt: (store: Store<AppState>, 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)
        );
      },
    },
    userRolesFetch: cfUserRolesFetch,
    userRolesReducer: currentCfUserRolesReducer
  };
  return [
    generateCfEndpointEntity(endpointDefinition),
    generateCfApplicationEntity(endpointDefinition),
    generateCfSpaceEntity(endpointDefinition),
    generateCfOrgEntity(endpointDefinition),
    generateFeatureFlagEntity(endpointDefinition),
    generateStackEntity(endpointDefinition),
    generateRouteEntity(endpointDefinition),
    generateEventEntity(endpointDefinition),
    generateCFDomainEntity(endpointDefinition),
    generateCFUserEntity(endpointDefinition),
    generateCFServiceInstanceEntity(endpointDefinition),
    generateCFServicePlanEntity(endpointDefinition),
    generateCFServiceEntity(endpointDefinition),
    generateCFServiceBindingEntity(endpointDefinition),
    generateCFSecurityGroupEntity(endpointDefinition),
    generateCFServicePlanVisibilityEntity(endpointDefinition),
    generateCFServiceBrokerEntity(endpointDefinition),
    generateCFBuildPackEntity(endpointDefinition),
    generateCFAppStatsEntity(endpointDefinition),
    generateCFUserProvidedServiceInstanceEntity(endpointDefinition),
    generateCFInfoEntity(endpointDefinition),
    generateCFPrivateDomainEntity(endpointDefinition),
    generateCFSpaceQuotaEntity(endpointDefinition),
    generateCFAppSummaryEntity(endpointDefinition),
    generateCFAppEnvVarEntity(endpointDefinition),
    generateCFQuotaDefinitionEntity(endpointDefinition),
    generateCFMetrics(endpointDefinition)
  ];
}

function generateCFQuotaDefinitionEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: quotaDefinitionEntityType,
    schema: cfEntityFactory(quotaDefinitionEntityType),
    endpoint: endpointDefinition,
    label: 'Organization Quota',
    labelPlural: 'Organization Quotas',
  };
  cfEntityCatalog.quotaDefinition = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IOrgQuotaDefinition>,
    QuotaDefinitionActionBuilder
  >(definition, {
    dataReducers: [
      endpointDisconnectRemoveEntitiesReducer()
    ],
    actionBuilders: quotaDefinitionActionBuilder
  });
  return cfEntityCatalog.quotaDefinition;
}

function generateCFAppEnvVarEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition<any, APIResource, any> = {
    type: appEnvVarsEntityType,
    schema: cfEntityFactory(appEnvVarsEntityType),
    endpoint: endpointDefinition,
    paginationConfig: {
      getEntitiesFromResponse: (response) => response,
      getTotalPages: (responses: JetstreamResponse<CFResponse>) => Object.values(responses).length,
      getTotalEntities: (responses: JetstreamResponse<CFResponse>) => 1,
      getPaginationParameters: (page: number) => ({ page: '1' }),
      canIgnoreMaxedState: () => of(false),
      maxedStateStartAt: () => of(null),
    },
    successfulRequestDataMapper: (data, endpointGuid, guid, entityType, endpointType, action) => {
      return {
        entity: {
          ...(data || {}),
          cfGuid: endpointGuid
        },
        metadata: {
          guid: action.guid,
          created_at: '',
          updated_at: '',
          url: ''
        }
      };
    },
    label: 'App Env Var',
    labelPlural: 'App Env Vars',
  };
  cfEntityCatalog.appEnvVar = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource,
    AppEnvVarActionBuilders,
    AppEnvVarActionBuilders
  >(definition, {
    dataReducers: [
      endpointDisconnectRemoveEntitiesReducer()
    ],
    actionBuilders: appEnvVarActionBuilders,
    entityBuilder: {
      getMetadata: ent => ({
        name: `Application environment variables (${ent.metadata.guid}).`,
      }),
      getGuid: entity => entity.metadata.guid
    },
  });
  return cfEntityCatalog.appEnvVar;
}

function generateCFAppSummaryEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: appSummaryEntityType,
    schema: cfEntityFactory(appSummaryEntityType),
    endpoint: endpointDefinition,
    label: 'App Summary',
    labelPlural: 'App Summaries',
  };
  cfEntityCatalog.appSummary = new StratosCatalogEntity<IFavoriteMetadata, IAppSummary, AppSummaryActionBuilders>(definition, {
    dataReducers: [
      updateAppSummaryRoutesReducer,
      endpointDisconnectRemoveEntitiesReducer()
    ],
    actionBuilders: appSummaryActionBuilders,
    entityBuilder: {
      getMetadata: ent => ({
        name: ent.name,
      }),
      getGuid: entity => entity.guid,
    }
  });
  return cfEntityCatalog.appSummary;
}

function generateCFSpaceQuotaEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: spaceQuotaEntityType,
    schema: cfEntityFactory(spaceQuotaEntityType),
    endpoint: endpointDefinition,
    label: 'Space Quota',
    labelPlural: 'Space Quotas',
  };
  cfEntityCatalog.spaceQuota = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<ISpaceQuotaDefinition>,
    SpaceQuotaDefinitionActionBuilders>(definition, {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: spaceQuotaDefinitionActionBuilders
    });
  return cfEntityCatalog.spaceQuota;
}

function generateCFPrivateDomainEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: privateDomainsEntityType,
    schema: cfEntityFactory(privateDomainsEntityType),
    endpoint: endpointDefinition,
    label: 'Private Domain',
    labelPlural: 'Private Domains',
  };
  cfEntityCatalog.privateDomain = new StratosCatalogEntity<IFavoriteMetadata, APIResource<IPrivateDomain>>(definition, {
    dataReducers: [
      endpointDisconnectRemoveEntitiesReducer()
    ],
  });
  return cfEntityCatalog.privateDomain;
}

function generateCFInfoEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const cfInfoDefinition: IStratosEntityDefinition = {
    type: cfInfoEntityType,
    schema: cfEntityFactory(cfInfoEntityType),
    label: 'Cloud Foundry Info',
    labelPlural: 'Cloud Foundry Infos',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.cfInfo = new StratosCatalogEntity<IFavoriteMetadata, APIResource<ICfV2Info>, CfInfoDefinitionActionBuilders>(
    cfInfoDefinition,
    {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: cfInfoDefinitionActionBuilders,
      entityBuilder: {
        getMetadata: info => ({
          name: info.entity.name,
        }),
        getGuid: entity => entity.metadata.guid
      }
    }
  );
  return cfEntityCatalog.cfInfo;
}

function generateCFUserProvidedServiceInstanceEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: userProvidedServiceInstanceEntityType,
    schema: cfEntityFactory(userProvidedServiceInstanceEntityType),
    label: 'User Provided Service Instance',
    labelPlural: 'User Provided Service Instances',
    endpoint: endpointDefinition,
  };
  cfEntityCatalog.userProvidedService = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IUserProvidedServiceInstance>,
    UserProvidedServiceActionBuilder
  >(
    definition,
    {
      actionBuilders: userProvidedServiceActionBuilder,
      dataReducers: [
        serviceInstanceReducer,
        endpointDisconnectRemoveEntitiesReducer()
      ],
      entityBuilder: {
        getMetadata: ent => ({
          name: ent.entity.name,
        }),
        getGuid: entity => entity.metadata.guid
      },
    }
  );
  return cfEntityCatalog.userProvidedService;
}

function generateCFAppStatsEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition<any, AppStat> = {
    type: appStatsEntityType,
    schema: cfEntityFactory(appStatsEntityType),
    endpoint: endpointDefinition,
    paginationConfig: {
      getEntitiesFromResponse: (response) => {
        return Object.keys(response).map(key => {
          const stat = response[key];
          stat.guid = key;
          return stat;
        });
      },
      getTotalPages: (responses: JetstreamResponse) => Object.values(responses).length,
      getTotalEntities: (responses: JetstreamResponse) => Object.values(responses).reduce((count, response) => {
        return count + Object.keys(response).length;
      }, 0),
      getPaginationParameters: (page: number) => ({ page: page + '' }),
      canIgnoreMaxedState: () => of(false),
      maxedStateStartAt: () => of(null),
    },
    successfulRequestDataMapper: (data, endpointGuid, guid, entityType, endpointType, action) => {
      if (data) {
        return {
          ...data,
          cfGuid: endpointGuid,
          guid: `${action.guid}-${guid}`
        };
      }
      return data;
    },
  };
  cfEntityCatalog.appStats = new StratosCatalogEntity<IFavoriteMetadata, AppStat, AppStatsActionBuilders>(definition, {
    dataReducers: [
      endpointDisconnectRemoveEntitiesReducer()
    ],
    actionBuilders: appStatsActionBuilders,
    entityBuilder: {
      getMetadata: ent => ({
        name: ent.guid,
      }),
      getGuid: entity => entity.guid
    }
  });
  return cfEntityCatalog.appStats;
}

function generateCFBuildPackEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: buildpackEntityType,
    schema: cfEntityFactory(buildpackEntityType),
    endpoint: endpointDefinition
  };
  cfEntityCatalog.buildPack = new StratosCatalogEntity<IFavoriteMetadata, APIResource<IBuildpack>, BuildpackActionBuilders>(definition, {
    dataReducers: [
      endpointDisconnectRemoveEntitiesReducer()
    ],
    actionBuilders: buildpackActionBuilders
  });
  return cfEntityCatalog.buildPack;
}

function generateCFServiceBrokerEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: serviceBrokerEntityType,
    schema: cfEntityFactory(serviceBrokerEntityType),
    endpoint: endpointDefinition
  };
  cfEntityCatalog.serviceBroker = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IServiceBroker>,
    ServiceBrokerActionBuilders>(definition, {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: serviceBrokerActionBuilders
    });
  return cfEntityCatalog.serviceBroker;
}

function generateCFServicePlanVisibilityEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: servicePlanVisibilityEntityType,
    schema: cfEntityFactory(servicePlanVisibilityEntityType),
    endpoint: endpointDefinition
  };
  cfEntityCatalog.servicePlanVisibility = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IServicePlanVisibility>,
    ServicePlanVisibilityActionBuilders
  >(definition, {
    dataReducers: [
      endpointDisconnectRemoveEntitiesReducer()
    ],
    actionBuilders: servicePlanVisibilityActionBuilders
  });
  return cfEntityCatalog.servicePlanVisibility;
}

function generateCFSecurityGroupEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: securityGroupEntityType,
    schema: cfEntityFactory(securityGroupEntityType),
    label: 'Security Group',
    labelPlural: 'Security Groups',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.securityGroup = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<ISecurityGroup>,
    SecurityGroupBuilders>(definition, {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: securityGroupBuilders
    });
  return cfEntityCatalog.securityGroup;
}

function generateCFServiceBindingEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: serviceBindingEntityType,
    schema: {
      default: cfEntityFactory(serviceBindingEntityType),
      [serviceBindingNoBindingsEntityType]: cfEntityFactory(serviceBindingNoBindingsEntityType)
    },
    label: 'Service Binding',
    labelPlural: 'Service Bindings',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.serviceBinding = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IServiceBinding>,
    ServiceBindingActionBuilders
  >(
    definition,
    {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: serviceBindingActionBuilders,
      entityBuilder: {
        getMetadata: ent => ({
          name: ent.metadata.guid,
        }),
        getGuid: entity => entity.metadata.guid
      }
    }
  );
  return cfEntityCatalog.serviceBinding;
}

function generateCFServiceEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: serviceEntityType,
    schema: cfEntityFactory(serviceEntityType),
    label: 'Service',
    labelPlural: 'Services',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.service = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IService>,
    ServiceActionBuilders
  >(
    definition,
    {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: serviceActionBuilders,
      entityBuilder: {
        getMetadata: ent => ({
          name: ent.entity.label,
        }),
        getGuid: entity => entity.metadata.guid
      },
    }
  );
  return cfEntityCatalog.service;
}

function generateCFServicePlanEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: servicePlanEntityType,
    schema: cfEntityFactory(servicePlanEntityType),
    label: 'Service Plan',
    labelPlural: 'Service Plans',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.servicePlan = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IServicePlan>,
    ServicePlanActionBuilders
  >(
    definition,
    {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: servicePlanActionBuilders,
      entityBuilder: {
        getMetadata: ent => ({
          name: ent.entity.name,
        }),
        getGuid: entity => entity.metadata.guid
      }
    }
  );
  return cfEntityCatalog.servicePlan;
}

function generateCFServiceInstanceEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: serviceInstancesEntityType,
    schema: {
      default: cfEntityFactory(serviceInstancesEntityType),
      [serviceInstancesWithSpaceEntityType]: cfEntityFactory(serviceInstancesWithSpaceEntityType),
      [serviceInstancesWithNoBindingsEntityType]: cfEntityFactory(serviceInstancesWithNoBindingsEntityType),
    },
    label: 'Marketplace Service Instance',
    labelPlural: 'Marketplace Service Instances',
    endpoint: endpointDefinition,
  };
  cfEntityCatalog.serviceInstance = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IServiceInstance>,
    ServiceInstanceActionBuilders
  >(
    definition,
    {
      dataReducers: [
        serviceInstanceReducer,
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: serviceInstanceActionBuilders,
      entityBuilder: {
        getMetadata: ent => ({
          name: ent.entity.name,
        }),
        getGuid: entity => entity.metadata.guid
      }
    }
  );
  return cfEntityCatalog.serviceInstance;
}

function generateCFUserEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: cfUserEntityType,
    schema: cfEntityFactory(cfUserEntityType),
    label: 'User',
    labelPlural: 'Users',
    endpoint: endpointDefinition,
  };
  cfEntityCatalog.user = new StratosCatalogEntity<IFavoriteMetadata, APIResource<CfUser>, UserActionBuilders>(
    definition,
    {
      actionBuilders: userActionBuilders,
      dataReducers: [cfUserReducer, endpointDisconnectUserReducer],
      entityBuilder: {
        getMetadata: ent => ({
          name: ent.entity.username || ent.entity.guid || ent.metadata.guid,
        }),
        getGuid: entity => entity.metadata.guid
      }
    }
  );
  return cfEntityCatalog.user;
}

function generateCFDomainEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: domainEntityType,
    schema: cfEntityFactory(domainEntityType),
    label: 'Domain',
    labelPlural: 'Domains',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.domain = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IDomain>,
    DomainActionBuilders
  >(
    definition,
    {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: domainActionBuilders,
      entityBuilder: {
        getMetadata: ent => ({
          name: ent.entity.name,
        }),
        getGuid: entity => entity.metadata.guid
      }
    }
  );
  return cfEntityCatalog.domain;
}



function generateEventEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: cfEventEntityType,
    schema: cfEntityFactory(cfEventEntityType),
    label: 'Event',
    labelPlural: 'Events',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.event = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<CfEvent>,
    CfEventActionBuilders>(
      definition,
      {
        dataReducers: [
          endpointDisconnectRemoveEntitiesReducer()
        ],
        actionBuilders: cfEventActionBuilders,
        entityBuilder: {
          getMetadata: event => {
            return {
              name: event.metadata.guid,
            };
          },
          getGuid: entity => entity.metadata.guid
        }
      }
    );
  return cfEntityCatalog.event;
}

function generateRouteEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: routeEntityType,
    schema: cfEntityFactory(routeEntityType),
    label: 'Application Route',
    labelPlural: 'Application Routes',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.route = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IRoute>,
    RoutesActionBuilders
  >(
    definition,
    {
      actionBuilders: routesActionBuilders,
      dataReducers: [
        routeReducer,
        endpointDisconnectRemoveEntitiesReducer()
      ],
      entityBuilder: {
        getMetadata: app => ({
          name: app.entity.domain_url,
        }),
        getGuid: entity => entity.metadata.guid
      }
    }
  );
  return cfEntityCatalog.route;
}

function generateStackEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: stackEntityType,
    schema: cfEntityFactory(stackEntityType),
    label: 'Stack',
    labelPlural: 'Stacks',
    endpoint: endpointDefinition
  };
  cfEntityCatalog.stack = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IStack>,
    StackActionBuilders
  >(
    definition,
    {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer()
      ],
      actionBuilders: stackActionBuilders,
      entityBuilder: {
        getMetadata: app => ({
          name: app.entity.name,
        }),
        getGuid: entity => entity.metadata.guid
      }
    }
  );
  return cfEntityCatalog.stack;
}

function generateFeatureFlagEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const featureFlagDefinition: IStratosEntityDefinition = {
    type: featureFlagEntityType,
    schema: cfEntityFactory(featureFlagEntityType),
    label: 'Feature Flag',
    labelPlural: 'Feature Flags',
    endpoint: endpointDefinition,
    successfulRequestDataMapper: (
      response,
      endpointGuid
    ) => {
      return {
        ...response,
        guid: `${endpointGuid}-${response.name}`
      };
    },
    paginationConfig: {
      getEntitiesFromResponse: (response) => {
        return response;
      },
      getTotalPages: (responses: JetstreamResponse) => 1,
      getTotalEntities: (responses: JetstreamResponse) => responses.length,
      getPaginationParameters: (page: number) => ({ page: page + '' }),
      canIgnoreMaxedState: () => of(false),
      maxedStateStartAt: () => of(null),
    }
  };
  cfEntityCatalog.featureFlag = new StratosCatalogEntity<
    IFavoriteMetadata,
    IFeatureFlag,
    FeatureFlagActionBuilders>(
      featureFlagDefinition,
      {
        dataReducers: [
          endpointDisconnectRemoveEntitiesReducer()
        ],
        actionBuilders: featureFlagActionBuilders,
        entityBuilder: {
          getMetadata: ff => ({
            name: ff.name,
          }),
          getGuid: entity => entity.guid,
        }
      }
    );
  return cfEntityCatalog.featureFlag;
}

function generateCfEndpointEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  cfEntityCatalog.cfEndpoint = new StratosCatalogEndpointEntity(
    endpointDefinition,
    favorite => `/cloud-foundry/${favorite.endpointId}`
  );
  return cfEntityCatalog.cfEndpoint;
}

function generateCfApplicationEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const applicationDefinition: IStratosEntityDefinition<EntitySchema, APIResource<IApp>> = {
    type: applicationEntityType,
    schema: cfEntityFactory(applicationEntityType),
    label: 'Application',
    labelPlural: 'Applications',
    endpoint: endpointDefinition,
    icon: 'apps',
    tableConfig: {
      rowBuilders: [
        ['Name', (entity) => entity.entity.name],
        ['Created', (entity) => entity.metadata.created_at]
      ]
    }
  };

  cfEntityCatalog.application = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IApp>,
    ApplicationActionBuilders
  >(
    applicationDefinition,
    {
      dataReducers: [
        updateApplicationRoutesReducer(),
        endpointDisconnectRemoveEntitiesReducer()
      ],
      entityBuilder: {
        getMetadata: app => ({
          name: app.entity.name,
        }),
        getLink: favorite => `/applications/${favorite.endpointId}/${favorite.entityId}/summary`,
        getGuid: entity => entity.metadata.guid,
        getIsValid: (fav) => cfEntityCatalog.application.api.get(fav.entityId, fav.endpointId, {}).pipe(entityFetchedWithoutError())
      },
      actionBuilders: applicationActionBuilder
    },
  );
  return cfEntityCatalog.application;
}

function generateCfSpaceEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const spaceDefinition: IStratosEntityDefinition = {
    type: spaceEntityType,
    schema: {
      default: cfEntityFactory(spaceEntityType),
      [spaceWithOrgEntityType]: cfEntityFactory(spaceWithOrgEntityType)
    },
    label: 'Space',
    labelPlural: 'Spaces',
    endpoint: endpointDefinition,
    icon: 'virtual_space',
    iconFont: 'stratos-icons'
  };
  cfEntityCatalog.space = new StratosCatalogEntity<ISpaceFavMetadata, APIResource<ISpace>, SpaceActionBuilders>(
    spaceDefinition,
    {
      actionBuilders: spaceActionBuilders,
      dataReducers: [
        updateSpaceQuotaReducer,
        endpointDisconnectRemoveEntitiesReducer(),
        userSpaceOrgReducer(true)
      ],
      entityBuilder: {
        getMetadata: space => ({
          orgGuid: space.entity.organization_guid ? space.entity.organization_guid : space.entity.organization.metadata.guid,
          name: space.entity.name,
        }),
        getLink: favorite => `/cloud-foundry/${favorite.endpointId}/organizations/${favorite.metadata.orgGuid}/spaces/${favorite.entityId}/summary`,
        getGuid: entity => entity.metadata.guid,
        getIsValid: (fav) => cfEntityCatalog.space.api.get(fav.entityId, fav.endpointId).pipe(entityFetchedWithoutError())
      }
    }
  );
  return cfEntityCatalog.space;
}

function generateCfOrgEntity(endpointDefinition: StratosEndpointExtensionDefinition) {
  const orgDefinition: IStratosEntityDefinition = {
    type: organizationEntityType,
    schema: cfEntityFactory(organizationEntityType),
    label: 'Organization',
    labelPlural: 'Organizations',
    endpoint: endpointDefinition,
    icon: 'organization',
    iconFont: 'stratos-icons'
  };
  cfEntityCatalog.org = new StratosCatalogEntity<
    IFavoriteMetadata,
    APIResource<IOrganization>,
    OrganizationActionBuilders
  >(
    orgDefinition,
    {
      actionBuilders: organizationActionBuilders,
      dataReducers: [
        updateOrganizationQuotaReducer,
        updateOrganizationSpaceReducer(),
        endpointDisconnectRemoveEntitiesReducer(),
        userSpaceOrgReducer(false)
      ],
      entityBuilder: {
        getMetadata: org => ({
          name: org.entity.name,
        }),
        getLink: favorite => `/cloud-foundry/${favorite.endpointId}/organizations/${favorite.entityId}`,
        getGuid: entity => entity.metadata.guid,
        getIsValid: (favorite) => cfEntityCatalog.org.api.get(favorite.entityId, favorite.endpointId, {}).pipe(entityFetchedWithoutError())
      }
    }
  );
  return cfEntityCatalog.org;
}

function generateCFMetrics(endpointDefinition: StratosEndpointExtensionDefinition) {
  const definition: IStratosEntityDefinition = {
    type: metricEntityType,
    schema: cfEntityFactory(metricEntityType),
    label: 'CF Metric',
    labelPlural: 'CF Metrics',
    endpoint: endpointDefinition,
  };
  cfEntityCatalog.metric = new StratosCatalogEntity<IFavoriteMetadata>(
    definition,
    {
      dataReducers: [
        endpointDisconnectRemoveEntitiesReducer(),
      ],
    }
  );
  return cfEntityCatalog.metric;
}