cloudfoundry-incubator/stratos

View on GitHub
src/frontend/packages/store/src/entity-catalog/entity-catalog.types.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { Compiler, ComponentFactory, Injector } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';

import { HomePageEndpointCard } from '../../../core/src/features/home/home.types';
import { IListAction } from '../../../core/src/shared/components/list/list.component.types';
import { AppState, GeneralEntityAppState } from '../app-state';
import {
  ApiErrorMessageHandler,
  EntitiesFetchHandler,
  EntitiesInfoHandler,
  EntityFetchHandler,
  EntityInfoHandler,
  EntityUserRolesFetch,
  EntityUserRolesReducer,
  PreApiRequest,
  PrePaginationApiRequest,
  SuccessfulApiResponseDataMapper,
} from '../entity-request-pipeline/entity-request-pipeline.types';
import {
  PaginationPageIteratorConfig,
} from '../entity-request-pipeline/pagination-request-base-handlers/pagination-iterator.pipe';
import { EndpointAuthTypeConfig } from '../extension-types';
import { EntitySchema } from '../helpers/entity-schema';
import { EndpointModel } from '../types/endpoint.types';
import { StratosStatus } from '../types/shared.types';
import { UserFavorite } from '../types/user-favorites.types';
import { UserFavoriteManager } from '../user-favorite-manager';

export interface EntityCatalogEntityConfig {
  entityType: string;
  endpointType: string;
  subType?: string;
  schemaKey?: string;
}

export interface ActionBuilderConfig<T extends Record<any, any> = Record<any, any>> {
  actionMetadata?: T;
  entityGuid: string;
  endpointGuid?: string;
}

export type EntityActionBuilderEntityConfig = EntityCatalogEntityConfig & ActionBuilderConfig;

export const extractEntityCatalogEntityConfig = (ecec: Partial<EntityCatalogEntityConfig>): EntityCatalogEntityConfig => {
  const { entityType, endpointType, subType, schemaKey } = ecec;
  return { entityType, endpointType, subType, schemaKey };
};

export interface EntityCatalogSchemas {
  default: EntitySchema;
  [schemaKey: string]: EntitySchema;
}
export interface IStratosEntityWithIcons {
  icon?: string;
  iconFont?: string;
  logoUrl?: string;
}

export interface IEntityMetadata {
  name: string;
  [key: string]: string;
}

export interface HomeCardShortcut {
  title: string;
  link: string[];
  icon: string;
  iconFont?: string;
}

// Metadata for Home Card
export interface HomeCardMetadata {
  component?: (compiler: Compiler, injector: Injector) => Promise<ComponentFactory<HomePageEndpointCard>>;
  shortcuts?: (endpointID: string) => HomeCardShortcut[];
  fullView?: boolean;
}

/**
 * Static information describing a base stratos entity.
 *
 * @export
 */
export interface IStratosBaseEntityDefinition<T = EntitySchema | EntityCatalogSchemas> extends IStratosEntityWithIcons {
  readonly type?: string;
  readonly schema: T;
  readonly label?: string;
  readonly labelShort?: string;
  readonly labelPlural?: string;
  readonly renderPriority?: number;
  /**
   * Show custom content in the endpoints list. Should be Type<EndpointListDetailsComponent>
   */
  readonly listDetailsComponent?: any;
  readonly parentType?: string;
  readonly subTypes?: Omit<IStratosBaseEntityDefinition, 'schema' | 'subTypes'>[];
  readonly paginationConfig?: PaginationPageIteratorConfig;
  readonly tableConfig?: EntityTableConfig<any>;
  readonly registrationComponent?: any;
  /**
   * Hook that will fire before an entity is emitted by an entity service. This could be used, for example, entity validation
   */
  readonly entityEmitHandler?: EntityInfoHandler;
  /**
   * Hook that will fire before an entity is emitted by an entity service. This could be used, for example, entity validation
   */
  readonly entitiesEmitHandler?: EntitiesInfoHandler;
  /**
   * Hook that can override the way an entity is fetched
   */
  readonly entityFetchHandler?: EntityFetchHandler;
  /**
   * Hook that can override the way entities are fetched
   */
  readonly entitiesFetchHandler?: EntitiesFetchHandler;
}

export class EndpointHealthCheck {
  /**
   * @param check To show an error, the check should either call a WrapperRequestActionFailed
   * or kick off a chain that eventually calls a WrapperRequestActionFailed
   */
  constructor(
    public endpointType: string,
    public check: (endpoint: EndpointModel) => void
  ) { }
}

/**
 * Static information describing a stratos endpoint.
 *
 * @export
 */
export interface IStratosEndpointDefinition<T = EntityCatalogSchemas | EntitySchema> extends IStratosBaseEntityDefinition<T> {
  readonly logoUrl: string;
  readonly tokenSharing?: boolean;
  readonly unConnectable?: boolean;
  /**
   * How many endpoints of this type can be registered, 0 - many
   */
  readonly registeredLimit?: (store: Store<AppState>) => Observable<number> | number;
  /**
   * Indicates if this endpoint type is in tech preview and should only be shown when tech preview mode is enabled
   */
  readonly techPreview?: boolean;
  readonly urlValidationRegexString?: string;
  readonly authTypes: EndpointAuthTypeConfig[];
  readonly subTypes?: Omit<IStratosEndpointDefinition, 'schema' | 'subTypes'>[];

  /**
   * Allows an entity to manipulate the data that is returned from an api request before it makes it into the store.
   * This will be used for all entities with this endpoint type.
   */
  readonly globalSuccessfulRequestDataMapper?: SuccessfulApiResponseDataMapper;
  /**
   * Allows an entity to manipulate the request object before it's sent.
   * This will be used for all entities with this endpoint type unless the entity has it's own prerequest config.
   */
  readonly globalPreRequest?: PreApiRequest;
  readonly globalPrePaginationRequest?: PrePaginationApiRequest;
  readonly globalErrorMessageHandler?: ApiErrorMessageHandler;
  readonly healthCheck?: EndpointHealthCheck;
  // Used for favorites - given an entity, get the endpoint ID of the endpoint it belongs to
  readonly getEndpointIdFromEntity?: (entity: any) => string;
  readonly favoriteFromEntity?: <M extends IEntityMetadata = IEntityMetadata>(
    entity: any, entityKey: string, userFavoriteManager: UserFavoriteManager
  ) => UserFavorite<M>;
  /**
   * Allows the endpoint to fetch user roles, for example when the user loads Stratos or connects an endpoint of this type
   */
  readonly userRolesFetch?: EntityUserRolesFetch;
  /**
   * Allows the user roles to be stored, updated and removed in the current user permissions section of the store
   */
  readonly userRolesReducer?: EntityUserRolesReducer;
  /**
   * A list of actions that will be displayed in the endpoints lists
   * Note - These should be restricted by type
   */
  readonly endpointListActions?: (store: Store<AppState>) => IListAction<EndpointModel>[];

  /**
   * Metadata for the card to show on the Home Page for this endpoint type
   */
  readonly homeCard?: HomeCardMetadata;
}

export interface StratosEndpointExtensionDefinition extends Omit<IStratosEndpointDefinition, 'schema'> { }
export interface EntityTableConfig<T = any> {
  rowBuilders: EntityRowBuilder<T>[];
  showHeader?: boolean;
}
/**
 * Static information describing a stratos entity.
 *
 * @export
 */
export interface IStratosEntityDefinition<
  T = EntitySchema | EntityCatalogSchemas,
  E = any,
  I = E
  > extends IStratosBaseEntityDefinition<T> {
  readonly endpoint: StratosEndpointExtensionDefinition;
  readonly subTypes?: Omit<IStratosEntityDefinition, 'schema' | 'subTypes' | 'endpoint'>[];
  // Allows an entity to manipulate the data that is returned from an api request before it makes it into the store.
  // This will override any globalSuccessfulRequestDataMapper found in the endpoint.
  // TODO We should wrap this and the global version with immer to make them immutable.
  readonly successfulRequestDataMapper?: SuccessfulApiResponseDataMapper<E, I> | 'false' | string;
  // Allows an entity to manipulate the request object before it's sent.
  // This will override any globalPreRequest found in the endpoint.
  readonly preRequest?: PreApiRequest;
  readonly prePaginationRequest?: PrePaginationApiRequest;
  readonly errorMessageHandler?: ApiErrorMessageHandler;
  // Should the request response object for this entity be parsed as if it's passed through the jetstream backend?
  readonly nonJetstreamRequest?: boolean;
  readonly nonJetstreamRequestHandler?: NonJetstreamRequestHandler;
}

export class NonJetstreamRequestHandler<T = any> {
  isSuccess: (request: T) => boolean;
  getErrorCode?: (request: T) => string;
}

export interface IStratosEntityActions extends Partial<IStratosEntityWithIcons> {
  readonly label: string;
  readonly action: () => void;
  readonly actionable?: Observable<boolean>;
  readonly disabled?: Observable<boolean>;
}
export type EntityRowBuilder<T> = [string, (entity: T, store?: Store<GeneralEntityAppState>) => string | Observable<string>];

export interface IStratosEntityBuilder<T extends IEntityMetadata, Y = any> {
  getMetadata(entity: Y): T;
  // TODO This should be used in the entities schema.
  getGuid(entity: Y): string;
  getLink?(favorite: UserFavorite<T>): string;
  getSubTypeLabels?(entityMetadata: T): {
    singular: string,
    plural: string,
  };
  // Is the underlying entity for the favorite valid?
  getIsValid?(favorite: UserFavorite<T>): Observable<boolean>;
  /**
   * Actions that don't effect an individual entity i.e. create new
   * @returns global actions
   */
  getGlobalActions?(): IStratosEntityActions[];
  /**
   * Actions that effect on individual entity i.e. rename
   * @returns global actions
   */
  getActions?(entityMetadata: T): IStratosEntityActions[];
}

export interface IStratosEntityData<T extends IEntityMetadata = IEntityMetadata> {
  metadata: T;
  link: string;
  guid: string;
  lines: [string, string | Observable<string>][];
  actions?: IStratosEntityActions[];
  globalActions?: IStratosEntityActions[];
}

export interface IStratosEntityStatusData<Y extends IEntityMetadata = IEntityMetadata> extends IStratosEntityData<Y> {
  status$?: Observable<StratosStatus>;
}