From 37ca8502c973a7c9959a46991afea80c592b4cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Tue, 26 Nov 2019 17:09:58 -0300 Subject: [PATCH 01/31] ui: sidepanel preview widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../src/actions/relation.actions.ts | 14 ++- .../cloud-foundry/src/cf-entity-generator.ts | 12 ++- .../applications/application.service.ts | 39 ++++--- .../cloud-foundry/cloud-foundry.module.ts | 8 +- .../cloud-foundry-endpoint.service.ts | 42 +++++++- .../cloud-foundry-organization.service.ts | 25 +++-- .../services/cloud-foundry-space.service.ts | 34 +++--- ...cloud-foundry-space-summary.component.html | 2 +- .../application-preview.component.html | 68 ++++++++++++ .../application-preview.component.scss | 0 .../application-preview.component.spec.ts | 37 +++++++ .../application-preview.component.ts | 67 ++++++++++++ .../card-cf-info/card-cf-info.component.html | 4 +- .../card-cf-info/card-cf-info.component.ts | 48 +-------- .../card-cf-space-details.component.html | 2 +- .../card-cf-space-details.component.ts | 7 +- .../cf-endpoint-preview.component.html | 76 +++++++++++++ .../cf-endpoint-preview.component.scss | 0 .../cf-endpoint-preview.component.spec.ts | 37 +++++++ .../cf-endpoint-preview.component.ts | 45 ++++++++ .../shared/components/components.module.ts | 23 +++- .../organization-preview.component.html | 102 ++++++++++++++++++ .../organization-preview.component.scss | 0 .../organization-preview.component.spec.ts | 37 +++++++ .../organization-preview.component.ts | 50 +++++++++ .../space-preview.component.html | 101 +++++++++++++++++ .../space-preview.component.scss | 0 .../space-preview.component.spec.ts | 37 +++++++ .../space-preview/space-preview.component.ts | 47 ++++++++ .../packages/core/sass/_all-theme.scss | 5 +- .../entity-catalogue-entity.ts | 9 +- .../entity-catalogue.types.ts | 5 +- .../dashboard-base.component.html | 8 +- .../dashboard-base.component.scss | 6 +- .../dashboard-base.component.ts | 63 +++++------ .../connect-endpoint-dialog.component.ts | 4 +- .../create-endpoint-connect.component.ts | 4 +- .../favorites-meta-card.component.html | 2 +- .../favorites-meta-card.component.ts | 35 +++++- .../markdown-preview.component.html | 7 +- .../markdown-preview.component.scss | 21 ---- .../markdown-preview.component.ts | 14 ++- .../sidepanel-preview.component.html | 8 ++ .../sidepanel-preview.component.scss | 23 ++++ .../sidepanel-preview.component.spec.ts | 37 +++++++ .../sidepanel-preview.component.theme.scss} | 8 +- .../sidepanel-preview.component.ts | 14 +++ .../core/src/shared/previewable-component.ts | 3 + .../shared/services/panel-preview.service.ts | 67 ++++++++++++ .../packages/core/src/shared/shared.module.ts | 20 ++-- .../store/src/actions/dashboard-actions.ts | 13 --- .../store/src/reducers/dashboard-reducer.ts | 19 ++-- 52 files changed, 1130 insertions(+), 229 deletions(-) create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts rename src/frontend/packages/core/src/shared/components/{markdown-preview/markdown-preview.component.theme.scss => sidepanel-preview/sidepanel-preview.component.theme.scss} (67%) create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts create mode 100644 src/frontend/packages/core/src/shared/previewable-component.ts create mode 100644 src/frontend/packages/core/src/shared/services/panel-preview.service.ts diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index ade4c360b2..985214b8dd 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -1,13 +1,11 @@ +import { HttpParams, HttpRequest } from '@angular/common/http'; + import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; -import { - EntityInlineChildAction, - EntityInlineParentAction, -} from '../entity-relations/entity-relations.types'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; -import { RequestEntityLocation, RequestActionEntity } from '../../../store/src/types/request.types'; -import { CFStartAction } from './cf-action.types'; +import { RequestActionEntity, RequestEntityLocation } from '../../../store/src/types/request.types'; import { EntityTreeRelation } from '../entity-relations/entity-relation-tree'; -import { HttpRequest } from '@angular/common/http'; +import { EntityInlineChildAction, EntityInlineParentAction } from '../entity-relations/entity-relations.types'; +import { CFStartAction } from './cf-action.types'; const relationActionId = 'FetchRelationAction'; @@ -27,7 +25,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, { - params: {} + params: new HttpParams() } ); this.parentEntityConfig = parent.entity; 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 bd567c0332..d585f91bae 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,5 @@ import * as moment from 'moment'; + import { IService, IServiceBinding, @@ -39,6 +40,7 @@ import { JetstreamError, } 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 { endpointDisconnectRemoveEntitiesReducer } from '../../store/src/reducers/endpoint-disconnect-application.reducer'; import { APIResource } from '../../store/src/types/api.types'; import { IFavoriteMetadata } from '../../store/src/types/user-favorites.types'; @@ -117,7 +119,11 @@ import { spaceActionBuilders } from './entity-action-builders/space.action-build import { stackActionBuilders } from './entity-action-builders/stack-action-builders'; import { userProvidedServiceActionBuilder } from './entity-action-builders/user-provided-service.action-builders'; import { userActionBuilders } from './entity-action-builders/user.action-builders'; +import { ApplicationPreviewComponent } from './shared/components/application-preview/application-preview.component'; import { CfEndpointDetailsComponent } from './shared/components/cf-endpoint-details/cf-endpoint-details.component'; +import { CfEndpointPreviewComponent } from './shared/components/cf-endpoint-preview/cf-endpoint-preview.component'; +import { OrganizationPreviewComponent } from './shared/components/organization-preview/organization-preview.component'; +import { SpacePreviewComponent } from './shared/components/space-preview/space-preview.component'; import { updateApplicationRoutesReducer } from './store/reducers/application-route.reducer'; import { updateOrganizationQuotaReducer } from './store/reducers/organization-quota.reducer'; import { updateOrganizationSpaceReducer } from './store/reducers/organization-space.reducer'; @@ -129,8 +135,6 @@ import { AppStat } from './store/types/app-metadata.types'; import { CFResponse } from './store/types/cf-api.types'; import { GitBranch, GitCommit, GitRepo } from './store/types/git.types'; import { CfUser } from './store/types/user.types'; -import { CfApplicationState } from './store/types/application.types'; -import { EntitySchema } from '../../store/src/helpers/entity-schema'; export interface CFBasePipelineRequestActionMeta { includeRelations?: string[]; @@ -860,6 +864,7 @@ function generateCfEndpointEntity(endpointDefinition: StratosEndpointExtensionDe return new StratosCatalogueEndpointEntity( endpointDefinition, metadata => `/cloud-foundry/${metadata.guid}`, + () => CfEndpointPreviewComponent, ); } @@ -894,6 +899,7 @@ function generateCfApplicationEntity(endpointDefinition: StratosEndpointExtensio }), getLink: metadata => `/applications/${metadata.cfGuid}/${metadata.guid}/summary`, getGuid: metadata => metadata.guid, + getPreviewableComponent: () => ApplicationPreviewComponent, getLines: () => ([ ['Creation Date', (meta) => meta.createdAt] ]) @@ -930,6 +936,7 @@ function generateCfSpaceEntity(endpointDefinition: StratosEndpointExtensionDefin name: space.entity.name, cfGuid: space.entity.cfGuid, }), + getPreviewableComponent: () => SpacePreviewComponent, getLines: () => ([ ['Name', (meta) => meta.name], ]), @@ -965,6 +972,7 @@ function generateCfOrgEntity(endpointDefinition: StratosEndpointExtensionDefinit name: org.entity.name, cfGuid: org.entity.cfGuid, }), + getPreviewableComponent: () => OrganizationPreviewComponent, getLink: metadata => `/cloud-foundry/${metadata.cfGuid}/organizations/${metadata.guid}`, getGuid: metadata => metadata.guid } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index dd94db5dfe..ab5e375edd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -91,21 +91,9 @@ export class ApplicationService { private appEnvVarsService: ApplicationEnvVarsHelper, private paginationMonitorFactory: PaginationMonitorFactory, ) { - this.appEntityService = this.entityServiceFactory.create>( - appGuid, - createGetApplicationAction(appGuid, cfGuid) - ); - const appSummaryEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, appSummaryEntityType); - const actionBuilder = appSummaryEntity.actionOrchestrator.getActionBuilder('get'); - const getAppSummaryAction = actionBuilder(appGuid, cfGuid); - this.appSummaryEntityService = this.entityServiceFactory.create( - appGuid, - getAppSummaryAction - ); - - this.constructCoreObservables(); - this.constructAmalgamatedObservables(); - this.constructStatusObservables(); + if (cfGuid && appGuid) { + this.initialize(cfGuid, appGuid); + } } // NJ: This needs to be cleaned up. So much going on! @@ -161,6 +149,27 @@ export class ApplicationService { ).pipe(publishReplay(1), refCount()); } + public initialize(cfGuid, appGuid) { + this.cfGuid = cfGuid; + this.appGuid = appGuid; + + this.appEntityService = this.entityServiceFactory.create>( + appGuid, + createGetApplicationAction(appGuid, cfGuid) + ); + const appSummaryEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, appSummaryEntityType); + const actionBuilder = appSummaryEntity.actionOrchestrator.getActionBuilder('get'); + const getAppSummaryAction = actionBuilder(appGuid, cfGuid); + this.appSummaryEntityService = this.entityServiceFactory.create( + appGuid, + getAppSummaryAction + ); + + this.constructCoreObservables(); + this.constructAmalgamatedObservables(); + this.constructStatusObservables(); + } + private constructCoreObservables() { // First set up all the base observables this.app$ = this.appEntityService.waitForEntity$; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts index 3fae3aaa5a..700f7d5053 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts @@ -36,15 +36,12 @@ import { import { CloudFoundryQuotasComponent, } from '../../../../core/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component'; -import { EndpointListHelper } from '../../../../core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers'; -import { - EndpointsListConfigService, -} from '../../../../core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service'; import { SharedModule } from '../../../../core/src/shared/shared.module'; import { CloudFoundryComponentsModule } from '../../shared/components/components.module'; import { CFEndpointsListConfigService, } from '../../shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service'; +import { ApplicationService } from '../applications/application.service'; import { AddOrganizationComponent } from './add-organization/add-organization.component'; import { CreateOrganizationStepComponent, @@ -64,6 +61,7 @@ import { EditSpaceComponent } from './edit-space/edit-space.component'; import { QuotaDefinitionComponent } from './quota-definition/quota-definition.component'; import { CloudFoundryEndpointService } from './services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from './services/cloud-foundry-organization.service'; +import { CloudFoundrySpaceService } from './services/cloud-foundry-space.service'; import { SpaceQuotaDefinitionComponent } from './space-quota-definition/space-quota-definition.component'; import { CfAdminAddUserWarningComponent } from './tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component'; import { CloudFoundryBuildPacksComponent } from './tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component'; @@ -228,6 +226,8 @@ import { RemoveUserComponent } from './users/remove-user/remove-user.component'; provide: ActiveRouteCfCell, useValue: {} }, + ApplicationService, + CloudFoundrySpaceService, CloudFoundryOrganizationService, CloudFoundryEndpointService, // CfRolesService, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts index 20d3bffaa7..146b48f738 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts @@ -65,6 +65,8 @@ export class CloudFoundryEndpointService { hasSSHAccess$: Observable; totalMem$: Observable; + description$: Observable; + apiUrl$: Observable; paginationSubscription: any; appsPagObs: PaginationObservables>; usersCount$: Observable; @@ -100,6 +102,7 @@ export class CloudFoundryEndpointService { }) as PaginatedAction; return getAllOrganizationsAction; } + static createGetAllOrganizationsLimitedSchema(cfGuid: string) { const paginationKey = cfGuid ? createEntityRelationPaginationKey(endpointSchemaKey, cfGuid) @@ -140,7 +143,14 @@ export class CloudFoundryEndpointService { private endpointService: EndpointsService, private paginationMonitorFactory: PaginationMonitorFactory ) { - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; + if (cfGuid) { + this.initialize(cfGuid); + } + } + + public initialize(cfGuid) { + this.cfGuid = cfGuid; this.getAllOrgsAction = CloudFoundryEndpointService.createGetAllOrganizations(this.cfGuid) as GetAllOrganizations; this.getAllAppsAction = new GetAllApplications(createEntityRelationPaginationKey('cf', this.cfGuid), this.cfGuid); @@ -199,6 +209,14 @@ export class CloudFoundryEndpointService { ); this.totalMem$ = this.appsPagObs.entities$.pipe(map(apps => this.getMetricFromApps(apps, 'memory'))); + this.apiUrl$ = this.endpoint$.pipe( + map(endpoint => this.getApiEndpointUrl(endpoint.entity.api_endpoint)) + ); + + this.description$ = this.info$.pipe( + map(entity => this.getDescription(entity)) + ); + this.connected$ = this.endpoint$.pipe( map(p => p.entity.connectionStatus === 'connected') ); @@ -258,6 +276,28 @@ export class CloudFoundryEndpointService { this.store.dispatch(this.getAllAppsAction); } + private getApiEndpointUrl(apiEndpoint) { + const path = apiEndpoint.Path ? `/${apiEndpoint.Path}` : ''; + return `${apiEndpoint.Scheme}://${apiEndpoint.Host}${path}`; + } + + private getMetadataFromInfo(entity: EntityInfo>) { + return entity && entity.entity && entity.entity.entity ? entity.entity.entity : null; + } + + private getDescription(entity: EntityInfo>): string { + const metadata = this.getMetadataFromInfo(entity); + if (metadata) { + if (metadata.description) { + return metadata.description + (metadata.build ? ` (${metadata.build})` : ''); + } + if (metadata.support === 'pcfdev@pivotal.io') { + return 'PCF Dev'; + } + } + return '-'; + } + hasCellMetrics(endpointId: string): Observable { return this.endpointService.hasMetrics(endpointId).pipe( switchMap(hasMetrics => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts index a581a419d1..fe0356517e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts @@ -24,19 +24,19 @@ import { ISpaceQuotaDefinition, } from '../../../../../core/src/core/cf-api.types'; import { getEntityFlattenedList, getStartedAppInstanceCount } from '../../../../../core/src/core/cf.helpers'; +import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { CloudFoundryUserProvidedServicesService, } from '../../../../../core/src/shared/services/cloud-foundry-user-provided-services.service'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getOrgRolesString } from '../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; -import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; -import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; export const createOrgQuotaDefinition = (): IOrgQuotaDefinition => ({ memory_limit: -1, @@ -85,7 +85,7 @@ export class CloudFoundryOrganizationService { usersCount$: Observable; constructor( - public activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, + activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, private store: Store, private entityServiceFactory: EntityServiceFactory, private cfUserService: CfUserService, @@ -93,10 +93,12 @@ export class CloudFoundryOrganizationService { private cfEndpointService: CloudFoundryEndpointService, private cfUserProvidedServicesService: CloudFoundryUserProvidedServicesService ) { - this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const orgGuid = activeRouteCfOrgSpace.orgGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; - this.initialiseObservables(); + if (cfGuid && orgGuid) { + this.initialize(cfGuid, orgGuid); + } } public deleteSpace(spaceGuid: string, orgGuid: string, endpointGuid: string) { @@ -110,7 +112,10 @@ export class CloudFoundryOrganizationService { this.cfEndpointService.fetchApps(); } - private initialiseObservables() { + public initialize(cfGuid, orgGuid) { + this.cfGuid = cfGuid; + this.orgGuid = orgGuid; + this.org$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { const relations = [ @@ -133,7 +138,7 @@ export class CloudFoundryOrganizationService { } const orgEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, organizationEntityType); const getOrgActionBuilder = orgEntity.actionOrchestrator.getActionBuilder('get'); - const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid, relations); + const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid, { includeRelations: relations }); const orgEntityService = this.entityServiceFactory.create>( this.orgGuid, getOrgAction @@ -194,8 +199,8 @@ export class CloudFoundryOrganizationService { return CloudFoundryEndpointService.fetchAppCount( this.store, this.paginationMonitorFactory, - this.activeRouteCfOrgSpace.cfGuid, - this.activeRouteCfOrgSpace.orgGuid + this.cfGuid, + this.orgGuid ); } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 4ba4505590..371b9874d9 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { combineLatest, Observable, of } from 'rxjs'; -import { filter, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; +import { map, publishReplay, refCount, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { @@ -15,12 +15,14 @@ import { import { SpaceUserRoleNames } from '../../../../../cloud-foundry/src/store/types/user.types'; import { IApp, IOrgQuotaDefinition, IRoute, ISpace, ISpaceQuotaDefinition } from '../../../../../core/src/core/cf-api.types'; import { getStartedAppInstanceCount } from '../../../../../core/src/core/cf.helpers'; +import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { CloudFoundryUserProvidedServicesService, } from '../../../../../core/src/shared/services/cloud-foundry-user-provided-services.service'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { fetchServiceInstancesCount } from '../../service-catalog/services-helper'; @@ -28,8 +30,6 @@ import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getSpaceRolesString } from '../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService, createOrgQuotaDefinition } from './cloud-foundry-organization.service'; -import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; -import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; @Injectable() export class CloudFoundrySpaceService { @@ -48,6 +48,7 @@ export class CloudFoundrySpaceService { */ spaceQuotaDefinition$: Observable; allowSsh$: Observable; + allowSshStatus$: Observable; totalMem$: Observable; routes$: Observable[]>; serviceInstancesCount$: Observable; @@ -71,18 +72,20 @@ export class CloudFoundrySpaceService { private cfOrgService: CloudFoundryOrganizationService ) { - this.spaceGuid = activeRouteCfOrgSpace.spaceGuid; - this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const spaceGuid = activeRouteCfOrgSpace.spaceGuid; + const orgGuid = activeRouteCfOrgSpace.orgGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; - this.initialiseObservables(); + if (cfGuid && orgGuid && spaceGuid) { + this.initialize(cfGuid, orgGuid, spaceGuid); + } } - public fetchApps() { - this.cfEndpointService.fetchApps(); - } + public initialize(cfGuid: string, orgGuid: string, spaceGuid: string) { + this.cfGuid = cfGuid; + this.orgGuid = orgGuid; + this.spaceGuid = spaceGuid; - private initialiseObservables() { this.initialiseSpaceObservables(); this.initialiseAppObservables(); @@ -126,7 +129,7 @@ export class CloudFoundrySpaceService { this.spaceGuid, getSpaceAction ); - return spaceEntityService.entityObs$.pipe(filter(o => !!o && !!o.entity)); + return spaceEntityService.waitForEntity$; }), publishReplay(1), refCount() @@ -142,6 +145,7 @@ export class CloudFoundrySpaceService { this.cfUserProvidedServicesService.fetchUserProvidedServiceInstancesCount(this.cfGuid, this.orgGuid, this.spaceGuid); this.routes$ = this.space$.pipe(map(o => o.entity.entity.routes)); this.allowSsh$ = this.space$.pipe(map(o => o.entity.entity.allow_ssh ? 'true' : 'false')); + this.allowSshStatus$ = this.allowSsh$.pipe(map(status => status === 'false' ? 'Disabled' : 'Enabled')); this.spaceQuotaDefinition$ = this.space$.pipe( map(q => q.entity.entity.space_quota_definition ? q.entity.entity.space_quota_definition.entity : null) ); @@ -211,9 +215,9 @@ export class CloudFoundrySpaceService { return CloudFoundryEndpointService.fetchAppCount( this.store, this.paginationMonitorFactory, - this.activeRouteCfOrgSpace.cfGuid, - this.activeRouteCfOrgSpace.orgGuid, - this.activeRouteCfOrgSpace.spaceGuid + this.cfGuid, + this.orgGuid, + this.spaceGuid ); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html index 3ec288a9fa..1f03e7bdb5 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html @@ -75,7 +75,7 @@ + [loading$]="cfSpaceService.loadingApps$" (refresh)="cfEndpointService.fetchApps()"> diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html new file mode 100644 index 0000000000..9900b6a612 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -0,0 +1,68 @@ + + + + +
+ + + Summary + + + + + + {{ appSvc.cf?.name}} + + + {{ appSvc.cf?.name}} + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts new file mode 100644 index 0000000000..2d3d25d230 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { ApplicationPreviewComponent } from './application-preview.component'; + +describe('ApplicationPreviewComponent', () => { + let component: ApplicationPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ApplicationPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ApplicationPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts new file mode 100644 index 0000000000..5aa2772e67 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts @@ -0,0 +1,67 @@ +import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { combineLatest, map } from 'rxjs/operators'; + +import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; +import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; +import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { ApplicationService } from '../../../features/applications/application.service'; +import { getGuids } from '../../../features/applications/application/application-base.component'; + +@Component({ + selector: 'app-application-preview-component', + templateUrl: './application-preview.component.html', + styleUrls: ['./application-preview.component.scss'], + // useless, necessary to reuse ApplicationService + providers: [ + ApplicationService, + { + provide: CF_GUID, + useFactory: getGuids('cf'), + deps: [ActivatedRoute] + }, + { + provide: APP_GUID, + useFactory: getGuids(), + deps: [ActivatedRoute] + }, + ] +}) +export class ApplicationPreviewComponent implements PreviewableComponent { + + title = null; + cfEndpointService: object; + sshStatus$: Observable; + + getFullEndpointApiUrl = getFullEndpointApiUrl; + + constructor(public applicationService: ApplicationService) { + } + + setProps(props: { [key: string]: any }) { + this.title = props.title; + + this.applicationService.initialize(props.cfGuid, props.guid); + this.sshStatus$ = this.applicationService.application$.pipe( + combineLatest(this.applicationService.appSpace$), + map(([app, space]) => { + if (!space.entity.allow_ssh) { + return 'Disabled by the space'; + } else { + return app.app.entity.enable_ssh ? 'Yes' : 'No'; + } + }) + ); + + // this.detailsLoading$ = combineLatest([ + // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now + // this.cfEndpointService.appsPagObs.fetchingEntities$.pipe( + // filter(loading => !loading) + // ), + // ]).pipe( + // map(() => false), + // startWith(true) + // ); + } +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html index 9ce13dc103..e8a2a95463 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html @@ -6,8 +6,8 @@ \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 0884263a2b..82277d48cb 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -16,6 +16,10 @@ $app-sub-header-height: 48px; } &__side-help-outer { z-index: 9999; + + @include breakpoint(mobileonly) { + left: 0; + } } &__side-help-button { position: absolute; @@ -51,7 +55,7 @@ $app-sub-header-height: 48px; padding: 0; } &__side-help { - max-width: 600px; + min-width: 600px; } } diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts index ad8a819158..df6b6dd0e5 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts @@ -1,6 +1,6 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { Portal } from '@angular/cdk/portal'; -import { Component, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import { MatDrawer } from '@angular/material'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router } from '@angular/router'; import { Store } from '@ngrx/store'; @@ -13,12 +13,7 @@ import { cfInfoEntityType } from '../../../../../cloud-foundry/src/cf-entity-typ import { CfInfoDefinitionActionBuilders, } from '../../../../../cloud-foundry/src/entity-action-builders/cf-info.action-builders'; -import { - CloseSideHelp, - CloseSideNav, - DisableMobileNav, - EnableMobileNav, -} from '../../../../../store/src/actions/dashboard-actions'; +import { CloseSideNav, DisableMobileNav, EnableMobileNav } from '../../../../../store/src/actions/dashboard-actions'; import { GetUserFavoritesAction } from '../../../../../store/src/actions/user-favourites-actions/get-user-favorites-action'; import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { DashboardState } from '../../../../../store/src/reducers/dashboard-reducer'; @@ -29,6 +24,7 @@ import { CustomizationService } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IEntityMetadata } from '../../../core/entity-catalogue/entity-catalogue.types'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { PageHeaderService } from './../../../core/page-header-service/page-header.service'; import { SideNavItem } from './../side-nav/side-nav.component'; @@ -39,16 +35,28 @@ import { SideNavItem } from './../side-nav/side-nav.component'; styleUrls: ['./dashboard-base.component.scss'] }) -export class DashboardBaseComponent implements OnInit, OnDestroy { +export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit { public activeTabLabel$: Observable; public subNavData$: Observable<[string, Portal]>; public isMobile$: Observable; public sideNavMode$: Observable; public sideNavMode: string; public mainNavState$: Observable<{ mode: string; opened: boolean; iconMode: boolean }>; - public rightNavState$: Observable<{ opened: boolean; documentUrl: string; }>; + public rightNavState$: Observable<{ opened: boolean, component?: object, props?: object }>; private dashboardState$: Observable; + public noMargin$: Observable; + private closeSub: Subscription; + private mobileSub: Subscription; private drawer: MatDrawer; + public iconModeOpen = false; + public sideNavWidth = 54; + + sideNavTabs: SideNavItem[] = this.getNavigationRoutes(); + sideNaveMode = 'side'; + + @ViewChild('previewPanelContainer', { read: ViewContainerRef, static: false }) previewPanelContainer: ViewContainerRef; + + @ViewChild('content', { static: false }) public content; constructor( public pageHeaderService: PageHeaderService, @@ -59,6 +67,7 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { private endpointsService: EndpointsService, public tabNavService: TabNavService, private ngZone: NgZone, + public panelPreviewService: PanelPreviewService, private cs: CustomizationService ) { this.noMargin$ = this.router.events.pipe( @@ -89,24 +98,11 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { } }) ); - this.rightNavState$ = this.dashboardState$.pipe( - map(state => ({ - opened: !!state.sideHelpDocument && state.sideHelpOpen, - documentUrl: state.sideHelpDocument - })) - ); + this.mobileSub = this.isMobile$ .subscribe(isMobile => isMobile ? this.store.dispatch(new EnableMobileNav()) : this.store.dispatch(new DisableMobileNav())); } - public helpDocumentUrl: string; - - private closeSub: Subscription; - - public noMargin$: Observable; - - private mobileSub: Subscription; - @ViewChild('sidenav', { static: false }) set sidenav(drawer: MatDrawer) { this.drawer = drawer; if (!this.closeSub) { @@ -119,15 +115,6 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { } } - @ViewChild('content', { static: true }) public content; - - sideNavTabs: SideNavItem[] = this.getNavigationRoutes(); - - sideNaveMode = 'side'; - - public iconModeOpen = false; - public sideNavWidth = 54; - public redrawSideNav() { // We need to do this to ensure there isn't a space left behind // when going from mobile to desktop @@ -140,6 +127,14 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { this.store.dispatch(new GetCurrentUsersRelations()); } + sideHelpClosed() { + this.panelPreviewService.hide(); + } + + ngAfterViewInit() { + this.panelPreviewService.setContainer(this.previewPanelContainer); + } + ngOnInit() { this.subNavData$ = combineLatest( this.tabNavService.getCurrentTabHeaderObservable().pipe( @@ -172,10 +167,6 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { return false; } - public sideHelpClosed() { - this.store.dispatch(new CloseSideHelp()); - } - private getNavigationRoutes(): SideNavItem[] { let navItems = this.collectNavigationRoutes('', this.router.config); diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index e418b05aff..b4c554eba4 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -3,7 +3,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; -import { ShowSideHelp } from '../../../../../store/src/actions/dashboard-actions'; import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; @@ -38,7 +37,8 @@ export class ConnectEndpointDialogComponent implements OnDestroy { } showHelp() { - this.store.dispatch(new ShowSideHelp(this.helpDocumentUrl)); + // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); + // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); } ngOnDestroy(): void { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index d513432c29..b2282192bb 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -3,7 +3,6 @@ import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { ShowSideHelp } from '../../../../../../store/src/actions/dashboard-actions'; import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; @@ -31,7 +30,8 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { } showHelp() { - this.store.dispatch(new ShowSideHelp(this.helpDocumentUrl)); + // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); + // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); } onEnter = (data: ConnectEndpointConfig) => { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html index 53a2b5e295..9af11474b9 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html @@ -1,5 +1,5 @@ diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index a252d8b69e..29c8e373ef 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -7,10 +7,15 @@ import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { RemoveUserFavoriteAction, } from '../../../../../store/src/actions/user-favourites-actions/remove-user-favorite-action'; -import { endpointEntitiesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; +import { + endpointEntitiesSelector, + endpointsEntityRequestDataSelector, +} from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; +import { PanelPreviewService } from '../../services/panel-preview.service'; import { ComponentEntityMonitorConfig, StratosStatus } from '../../shared.types'; import { ConfirmationDialogConfig } from '../confirmation-dialog.config'; import { ConfirmationDialogService } from '../confirmation-dialog.service'; @@ -97,7 +102,33 @@ export class FavoritesMetaCardComponent { } } - constructor(private store: Store, private confirmDialog: ConfirmationDialogService) { } + constructor( + private store: Store, + private confirmDialog: ConfirmationDialogService, + private panelPreviewService: PanelPreviewService + ) { } + + previewPanel() { + const catalogueEntity = entityCatalogue.getEntity(this.favorite.endpointType, this.favorite.entityType); + const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); + + // TODO: use 'endpoint' as constant + if (this.favorite.entityType === 'endpoint') { + const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + entity$, + cfGuid: this.favorite.endpointId + }); + } else { + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + cfGuid: this.favorite.metadata.cfGuid, + orgGuid: this.favorite.metadata.orgGuid, + guid: this.favorite.metadata.guid + }); + } + } public setConfirmation(prettyName: string, favorite: UserFavorite) { this.confirmation = new ConfirmationDialogConfig( diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html index 4ad4077847..972ac85319 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html @@ -1,6 +1,3 @@ -
-
-

{{ title }}

-
+
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss index 2a7eaa882f..e69de29bb2 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss @@ -1,21 +0,0 @@ -.markdown-preview { - display: flex; - flex-direction: column; - height: 100vh; - &__header { - display: flex; - flex: 0 0 56px; - height: 56px; - - h1 { - align-self: center; - font-size: 20px; - margin-left: 24px; - } - } - &__content { - flex: 1; - overflow: auto; - padding: 10px 24px; - } -} diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index 97384669d0..dab1792c71 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -4,13 +4,14 @@ import { DomSanitizer } from '@angular/platform-browser'; import * as markdown from 'marked'; import { LoggerService } from '../../../core/logger.service'; +import { PreviewableComponent } from '../../previewable-component'; @Component({ selector: 'app-markdown-preview', templateUrl: './markdown-preview.component.html', styleUrls: ['./markdown-preview.component.scss'] }) -export class MarkdownPreviewComponent { +export class MarkdownPreviewComponent implements PreviewableComponent { markdownHtml: string; documentUrl: string; @@ -27,7 +28,16 @@ export class MarkdownPreviewComponent { @ViewChild('markdown', { static: true }) public markdown: ElementRef; - constructor(private httpClient: HttpClient, private logger: LoggerService, private domSanitizer: DomSanitizer) { } + constructor( + private httpClient: HttpClient, + private logger: LoggerService, + private domSanitizer: DomSanitizer + ) { } + + setProps(props: { [key: string]: any }) { + console.log('props', props); + this.setDocumentUrl = props.documentUrl; + } private loadDocument() { this.httpClient.get(this.documentUrl, { responseType: 'text' }).subscribe( diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html new file mode 100644 index 0000000000..9e95cac0d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html @@ -0,0 +1,8 @@ +
+
+

{{ title }}

+
+
+ +
+
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss new file mode 100644 index 0000000000..c3afff37d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss @@ -0,0 +1,23 @@ +.sidepanel-preview { + display: flex; + flex-direction: column; + height: 100vh; + &__header { + display: flex; + flex: 0 0 56px; + height: 56px; + + h1 { + align-self: center; + font-size: 20px; + margin-left: 24px; + } + } + &__content { + flex: 1; + overflow-x: hidden; + overflow-y: auto; + padding: 12px 15px; + position: relative; + } +} diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts new file mode 100644 index 0000000000..2eb1505ae2 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { SidepanelPreviewComponent } from './sidepanel-preview.component'; + +describe('SidepanelPreviewComponent', () => { + let component: SidepanelPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SidepanelPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SidepanelPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss similarity index 67% rename from src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss rename to src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss index 926eb8127e..c84bed25dc 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss @@ -1,12 +1,14 @@ -@mixin app-markdown-preview-theme($theme, $app-theme) { +@mixin app-sidepanel-preview-theme($theme, $app-theme) { $primary: map-get($theme, primary); - .markdown-preview__header { + .sidepanel-preview__header { background-color: mat-color($primary); color: mat-contrast($primary, 500); } - .markdown-preview__content { + .sidepanel-preview__content { + background-color: map-get($app-theme, app-background-color); + > h1:first-child { height: 0; margin: 0; diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts new file mode 100644 index 0000000000..315d11c160 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts @@ -0,0 +1,14 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-sidepanel-preview', + templateUrl: './sidepanel-preview.component.html', + styleUrls: ['./sidepanel-preview.component.scss'] +}) +export class SidepanelPreviewComponent { + + @Input() + title: string; + + constructor() { } +} diff --git a/src/frontend/packages/core/src/shared/previewable-component.ts b/src/frontend/packages/core/src/shared/previewable-component.ts new file mode 100644 index 0000000000..40410d6f07 --- /dev/null +++ b/src/frontend/packages/core/src/shared/previewable-component.ts @@ -0,0 +1,3 @@ +export interface PreviewableComponent { + setProps(props: { [key: string]: any }): void; +} diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts new file mode 100644 index 0000000000..dda1d3e374 --- /dev/null +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -0,0 +1,67 @@ +import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; +import { observeOn, publishReplay, refCount } from 'rxjs/operators'; + +@Injectable() +export class PanelPreviewService { + private openedSubject: BehaviorSubject; + public opened$: Observable; + + private container: ViewContainerRef; + + constructor(private componentFactoryResolver: ComponentFactoryResolver) { + this.openedSubject = new BehaviorSubject(false); + this.opened$ = this.observeSubject(this.openedSubject); + } + + public setContainer(container: ViewContainerRef) { + if (this.container) { + throw new Error('PanelPreviewService: container already set'); + } + + this.container = container; + } + + public show(component: object, props?: { [key: string]: any }) { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.render(component, props); + this.openedSubject.next(true); + } + + public hide() { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.openedSubject.next(false); + } + + render(component: object, props: { [key: string]: any }) { + if (this.container.length) { + this.container.remove(0); + } + + const factory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(component as any); + const componentRef: ComponentRef = this.container.createComponent(factory); + + if (props) { + componentRef.instance.setProps(props); + } + } + + public clear() { + this.container.clear(); + this.openedSubject.next(false); + } + + private observeSubject(subject: Subject) { + return subject.asObservable().pipe( + publishReplay(1), + refCount(), + observeOn(asapScheduler) + ); + } +} diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 12aae65f83..a1d52f874c 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -58,8 +58,12 @@ import { TableCellStatusDirective } from './components/list/list-table/table-cel import { TableComponent } from './components/list/list-table/table.component'; import { listTableComponents } from './components/list/list-table/table.types'; import { EndpointCardComponent } from './components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; +import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; +import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; import { ListComponent } from './components/list/list.component'; import { ListConfig } from './components/list/list.component.types'; +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'; import { LogViewerComponent } from './components/log-viewer/log-viewer.component'; import { MarkdownContentObserverDirective } from './components/markdown-preview/markdown-content-observer.directive'; @@ -78,6 +82,7 @@ import { PageSubNavComponent } from './components/page-sub-nav/page-sub-nav.comp import { PollingIndicatorComponent } from './components/polling-indicator/polling-indicator.component'; import { RingChartComponent } from './components/ring-chart/ring-chart.component'; import { RoutingIndicatorComponent } from './components/routing-indicator/routing-indicator.component'; +import { SidepanelPreviewComponent } from './components/sidepanel-preview/sidepanel-preview.component'; import { SimpleUsageChartComponent } from './components/simple-usage-chart/simple-usage-chart.component'; import { SnackBarReturnComponent } from './components/snackbar-return/snackbar-return.component'; import { SshViewerComponent } from './components/ssh-viewer/ssh-viewer.component'; @@ -111,11 +116,8 @@ import { UsageBytesPipe } from './pipes/usage-bytes.pipe'; import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; +import { PanelPreviewService } from './services/panel-preview.service'; import { UserPermissionDirective } from './user-permission.directive'; -import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; -import { ListHostDirective } from './components/list/simple-list/list-host.directive'; -import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; -import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; /* tslint:disable:max-line-length */ @@ -211,13 +213,13 @@ import { EndpointsListConfigService } from './components/list/list-types/endpoin BreadcrumbsComponent, PageSubNavSectionComponent, EntitySummaryTitleComponent, - MarkdownPreviewComponent, MarkdownContentObserverDirective, SnackBarReturnComponent, PollingIndicatorComponent, UnlimitedInputComponent, SimpleListComponent, ListHostDirective, + SidepanelPreviewComponent ], exports: [ ApplicationStateIconPipe, @@ -300,24 +302,26 @@ import { EndpointsListConfigService } from './components/list/list-types/endpoin AppNameUniqueDirective, SimpleUsageChartComponent, EntitySummaryTitleComponent, - MarkdownPreviewComponent, MarkdownContentObserverDirective, AppNameUniqueDirective, PollingIndicatorComponent, UnlimitedInputComponent, SimpleListComponent, - ListHostDirective + ListHostDirective, + SidepanelPreviewComponent, ], entryComponents: [ DialogConfirmComponent, EnvVarViewComponent, - SnackBarReturnComponent + SnackBarReturnComponent, + MarkdownPreviewComponent, ], providers: [ ListConfig, ApplicationStateService, EndpointListHelper, EndpointsListConfigService, + PanelPreviewService, // CfUserService, ConfirmationDialogService, EntityMonitorFactory, diff --git a/src/frontend/packages/store/src/actions/dashboard-actions.ts b/src/frontend/packages/store/src/actions/dashboard-actions.ts index 88c8f8019c..08e6b3fb42 100644 --- a/src/frontend/packages/store/src/actions/dashboard-actions.ts +++ b/src/frontend/packages/store/src/actions/dashboard-actions.ts @@ -10,10 +10,6 @@ export const SET_HEADER_EVENT = '[Dashboard] Set header event'; export const ENABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Enable mobile nav'; export const DISABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Disable mobile nav'; -export const SHOW_SIDE_HELP = '[Dashboard] Show side help'; -export const CLOSE_SIDE_HELP = '[Dashboard] Close side help'; - - export const TIMEOUT_SESSION = '[Dashboard] Timeout Session'; export const ENABLE_POLLING = '[Dashboard] Enable Polling'; @@ -36,15 +32,6 @@ export class ToggleSideNav implements Action { type = TOGGLE_SIDE_NAV; } -export class ShowSideHelp implements Action { - constructor(public document: string) { } - type = SHOW_SIDE_HELP; -} - -export class CloseSideHelp implements Action { - type = CLOSE_SIDE_HELP; -} - export class SetHeaderEvent implements Action { constructor(public minimised = false) { } type = SET_HEADER_EVENT; diff --git a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts index 5963b912b9..788c8307a2 100644 --- a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts +++ b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts @@ -1,5 +1,4 @@ import { - CLOSE_SIDE_HELP, CLOSE_SIDE_NAV, DISABLE_SIDE_NAV_MOBILE_MODE, ENABLE_POLLING, @@ -7,9 +6,10 @@ import { HYDRATE_DASHBOARD_STATE, HydrateDashboardStateAction, OPEN_SIDE_NAV, + SET_HEADER_EVENT, + SetHeaderEvent, SetPollingEnabledAction, SetSessionTimeoutAction, - SHOW_SIDE_HELP, TIMEOUT_SESSION, TOGGLE_SIDE_NAV, } from '../actions/dashboard-actions'; @@ -21,8 +21,7 @@ export interface DashboardState { isMobile: boolean; isMobileNavOpen: boolean; sideNavPinned: boolean; - sideHelpOpen: boolean; - sideHelpDocument: string; + headerEventMinimized: boolean; } export const defaultDashboardState: DashboardState = { @@ -32,8 +31,7 @@ export const defaultDashboardState: DashboardState = { isMobile: false, isMobileNavOpen: false, sideNavPinned: true, - sideHelpOpen: false, - sideHelpDocument: null, + headerEventMinimized: false, }; export function dashboardReducer(state: DashboardState = defaultDashboardState, action): DashboardState { @@ -57,10 +55,11 @@ export function dashboardReducer(state: DashboardState = defaultDashboardState, return { ...state, isMobile: true, isMobileNavOpen: false }; case DISABLE_SIDE_NAV_MOBILE_MODE: return { ...state, isMobile: false, isMobileNavOpen: false }; - case SHOW_SIDE_HELP: - return { ...state, sideHelpOpen: true, sideHelpDocument: action.document }; - case CLOSE_SIDE_HELP: - return { ...state, sideHelpOpen: false, sideHelpDocument: '' }; + case SET_HEADER_EVENT: + const setHeaderEvent = action as SetHeaderEvent; + return { + ...state, headerEventMinimized: setHeaderEvent.minimised + }; case TIMEOUT_SESSION: const timeoutSessionAction = action as SetSessionTimeoutAction; return { From 8ea2b10370e8c18fd3138dc2579d500373a07e25 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 29 Nov 2019 17:16:07 +0000 Subject: [PATCH 02/31] Fix issue where clicking on org and then app fav failed - Issue 1 (slightly unrelated) - app service getspace action used space entity without an org - Issue 2 - app entity validation found it was missing the space - validation process fetched space with custom action - custom action did not contain schema key to use space with org schema - org was not stored correctly in store (contained in space rather than seperatly) - Still to do - Fix for issue 2 would need to be expanded to ALL schema's with inline entities - These new schemas would need to be added to their entities - All usages would have to provide the overriding schemaKey --- .../src/actions/relation.actions.ts | 2 + .../actions/user-provided-service.actions.ts | 6 +-- .../cloud-foundry/src/cf-entity-factory.ts | 38 +++++++++---------- .../src/cf-entity-schema-types.ts | 10 +++-- .../applications/application.service.ts | 29 +++++++------- ...aces-user-service-instances-data-source.ts | 12 +++--- 6 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index 985214b8dd..d686404b22 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -21,6 +21,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit ) { super(); this.entityType = child.entityType; + this.schemaKey = child.entity.schemaKey; this.options = new HttpRequest( 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, @@ -32,6 +33,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit } entity: RequestActionEntity; entityType: string; + schemaKey: string; isId = relationActionId; actions = [ '[Fetch Relations] Start', 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 09713ee768..bdedde7d05 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,3 +1,5 @@ +import { HttpRequest } from '@angular/common/http'; + import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; @@ -10,7 +12,6 @@ import { organizationEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../cf-entity-types'; import { @@ -19,10 +20,9 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; -import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ - createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), + createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType), createEntityRelationKey(userProvidedServiceInstanceEntityType, serviceBindingEntityType), createEntityRelationKey(serviceBindingEntityType, applicationEntityType) diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 5ed43bd63c..1da855c14e 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -244,7 +244,7 @@ const SpaceWithOrgsEntitySchema = new CFSpaceEntitySchema({ apps: [ApplicationWithoutSpaceEntitySchema], organization: OrganizationsWithoutSpaces, } -}, spaceWithOrgEntityType); +}, null, spaceWithOrgEntityType); entityCache[spaceWithOrgEntityType] = SpaceWithOrgsEntitySchema; @@ -286,7 +286,7 @@ const ApplicationEntitySchema = new CFApplicationEntitySchema( service_instances: [ServiceInstancesWithNoBindingsSchema], organization: OrganizationsWithoutSpaces, } - }), + }, null, spaceWithOrgEntityType), routes: [RouteNoAppsSchema], service_bindings: [ServiceBindingsSchema] } @@ -328,24 +328,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity - }; + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; } - }); + const entity = { + ...user.entity, + username: user.metadata.guid + }; + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } +}); entityCache[cfUserEntityType] = CFUserSchema; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts index 5e5ccbe303..ec7741c151 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts @@ -33,9 +33,10 @@ export class CFEntitySchema extends EntitySchema { definition?: Schema, options?: schema.EntityOptions, relationKey?: string, - excludeFromRecursiveDelete?: string[] + excludeFromRecursiveDelete?: string[], + schemaKey?: string ) { - super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, null, excludeFromRecursiveDelete); + super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, schemaKey, excludeFromRecursiveDelete); } } @@ -124,7 +125,8 @@ export class CFApplicationEntitySchema extends CFEntitySchema { export class CFSpaceEntitySchema extends CFEntitySchema { constructor( definition?: Schema, - relationKey?: string + relationKey?: string, + schemaKey?: string ) { super(spaceEntityType, definition, { idAttribute: getAPIResourceGuid }, relationKey, [ domainEntityType, @@ -133,6 +135,6 @@ export class CFSpaceEntitySchema extends CFEntitySchema { servicePlanEntityType, // App Related stackEntityType - ]); + ], schemaKey); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index ab5e375edd..c854e5f873 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; -import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,6 +47,7 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; +import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -59,13 +60,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -186,6 +187,8 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); + getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); + getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -198,13 +201,9 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(app => this.appSpace$.pipe( - map(space => space.entity.organization_guid), - switchMap(orgGuid => { - return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); - }), - filter(org => !!org) - )) + switchMap(() => this.appSpace$), + map(space => space.entity.organization), + filter(org => !!org) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts index 4c88d174d8..6769675f44 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts @@ -1,19 +1,18 @@ import { Store } from '@ngrx/store'; -import { GetAllUserProvidedServices } from '../../../../../../../cloud-foundry/src/actions/user-provided-service.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; +import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -22,11 +21,12 @@ import { IListConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; +import { + UserProvidedServiceActionBuilder, +} from '../../../../../entity-action-builders/user-provided-service.action-builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; -import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; -import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; -import { UserProvidedServiceActionBuilder } from '../../../../../entity-action-builders/user-provided-service.action-builders'; export class CfSpacesUserServiceInstancesDataSource extends ListDataSource { constructor(cfGuid: string, spaceGuid: string, store: Store, listConfig?: IListConfig) { @@ -38,7 +38,7 @@ export class CfSpacesUserServiceInstancesDataSource extends ListDataSource Date: Fri, 29 Nov 2019 18:04:12 +0000 Subject: [PATCH 03/31] Revert "Fix issue where clicking on org and then app fav failed" This reverts commit 0a16203284cc0b42d172972047f5d08cddfecc99. --- .../src/actions/relation.actions.ts | 2 - .../actions/user-provided-service.actions.ts | 6 +-- .../cloud-foundry/src/cf-entity-factory.ts | 38 +++++++++---------- .../src/cf-entity-schema-types.ts | 10 ++--- .../applications/application.service.ts | 29 +++++++------- ...aces-user-service-instances-data-source.ts | 12 +++--- 6 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index d686404b22..985214b8dd 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -21,7 +21,6 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit ) { super(); this.entityType = child.entityType; - this.schemaKey = child.entity.schemaKey; this.options = new HttpRequest( 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, @@ -33,7 +32,6 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit } entity: RequestActionEntity; entityType: string; - schemaKey: string; isId = relationActionId; actions = [ '[Fetch Relations] Start', 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 bdedde7d05..09713ee768 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,5 +1,3 @@ -import { HttpRequest } from '@angular/common/http'; - import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; @@ -12,6 +10,7 @@ import { organizationEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../cf-entity-types'; import { @@ -20,9 +19,10 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; +import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ - createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceEntityType), + createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType), createEntityRelationKey(userProvidedServiceInstanceEntityType, serviceBindingEntityType), createEntityRelationKey(serviceBindingEntityType, applicationEntityType) diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 1da855c14e..5ed43bd63c 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -244,7 +244,7 @@ const SpaceWithOrgsEntitySchema = new CFSpaceEntitySchema({ apps: [ApplicationWithoutSpaceEntitySchema], organization: OrganizationsWithoutSpaces, } -}, null, spaceWithOrgEntityType); +}, spaceWithOrgEntityType); entityCache[spaceWithOrgEntityType] = SpaceWithOrgsEntitySchema; @@ -286,7 +286,7 @@ const ApplicationEntitySchema = new CFApplicationEntitySchema( service_instances: [ServiceInstancesWithNoBindingsSchema], organization: OrganizationsWithoutSpaces, } - }, null, spaceWithOrgEntityType), + }), routes: [RouteNoAppsSchema], service_bindings: [ServiceBindingsSchema] } @@ -328,24 +328,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; + } + const entity = { + ...user.entity, + username: user.metadata.guid }; - } -}); + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } + }); entityCache[cfUserEntityType] = CFUserSchema; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts index ec7741c151..5e5ccbe303 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts @@ -33,10 +33,9 @@ export class CFEntitySchema extends EntitySchema { definition?: Schema, options?: schema.EntityOptions, relationKey?: string, - excludeFromRecursiveDelete?: string[], - schemaKey?: string + excludeFromRecursiveDelete?: string[] ) { - super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, schemaKey, excludeFromRecursiveDelete); + super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, null, excludeFromRecursiveDelete); } } @@ -125,8 +124,7 @@ export class CFApplicationEntitySchema extends CFEntitySchema { export class CFSpaceEntitySchema extends CFEntitySchema { constructor( definition?: Schema, - relationKey?: string, - schemaKey?: string + relationKey?: string ) { super(spaceEntityType, definition, { idAttribute: getAPIResourceGuid }, relationKey, [ domainEntityType, @@ -135,6 +133,6 @@ export class CFSpaceEntitySchema extends CFEntitySchema { servicePlanEntityType, // App Related stackEntityType - ], schemaKey); + ]); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index c854e5f873..ab5e375edd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; +import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,7 +47,6 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; -import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -60,13 +59,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -187,8 +186,6 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); - getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); - getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -201,9 +198,13 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(() => this.appSpace$), - map(space => space.entity.organization), - filter(org => !!org) + switchMap(app => this.appSpace$.pipe( + map(space => space.entity.organization_guid), + switchMap(orgGuid => { + return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); + }), + filter(org => !!org) + )) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts index 6769675f44..4c88d174d8 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts @@ -1,18 +1,19 @@ import { Store } from '@ngrx/store'; +import { GetAllUserProvidedServices } from '../../../../../../../cloud-foundry/src/actions/user-provided-service.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -21,12 +22,11 @@ import { IListConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { - UserProvidedServiceActionBuilder, -} from '../../../../../entity-action-builders/user-provided-service.action-builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; +import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; +import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; +import { UserProvidedServiceActionBuilder } from '../../../../../entity-action-builders/user-provided-service.action-builders'; export class CfSpacesUserServiceInstancesDataSource extends ListDataSource { constructor(cfGuid: string, spaceGuid: string, store: Store, listConfig?: IListConfig) { @@ -38,7 +38,7 @@ export class CfSpacesUserServiceInstancesDataSource extends ListDataSource Date: Fri, 29 Nov 2019 18:16:15 +0000 Subject: [PATCH 04/31] Fix issue where clicking on org and then app fav failed - Issue 1 (slightly unrelated) - app service getspace action used space schema without an org - Issue 2 - app entity validation found it was missing the space - validation process fetched space with custom action - custom action did not contain schema key linked to space schema with org - this lead to org being not stored correctly in store (contained in space rather than seperatly) - Simple fix (see 0a16203284c for harder) - When normalizing prioritise the action's schema over attempting to fetch via entity catalogue + schemaKey (avoids A LOT of plumbing) --- .../entity-relations/entity-relation-tree.ts | 4 +- .../applications/application.service.ts | 29 +++++---- .../entity-catalogue-entity.ts | 13 +--- .../map-multi-endpoint.pipes.ts | 62 +++++++++++-------- .../normalize-entity-request-response.pipe.ts | 14 ----- 5 files changed, 55 insertions(+), 67 deletions(-) delete mode 100644 src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts diff --git a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts index a60a65894e..d758718c6c 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts @@ -1,5 +1,5 @@ -import { EntitySchema } from '../../../store/src/helpers/entity-schema'; import { entityCatalogue } from '../../../core/src/core/entity-catalogue/entity-catalogue.service'; +import { EntitySchema } from '../../../store/src/helpers/entity-schema'; /** * A structure which represents the tree like layout of entity dependencies. For example organization --> space --> routes @@ -18,7 +18,7 @@ export class EntityTreeRelation { /** * Creates an instance of EntityTreeRelation. - * @param [isArray=false] is this a collection of entities (should be paginationed) or not + * @param [isArray=false] is this a collection of entities (should be paginated) or not * @param paramName parameter name of the entity within the schema. For example `space` may be `spaces` (entity.spaces) * @param [path=''] location of the entity within the parent. For example `space` entity maybe be `entity.spaces` */ diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index ab5e375edd..c854e5f873 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; -import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,6 +47,7 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; +import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -59,13 +60,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -186,6 +187,8 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); + getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); + getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -198,13 +201,9 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(app => this.appSpace$.pipe( - map(space => space.entity.organization_guid), - switchMap(orgGuid => { - return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); - }), - filter(org => !!org) - )) + switchMap(() => this.appSpace$), + map(space => space.entity.organization), + filter(org => !!org) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts index 11de55d636..64fa92b6c6 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts @@ -1,10 +1,8 @@ import { ActionReducer, Store } from '@ngrx/store'; -import { normalize } from 'normalizr'; import { AppState, IRequestEntityTypeState } from '../../../../store/src/app-state'; import { EntityPipelineEntity, stratosEndpointGuidKey } from '../../../../store/src/entity-request-pipeline/pipeline.types'; import { EntitySchema } from '../../../../store/src/helpers/entity-schema'; -import { NormalizedResponse } from '../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { APISuccessOrFailedAction, EntityRequestAction } from '../../../../store/src/types/request.types'; import { IEndpointFavMetadata } from '../../../../store/src/types/user-favorites.types'; @@ -89,8 +87,8 @@ export class StratosBaseCatalogueEntity< } return newSchema; }, { - default: entitySchemas.default - }); + default: entitySchemas.default + }); } private getEndpointType(definition: IStratosBaseEntityDefinition) { @@ -217,13 +215,6 @@ export class StratosBaseCatalogueEntity< } - public getNormalizedEntityData(entities: Y | Y[], schemaKey?: string): NormalizedResponse { - const schema = this.getSchema(schemaKey); - if (Array.isArray(entities)) { - return normalize(entities, [schema]); - } - return normalize(entities, schema); - } } export class StratosCatalogueEntity< diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts index 00a2a1f541..d08d04483d 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts @@ -1,4 +1,5 @@ import { Action } from '@ngrx/store'; +import { normalize } from 'normalizr'; import { StratosBaseCatalogueEntity } from '../../../../core/src/core/entity-catalogue/entity-catalogue-entity'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; @@ -10,9 +11,8 @@ import { PipelineResult } from '../entity-request-pipeline.types'; import { getSuccessMapper } from '../pipeline-helpers'; import { endpointErrorsHandlerFactory } from './endpoint-errors.handler'; import { patchActionWithForcedConfig } from './forced-action-type.helpers'; -import { HandledMultiEndpointResponse, JetstreamError } from './handle-multi-endpoints.pipe'; +import { HandledMultiEndpointResponse, JetstreamError, MultiEndpointResponse } from './handle-multi-endpoints.pipe'; import { multiEndpointResponseMergePipe } from './merge-multi-endpoint-data.pipe'; -import { normalizeEntityPipeFactory } from './normalize-entity-request-response.pipe'; const baseErrorHandler = () => 'Api Request Failed'; @@ -56,6 +56,19 @@ function getEntities( }, {}); } +// TODO: Type the output of this pipe. #3976 +function getNormalizedEntityData( + entities: any[], + action: EntityRequestAction, + catalogueEntity: StratosBaseCatalogueEntity) { + // Can patchActionWithForcedConfig be done outside of the pipe? + // This pipe shouldn't have to worry about the multi entity lists. + const patchedAction = patchActionWithForcedConfig(action); + const schema = patchedAction.entity || catalogueEntity.getSchema(patchedAction.schemaKey); + const arraySafeSchema = Array.isArray(schema) ? schema[0] : schema; + return normalize(entities, Array.isArray(entities) ? [arraySafeSchema] : arraySafeSchema); +} + export function mapMultiEndpointResponses( action: EntityRequestAction, catalogueEntity: StratosBaseCatalogueEntity, @@ -63,12 +76,6 @@ export function mapMultiEndpointResponses( multiEndpointResponses: HandledMultiEndpointResponse, actionDispatcher: (actionToDispatch: Action) => void ): PipelineResult { - const normalizeEntityPipe = normalizeEntityPipeFactory( - catalogueEntity, - // Can this be done outside of the pipe? - // This pipe shouldn't have to worry about the multi entity lists. - patchActionWithForcedConfig(action).schemaKey - ); const endpointErrorHandler = endpointErrorsHandlerFactory(actionDispatcher); endpointErrorHandler( action, @@ -84,23 +91,28 @@ export function mapMultiEndpointResponses( errorMessage }; } else { - const responses = multiEndpointResponses.successes.map(normalizeEntityPipe); - const mapped = responses.map(endpointResponse => { - const entities = getEntities(endpointResponse, action); - const parentEntities = entities[catalogueEntity.entityKey]; - return { - response: { - entities, - // If we changed the guid of the entities then make sure this is reflected in the result array. - result: parentEntities ? Object.keys(parentEntities) : endpointResponse.normalizedEntities.result, - }, - totalPages: endpointResponse.totalPages, - totalResults: endpointResponse.totalResults, - success: null - }; - }); - // NormalizedResponse - const response = multiEndpointResponseMergePipe(mapped); + const responses = multiEndpointResponses.successes + .map((responseData: MultiEndpointResponse) => ({ + normalizedEntities: getNormalizedEntityData(responseData.entities, action, catalogueEntity), + endpointGuid: responseData.endpointGuid, + totalResults: responseData.totalResults, + totalPages: responseData.totalPages + })) + .map(endpointResponse => { + const entities = getEntities(endpointResponse, action); + const parentEntities = entities[catalogueEntity.entityKey]; + return { + response: { + entities, + // If we changed the guid of the entities then make sure this is reflected in the result array. + result: parentEntities ? Object.keys(parentEntities) : endpointResponse.normalizedEntities.result, + }, + totalPages: endpointResponse.totalPages, + totalResults: endpointResponse.totalResults, + success: null + }; + }); + const response = multiEndpointResponseMergePipe(responses); return { ...response, success: true, diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts deleted file mode 100644 index ee538455b8..0000000000 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StratosBaseCatalogueEntity } from '../../../../core/src/core/entity-catalogue/entity-catalogue-entity'; -import { MultiEndpointResponse } from './handle-multi-endpoints.pipe'; - -// TODO: Type the output of this pipe. #3976 -export const normalizeEntityPipeFactory = (catalogueEntity: StratosBaseCatalogueEntity, schemaKey?: string) => { - return (responseData: MultiEndpointResponse) => { - return { - normalizedEntities: catalogueEntity.getNormalizedEntityData(responseData.entities, schemaKey), - endpointGuid: responseData.endpointGuid, - totalResults: responseData.totalResults, - totalPages: responseData.totalPages - }; - }; -}; From 4b4c6b5c3023b29a34f1258c19b53b810a9fa6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Mon, 2 Dec 2019 14:18:03 -0300 Subject: [PATCH 05/31] sidepanel preview: cleanup and minor improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../services/cloud-foundry-space.service.ts | 4 + ...cloud-foundry-space-summary.component.html | 2 +- .../application-preview.component.html | 126 +++++++++--------- .../application-preview.component.ts | 27 ++-- .../organization-preview.component.html | 2 +- .../packages/core/src/base-entity-schemas.ts | 3 +- .../entity-catalogue-entity.ts | 2 +- .../entity-catalogue.types.ts | 1 + .../dashboard-base.component.scss | 5 + .../connect-endpoint-dialog.component.ts | 6 +- .../create-endpoint-connect.component.ts | 8 +- .../favorites-meta-card.component.ts | 4 +- .../markdown-preview.component.ts | 1 - .../shared/services/panel-preview.service.ts | 17 ++- 14 files changed, 121 insertions(+), 87 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 371b9874d9..a3a230d5c7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -103,6 +103,10 @@ export class CloudFoundrySpaceService { this.usersCount$ = this.cfUserService.fetchTotalUsers(this.cfGuid, this.orgGuid, this.spaceGuid); } + public fetchApps() { + this.cfEndpointService.fetchApps(); + } + private initialiseSpaceObservables() { this.space$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html index 1f03e7bdb5..3ec288a9fa 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html @@ -75,7 +75,7 @@ + [loading$]="cfSpaceService.loadingApps$" (refresh)="cfSpaceService.fetchApps()"> diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html index 9900b6a612..f47a8f18fe 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -1,68 +1,70 @@ - - -
- - - Summary - - - - - - {{ appSvc.cf?.name}} - - - {{ appSvc.cf?.name}} - - - - - - - -
-
-
+ + + +
+ + + Summary + + + + + + {{ appSvc.cf?.name}} + + + {{ appSvc.cf?.name}} + + + + + + + +
+
+
- - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + +
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts index 5aa2772e67..649e38af6f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts @@ -1,30 +1,27 @@ import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; -import { combineLatest, map } from 'rxjs/operators'; +import { combineLatest, distinct, map } from 'rxjs/operators'; +import { IAppSummary } from '../../../../../core/src/core/cf-api.types'; import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; -import { ApplicationService } from '../../../features/applications/application.service'; -import { getGuids } from '../../../features/applications/application/application-base.component'; +import { EntityInfo } from '../../../../../store/src/types/api.types'; +import { ApplicationData, ApplicationService } from '../../../features/applications/application.service'; @Component({ selector: 'app-application-preview-component', templateUrl: './application-preview.component.html', styleUrls: ['./application-preview.component.scss'], - // useless, necessary to reuse ApplicationService providers: [ ApplicationService, { provide: CF_GUID, - useFactory: getGuids('cf'), - deps: [ActivatedRoute] + useValue: '', }, { provide: APP_GUID, - useFactory: getGuids(), - deps: [ActivatedRoute] + useValue: '', }, ] }) @@ -33,6 +30,7 @@ export class ApplicationPreviewComponent implements PreviewableComponent { title = null; cfEndpointService: object; sshStatus$: Observable; + detailsLoading$: Observable; getFullEndpointApiUrl = getFullEndpointApiUrl; @@ -54,8 +52,15 @@ export class ApplicationPreviewComponent implements PreviewableComponent { }) ); - // this.detailsLoading$ = combineLatest([ - // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now + this.detailsLoading$ = this.applicationService.application$.pipe( + combineLatest( + this.applicationService.appSummary$ + ), + map(([app, appSummary]: [ApplicationData, EntityInfo]) => { + return app.fetching || appSummary.entityRequestInfo.fetching; + }), distinct()); + + // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now // this.cfEndpointService.appsPagObs.fetchingEntities$.pipe( // filter(loading => !loading) // ), diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html index 94359a1d9b..6387b13274 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html @@ -94,7 +94,7 @@ + [loading$]="cfOrgService.loadingApps$" (refresh)="cfOrgService.fetchApps()"> diff --git a/src/frontend/packages/core/src/base-entity-schemas.ts b/src/frontend/packages/core/src/base-entity-schemas.ts index 6a1f2c9ee8..4242afd616 100644 --- a/src/frontend/packages/core/src/base-entity-schemas.ts +++ b/src/frontend/packages/core/src/base-entity-schemas.ts @@ -1,13 +1,14 @@ import { endpointSchemaKey, entityFactory, + systemInfoSchemaKey, userFavouritesSchemaKey, userProfileSchemaKey, - systemInfoSchemaKey, } from '../../store/src/helpers/entity-factory'; import { EntitySchema } from '../../store/src/helpers/entity-schema'; export const STRATOS_ENDPOINT_TYPE = 'stratos'; +export const ENDPOINT_TYPE = 'endpoint'; class StratosEntitySchema extends EntitySchema { constructor(entityType: string) { diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts index 64fa92b6c6..4ca21ea92d 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts @@ -255,7 +255,7 @@ export class StratosCatalogueEndpointEntity extends StratosBaseCatalogueEntity string, - // TODO: attach to PreviewableComponent + // TODO find a way to attach this to PreviewableComponent getPreviewableComponent?: () => object ) { const fullEntity = { diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts index 241c7d5643..6e948950f9 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts @@ -149,6 +149,7 @@ export interface IStratosEntityBuilder { getStatusObservable?(entity: Y): Observable; // TODO This should be used in the entities schema. getGuid(entityMetadata: T): string; + // TODO find a way to attach this to PreviewableComponent getPreviewableComponent?(): object; getLink?(entityMetadata: T): string; getLines?(): EntityRowBuilder[]; diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 82277d48cb..41575a238a 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -55,7 +55,12 @@ $app-sub-header-height: 48px; padding: 0; } &__side-help { + max-width: 600px; min-width: 600px; + + @include breakpoint(mobileonly) { + min-width: auto; + } } } diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index b4c554eba4..788d2bc4ae 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -6,6 +6,8 @@ import { Subscription } from 'rxjs'; import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../shared/components/markdown-preview/markdown-preview.component'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../connect.service'; @@ -27,6 +29,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { @Inject(MAT_DIALOG_DATA) public data: ConnectEndpointConfig, private store: Store, endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { this.connectService = new ConnectEndpointService(store, endpointsService, data); @@ -37,8 +40,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { } showHelp() { - // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); - // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } ngOnDestroy(): void { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index b2282192bb..ff128ef73c 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -5,7 +5,9 @@ import { map } from 'rxjs/operators'; import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../../shared/components/markdown-preview/markdown-preview.component'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; +import { PanelPreviewService } from '../../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../../connect.service'; @@ -25,13 +27,13 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { constructor( private store: Store, - private endpointsService: EndpointsService + private endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { } showHelp() { - // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); - // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } onEnter = (data: ConnectEndpointConfig) => { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index 29c8e373ef..5a91f03f22 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -12,7 +12,7 @@ import { endpointsEntityRequestDataSelector, } from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; -import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { ENDPOINT_TYPE, userFavoritesEntitySchema } from '../../../base-entity-schemas'; import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; import { PanelPreviewService } from '../../services/panel-preview.service'; @@ -113,7 +113,7 @@ export class FavoritesMetaCardComponent { const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); // TODO: use 'endpoint' as constant - if (this.favorite.entityType === 'endpoint') { + if (this.favorite.entityType === ENDPOINT_TYPE) { const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); this.panelPreviewService.show(previewComponent, { title: this.favorite.metadata.name, diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index dab1792c71..9dba74b1d5 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -35,7 +35,6 @@ export class MarkdownPreviewComponent implements PreviewableComponent { ) { } setProps(props: { [key: string]: any }) { - console.log('props', props); this.setDocumentUrl = props.documentUrl; } diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts index dda1d3e374..04b42e4010 100644 --- a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -1,6 +1,7 @@ import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { Router } from '@angular/router'; import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; -import { observeOn, publishReplay, refCount } from 'rxjs/operators'; +import { filter, observeOn, publishReplay, refCount, tap } from 'rxjs/operators'; @Injectable() export class PanelPreviewService { @@ -9,9 +10,14 @@ export class PanelPreviewService { private container: ViewContainerRef; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private router: Router, + ) { this.openedSubject = new BehaviorSubject(false); this.opened$ = this.observeSubject(this.openedSubject); + + this.setupRouterListener(); } public setContainer(container: ViewContainerRef) { @@ -57,6 +63,13 @@ export class PanelPreviewService { this.openedSubject.next(false); } + private setupRouterListener() { + this.router.events.pipe( + filter(() => !!this.container), + tap((e) => this.hide())) + .subscribe(); + } + private observeSubject(subject: Subject) { return subject.asObservable().pipe( publishReplay(1), From 058411db14d13e664a707e80acc04ab4b4597a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Tue, 26 Nov 2019 17:09:58 -0300 Subject: [PATCH 06/31] ui: sidepanel preview widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../src/actions/relation.actions.ts | 14 ++- .../cloud-foundry/src/cf-entity-generator.ts | 12 ++- .../applications/application.service.ts | 39 ++++--- .../cloud-foundry/cloud-foundry.module.ts | 8 +- .../cloud-foundry-endpoint.service.ts | 42 +++++++- .../cloud-foundry-organization.service.ts | 25 +++-- .../services/cloud-foundry-space.service.ts | 34 +++--- ...cloud-foundry-space-summary.component.html | 2 +- .../application-preview.component.html | 68 ++++++++++++ .../application-preview.component.scss | 0 .../application-preview.component.spec.ts | 37 +++++++ .../application-preview.component.ts | 67 ++++++++++++ .../card-cf-info/card-cf-info.component.html | 4 +- .../card-cf-info/card-cf-info.component.ts | 48 +-------- .../card-cf-space-details.component.html | 2 +- .../card-cf-space-details.component.ts | 7 +- .../cf-endpoint-preview.component.html | 76 +++++++++++++ .../cf-endpoint-preview.component.scss | 0 .../cf-endpoint-preview.component.spec.ts | 37 +++++++ .../cf-endpoint-preview.component.ts | 45 ++++++++ .../shared/components/components.module.ts | 23 +++- .../organization-preview.component.html | 102 ++++++++++++++++++ .../organization-preview.component.scss | 0 .../organization-preview.component.spec.ts | 37 +++++++ .../organization-preview.component.ts | 50 +++++++++ .../space-preview.component.html | 101 +++++++++++++++++ .../space-preview.component.scss | 0 .../space-preview.component.spec.ts | 37 +++++++ .../space-preview/space-preview.component.ts | 47 ++++++++ .../packages/core/sass/_all-theme.scss | 5 +- .../entity-catalogue-entity.ts | 9 +- .../entity-catalogue.types.ts | 5 +- .../dashboard-base.component.html | 8 +- .../dashboard-base.component.scss | 6 +- .../dashboard-base.component.ts | 63 +++++------ .../connect-endpoint-dialog.component.ts | 4 +- .../create-endpoint-connect.component.ts | 4 +- .../favorites-meta-card.component.html | 2 +- .../favorites-meta-card.component.ts | 35 +++++- .../markdown-preview.component.html | 7 +- .../markdown-preview.component.scss | 21 ---- .../markdown-preview.component.ts | 14 ++- .../sidepanel-preview.component.html | 8 ++ .../sidepanel-preview.component.scss | 23 ++++ .../sidepanel-preview.component.spec.ts | 37 +++++++ .../sidepanel-preview.component.theme.scss} | 8 +- .../sidepanel-preview.component.ts | 14 +++ .../core/src/shared/previewable-component.ts | 3 + .../shared/services/panel-preview.service.ts | 67 ++++++++++++ .../packages/core/src/shared/shared.module.ts | 20 ++-- .../store/src/actions/dashboard-actions.ts | 13 --- .../store/src/reducers/dashboard-reducer.ts | 19 ++-- 52 files changed, 1130 insertions(+), 229 deletions(-) create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts rename src/frontend/packages/core/src/shared/components/{markdown-preview/markdown-preview.component.theme.scss => sidepanel-preview/sidepanel-preview.component.theme.scss} (67%) create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts create mode 100644 src/frontend/packages/core/src/shared/previewable-component.ts create mode 100644 src/frontend/packages/core/src/shared/services/panel-preview.service.ts diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index ade4c360b2..985214b8dd 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -1,13 +1,11 @@ +import { HttpParams, HttpRequest } from '@angular/common/http'; + import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; -import { - EntityInlineChildAction, - EntityInlineParentAction, -} from '../entity-relations/entity-relations.types'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; -import { RequestEntityLocation, RequestActionEntity } from '../../../store/src/types/request.types'; -import { CFStartAction } from './cf-action.types'; +import { RequestActionEntity, RequestEntityLocation } from '../../../store/src/types/request.types'; import { EntityTreeRelation } from '../entity-relations/entity-relation-tree'; -import { HttpRequest } from '@angular/common/http'; +import { EntityInlineChildAction, EntityInlineParentAction } from '../entity-relations/entity-relations.types'; +import { CFStartAction } from './cf-action.types'; const relationActionId = 'FetchRelationAction'; @@ -27,7 +25,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, { - params: {} + params: new HttpParams() } ); this.parentEntityConfig = parent.entity; 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 c376c55e85..f89dc2ca16 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,5 @@ import * as moment from 'moment'; + import { IService, IServiceBinding, @@ -38,6 +39,7 @@ import { JetstreamError, } 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 { endpointDisconnectRemoveEntitiesReducer } from '../../store/src/reducers/endpoint-disconnect-application.reducer'; import { APIResource } from '../../store/src/types/api.types'; import { IFavoriteMetadata } from '../../store/src/types/user-favorites.types'; @@ -116,7 +118,11 @@ import { spaceActionBuilders } from './entity-action-builders/space.action-build import { stackActionBuilders } from './entity-action-builders/stack-action-builders'; import { userProvidedServiceActionBuilder } from './entity-action-builders/user-provided-service.action-builders'; import { userActionBuilders } from './entity-action-builders/user.action-builders'; +import { ApplicationPreviewComponent } from './shared/components/application-preview/application-preview.component'; import { CfEndpointDetailsComponent } from './shared/components/cf-endpoint-details/cf-endpoint-details.component'; +import { CfEndpointPreviewComponent } from './shared/components/cf-endpoint-preview/cf-endpoint-preview.component'; +import { OrganizationPreviewComponent } from './shared/components/organization-preview/organization-preview.component'; +import { SpacePreviewComponent } from './shared/components/space-preview/space-preview.component'; import { updateApplicationRoutesReducer } from './store/reducers/application-route.reducer'; import { updateOrganizationQuotaReducer } from './store/reducers/organization-quota.reducer'; import { updateOrganizationSpaceReducer } from './store/reducers/organization-space.reducer'; @@ -128,8 +134,6 @@ import { AppStat } from './store/types/app-metadata.types'; import { CFResponse } from './store/types/cf-api.types'; import { GitBranch, GitCommit, GitRepo } from './store/types/git.types'; import { CfUser } from './store/types/user.types'; -import { CfApplicationState } from './store/types/application.types'; -import { EntitySchema } from '../../store/src/helpers/entity-schema'; export interface CFBasePipelineRequestActionMeta { includeRelations?: string[]; @@ -855,6 +859,7 @@ function generateCfEndpointEntity(endpointDefinition: StratosEndpointExtensionDe return new StratosCatalogueEndpointEntity( endpointDefinition, metadata => `/cloud-foundry/${metadata.guid}`, + () => CfEndpointPreviewComponent, ); } @@ -889,6 +894,7 @@ function generateCfApplicationEntity(endpointDefinition: StratosEndpointExtensio }), getLink: metadata => `/applications/${metadata.cfGuid}/${metadata.guid}/summary`, getGuid: metadata => metadata.guid, + getPreviewableComponent: () => ApplicationPreviewComponent, getLines: () => ([ ['Creation Date', (meta) => meta.createdAt] ]) @@ -925,6 +931,7 @@ function generateCfSpaceEntity(endpointDefinition: StratosEndpointExtensionDefin name: space.entity.name, cfGuid: space.entity.cfGuid, }), + getPreviewableComponent: () => SpacePreviewComponent, getLines: () => ([ ['Name', (meta) => meta.name], ]), @@ -960,6 +967,7 @@ function generateCfOrgEntity(endpointDefinition: StratosEndpointExtensionDefinit name: org.entity.name, cfGuid: org.entity.cfGuid, }), + getPreviewableComponent: () => OrganizationPreviewComponent, getLink: metadata => `/cloud-foundry/${metadata.cfGuid}/organizations/${metadata.guid}`, getGuid: metadata => metadata.guid } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index dd94db5dfe..ab5e375edd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -91,21 +91,9 @@ export class ApplicationService { private appEnvVarsService: ApplicationEnvVarsHelper, private paginationMonitorFactory: PaginationMonitorFactory, ) { - this.appEntityService = this.entityServiceFactory.create>( - appGuid, - createGetApplicationAction(appGuid, cfGuid) - ); - const appSummaryEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, appSummaryEntityType); - const actionBuilder = appSummaryEntity.actionOrchestrator.getActionBuilder('get'); - const getAppSummaryAction = actionBuilder(appGuid, cfGuid); - this.appSummaryEntityService = this.entityServiceFactory.create( - appGuid, - getAppSummaryAction - ); - - this.constructCoreObservables(); - this.constructAmalgamatedObservables(); - this.constructStatusObservables(); + if (cfGuid && appGuid) { + this.initialize(cfGuid, appGuid); + } } // NJ: This needs to be cleaned up. So much going on! @@ -161,6 +149,27 @@ export class ApplicationService { ).pipe(publishReplay(1), refCount()); } + public initialize(cfGuid, appGuid) { + this.cfGuid = cfGuid; + this.appGuid = appGuid; + + this.appEntityService = this.entityServiceFactory.create>( + appGuid, + createGetApplicationAction(appGuid, cfGuid) + ); + const appSummaryEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, appSummaryEntityType); + const actionBuilder = appSummaryEntity.actionOrchestrator.getActionBuilder('get'); + const getAppSummaryAction = actionBuilder(appGuid, cfGuid); + this.appSummaryEntityService = this.entityServiceFactory.create( + appGuid, + getAppSummaryAction + ); + + this.constructCoreObservables(); + this.constructAmalgamatedObservables(); + this.constructStatusObservables(); + } + private constructCoreObservables() { // First set up all the base observables this.app$ = this.appEntityService.waitForEntity$; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts index 7e396021dc..c59f4c1a86 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts @@ -36,15 +36,12 @@ import { import { CloudFoundryQuotasComponent, } from '../../../../core/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component'; -import { EndpointListHelper } from '../../../../core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers'; -import { - EndpointsListConfigService, -} from '../../../../core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service'; import { SharedModule } from '../../../../core/src/shared/shared.module'; import { CloudFoundryComponentsModule } from '../../shared/components/components.module'; import { CFEndpointsListConfigService, } from '../../shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service'; +import { ApplicationService } from '../applications/application.service'; import { AddOrganizationComponent } from './add-organization/add-organization.component'; import { CreateOrganizationStepComponent, @@ -64,6 +61,7 @@ import { EditSpaceComponent } from './edit-space/edit-space.component'; import { QuotaDefinitionComponent } from './quota-definition/quota-definition.component'; import { CloudFoundryEndpointService } from './services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from './services/cloud-foundry-organization.service'; +import { CloudFoundrySpaceService } from './services/cloud-foundry-space.service'; import { SpaceQuotaDefinitionComponent } from './space-quota-definition/space-quota-definition.component'; import { CfAdminAddUserWarningComponent } from './tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component'; import { CloudFoundryBuildPacksComponent } from './tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component'; @@ -228,6 +226,8 @@ import { RemoveUserComponent } from './users/remove-user/remove-user.component'; provide: ActiveRouteCfCell, useValue: {} }, + ApplicationService, + CloudFoundrySpaceService, CloudFoundryOrganizationService, CloudFoundryEndpointService, // CfRolesService, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts index 0dc2f48391..ac0dfa57ec 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts @@ -63,6 +63,8 @@ export function appDataSort(app1: APIResource, app2: APIResource): n export class CloudFoundryEndpointService { hasSSHAccess$: Observable; totalMem$: Observable; + description$: Observable; + apiUrl$: Observable; paginationSubscription: any; appsPagObs: PaginationObservables>; usersCount$: Observable; @@ -98,6 +100,7 @@ export class CloudFoundryEndpointService { }) as PaginatedAction; return getAllOrganizationsAction; } + static createGetAllOrganizationsLimitedSchema(cfGuid: string) { const paginationKey = cfGuid ? createEntityRelationPaginationKey(endpointSchemaKey, cfGuid) @@ -138,7 +141,14 @@ export class CloudFoundryEndpointService { private endpointService: EndpointsService, private paginationMonitorFactory: PaginationMonitorFactory ) { - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; + if (cfGuid) { + this.initialize(cfGuid); + } + } + + public initialize(cfGuid) { + this.cfGuid = cfGuid; this.getAllOrgsAction = CloudFoundryEndpointService.createGetAllOrganizations(this.cfGuid) as GetAllOrganizations; this.getAllAppsAction = new GetAllApplications(createEntityRelationPaginationKey('cf', this.cfGuid), this.cfGuid); @@ -197,6 +207,14 @@ export class CloudFoundryEndpointService { ); this.totalMem$ = this.appsPagObs.entities$.pipe(map(apps => this.getMetricFromApps(apps, 'memory'))); + this.apiUrl$ = this.endpoint$.pipe( + map(endpoint => this.getApiEndpointUrl(endpoint.entity.api_endpoint)) + ); + + this.description$ = this.info$.pipe( + map(entity => this.getDescription(entity)) + ); + this.connected$ = this.endpoint$.pipe( map(p => p.entity.connectionStatus === 'connected') ); @@ -256,6 +274,28 @@ export class CloudFoundryEndpointService { this.store.dispatch(this.getAllAppsAction); } + private getApiEndpointUrl(apiEndpoint) { + const path = apiEndpoint.Path ? `/${apiEndpoint.Path}` : ''; + return `${apiEndpoint.Scheme}://${apiEndpoint.Host}${path}`; + } + + private getMetadataFromInfo(entity: EntityInfo>) { + return entity && entity.entity && entity.entity.entity ? entity.entity.entity : null; + } + + private getDescription(entity: EntityInfo>): string { + const metadata = this.getMetadataFromInfo(entity); + if (metadata) { + if (metadata.description) { + return metadata.description + (metadata.build ? ` (${metadata.build})` : ''); + } + if (metadata.support === 'pcfdev@pivotal.io') { + return 'PCF Dev'; + } + } + return '-'; + } + hasCellMetrics(endpointId: string): Observable { return this.endpointService.hasMetrics(endpointId).pipe( switchMap(hasMetrics => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts index a581a419d1..fe0356517e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts @@ -24,19 +24,19 @@ import { ISpaceQuotaDefinition, } from '../../../../../core/src/core/cf-api.types'; import { getEntityFlattenedList, getStartedAppInstanceCount } from '../../../../../core/src/core/cf.helpers'; +import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { CloudFoundryUserProvidedServicesService, } from '../../../../../core/src/shared/services/cloud-foundry-user-provided-services.service'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getOrgRolesString } from '../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; -import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; -import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; export const createOrgQuotaDefinition = (): IOrgQuotaDefinition => ({ memory_limit: -1, @@ -85,7 +85,7 @@ export class CloudFoundryOrganizationService { usersCount$: Observable; constructor( - public activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, + activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, private store: Store, private entityServiceFactory: EntityServiceFactory, private cfUserService: CfUserService, @@ -93,10 +93,12 @@ export class CloudFoundryOrganizationService { private cfEndpointService: CloudFoundryEndpointService, private cfUserProvidedServicesService: CloudFoundryUserProvidedServicesService ) { - this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const orgGuid = activeRouteCfOrgSpace.orgGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; - this.initialiseObservables(); + if (cfGuid && orgGuid) { + this.initialize(cfGuid, orgGuid); + } } public deleteSpace(spaceGuid: string, orgGuid: string, endpointGuid: string) { @@ -110,7 +112,10 @@ export class CloudFoundryOrganizationService { this.cfEndpointService.fetchApps(); } - private initialiseObservables() { + public initialize(cfGuid, orgGuid) { + this.cfGuid = cfGuid; + this.orgGuid = orgGuid; + this.org$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { const relations = [ @@ -133,7 +138,7 @@ export class CloudFoundryOrganizationService { } const orgEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, organizationEntityType); const getOrgActionBuilder = orgEntity.actionOrchestrator.getActionBuilder('get'); - const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid, relations); + const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid, { includeRelations: relations }); const orgEntityService = this.entityServiceFactory.create>( this.orgGuid, getOrgAction @@ -194,8 +199,8 @@ export class CloudFoundryOrganizationService { return CloudFoundryEndpointService.fetchAppCount( this.store, this.paginationMonitorFactory, - this.activeRouteCfOrgSpace.cfGuid, - this.activeRouteCfOrgSpace.orgGuid + this.cfGuid, + this.orgGuid ); } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 4ba4505590..371b9874d9 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { combineLatest, Observable, of } from 'rxjs'; -import { filter, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; +import { map, publishReplay, refCount, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { @@ -15,12 +15,14 @@ import { import { SpaceUserRoleNames } from '../../../../../cloud-foundry/src/store/types/user.types'; import { IApp, IOrgQuotaDefinition, IRoute, ISpace, ISpaceQuotaDefinition } from '../../../../../core/src/core/cf-api.types'; import { getStartedAppInstanceCount } from '../../../../../core/src/core/cf.helpers'; +import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { CloudFoundryUserProvidedServicesService, } from '../../../../../core/src/shared/services/cloud-foundry-user-provided-services.service'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { fetchServiceInstancesCount } from '../../service-catalog/services-helper'; @@ -28,8 +30,6 @@ import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getSpaceRolesString } from '../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService, createOrgQuotaDefinition } from './cloud-foundry-organization.service'; -import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; -import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; @Injectable() export class CloudFoundrySpaceService { @@ -48,6 +48,7 @@ export class CloudFoundrySpaceService { */ spaceQuotaDefinition$: Observable; allowSsh$: Observable; + allowSshStatus$: Observable; totalMem$: Observable; routes$: Observable[]>; serviceInstancesCount$: Observable; @@ -71,18 +72,20 @@ export class CloudFoundrySpaceService { private cfOrgService: CloudFoundryOrganizationService ) { - this.spaceGuid = activeRouteCfOrgSpace.spaceGuid; - this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const spaceGuid = activeRouteCfOrgSpace.spaceGuid; + const orgGuid = activeRouteCfOrgSpace.orgGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; - this.initialiseObservables(); + if (cfGuid && orgGuid && spaceGuid) { + this.initialize(cfGuid, orgGuid, spaceGuid); + } } - public fetchApps() { - this.cfEndpointService.fetchApps(); - } + public initialize(cfGuid: string, orgGuid: string, spaceGuid: string) { + this.cfGuid = cfGuid; + this.orgGuid = orgGuid; + this.spaceGuid = spaceGuid; - private initialiseObservables() { this.initialiseSpaceObservables(); this.initialiseAppObservables(); @@ -126,7 +129,7 @@ export class CloudFoundrySpaceService { this.spaceGuid, getSpaceAction ); - return spaceEntityService.entityObs$.pipe(filter(o => !!o && !!o.entity)); + return spaceEntityService.waitForEntity$; }), publishReplay(1), refCount() @@ -142,6 +145,7 @@ export class CloudFoundrySpaceService { this.cfUserProvidedServicesService.fetchUserProvidedServiceInstancesCount(this.cfGuid, this.orgGuid, this.spaceGuid); this.routes$ = this.space$.pipe(map(o => o.entity.entity.routes)); this.allowSsh$ = this.space$.pipe(map(o => o.entity.entity.allow_ssh ? 'true' : 'false')); + this.allowSshStatus$ = this.allowSsh$.pipe(map(status => status === 'false' ? 'Disabled' : 'Enabled')); this.spaceQuotaDefinition$ = this.space$.pipe( map(q => q.entity.entity.space_quota_definition ? q.entity.entity.space_quota_definition.entity : null) ); @@ -211,9 +215,9 @@ export class CloudFoundrySpaceService { return CloudFoundryEndpointService.fetchAppCount( this.store, this.paginationMonitorFactory, - this.activeRouteCfOrgSpace.cfGuid, - this.activeRouteCfOrgSpace.orgGuid, - this.activeRouteCfOrgSpace.spaceGuid + this.cfGuid, + this.orgGuid, + this.spaceGuid ); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html index 3ec288a9fa..1f03e7bdb5 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html @@ -75,7 +75,7 @@ + [loading$]="cfSpaceService.loadingApps$" (refresh)="cfEndpointService.fetchApps()"> diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html new file mode 100644 index 0000000000..9900b6a612 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -0,0 +1,68 @@ + + + + +
+ + + Summary + + + + + + {{ appSvc.cf?.name}} + + + {{ appSvc.cf?.name}} + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts new file mode 100644 index 0000000000..2d3d25d230 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { ApplicationPreviewComponent } from './application-preview.component'; + +describe('ApplicationPreviewComponent', () => { + let component: ApplicationPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ApplicationPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ApplicationPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts new file mode 100644 index 0000000000..5aa2772e67 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts @@ -0,0 +1,67 @@ +import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { combineLatest, map } from 'rxjs/operators'; + +import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; +import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; +import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { ApplicationService } from '../../../features/applications/application.service'; +import { getGuids } from '../../../features/applications/application/application-base.component'; + +@Component({ + selector: 'app-application-preview-component', + templateUrl: './application-preview.component.html', + styleUrls: ['./application-preview.component.scss'], + // useless, necessary to reuse ApplicationService + providers: [ + ApplicationService, + { + provide: CF_GUID, + useFactory: getGuids('cf'), + deps: [ActivatedRoute] + }, + { + provide: APP_GUID, + useFactory: getGuids(), + deps: [ActivatedRoute] + }, + ] +}) +export class ApplicationPreviewComponent implements PreviewableComponent { + + title = null; + cfEndpointService: object; + sshStatus$: Observable; + + getFullEndpointApiUrl = getFullEndpointApiUrl; + + constructor(public applicationService: ApplicationService) { + } + + setProps(props: { [key: string]: any }) { + this.title = props.title; + + this.applicationService.initialize(props.cfGuid, props.guid); + this.sshStatus$ = this.applicationService.application$.pipe( + combineLatest(this.applicationService.appSpace$), + map(([app, space]) => { + if (!space.entity.allow_ssh) { + return 'Disabled by the space'; + } else { + return app.app.entity.enable_ssh ? 'Yes' : 'No'; + } + }) + ); + + // this.detailsLoading$ = combineLatest([ + // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now + // this.cfEndpointService.appsPagObs.fetchingEntities$.pipe( + // filter(loading => !loading) + // ), + // ]).pipe( + // map(() => false), + // startWith(true) + // ); + } +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html index 9ce13dc103..e8a2a95463 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html @@ -6,8 +6,8 @@ \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 0884263a2b..82277d48cb 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -16,6 +16,10 @@ $app-sub-header-height: 48px; } &__side-help-outer { z-index: 9999; + + @include breakpoint(mobileonly) { + left: 0; + } } &__side-help-button { position: absolute; @@ -51,7 +55,7 @@ $app-sub-header-height: 48px; padding: 0; } &__side-help { - max-width: 600px; + min-width: 600px; } } diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts index 00a22d86dd..f7d95b7362 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts @@ -1,6 +1,6 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { Portal } from '@angular/cdk/portal'; -import { Component, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import { MatDrawer } from '@angular/material'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router } from '@angular/router'; import { Store } from '@ngrx/store'; @@ -13,12 +13,7 @@ import { cfInfoEntityType } from '../../../../../cloud-foundry/src/cf-entity-typ import { CfInfoDefinitionActionBuilders, } from '../../../../../cloud-foundry/src/entity-action-builders/cf-info.action-builders'; -import { - CloseSideHelp, - CloseSideNav, - DisableMobileNav, - EnableMobileNav, -} from '../../../../../store/src/actions/dashboard-actions'; +import { CloseSideNav, DisableMobileNav, EnableMobileNav } from '../../../../../store/src/actions/dashboard-actions'; import { GetUserFavoritesAction } from '../../../../../store/src/actions/user-favourites-actions/get-user-favorites-action'; import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { DashboardState } from '../../../../../store/src/reducers/dashboard-reducer'; @@ -29,6 +24,7 @@ import { CustomizationService } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IEntityMetadata } from '../../../core/entity-catalogue/entity-catalogue.types'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { PageHeaderService } from './../../../core/page-header-service/page-header.service'; import { SideNavItem } from './../side-nav/side-nav.component'; @@ -39,16 +35,28 @@ import { SideNavItem } from './../side-nav/side-nav.component'; styleUrls: ['./dashboard-base.component.scss'] }) -export class DashboardBaseComponent implements OnInit, OnDestroy { +export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit { public activeTabLabel$: Observable; public subNavData$: Observable<[string, Portal]>; public isMobile$: Observable; public sideNavMode$: Observable; public sideNavMode: string; public mainNavState$: Observable<{ mode: string; opened: boolean; iconMode: boolean }>; - public rightNavState$: Observable<{ opened: boolean; documentUrl: string; }>; + public rightNavState$: Observable<{ opened: boolean, component?: object, props?: object }>; private dashboardState$: Observable; + public noMargin$: Observable; + private closeSub: Subscription; + private mobileSub: Subscription; private drawer: MatDrawer; + public iconModeOpen = false; + public sideNavWidth = 54; + + sideNavTabs: SideNavItem[] = this.getNavigationRoutes(); + sideNaveMode = 'side'; + + @ViewChild('previewPanelContainer', { read: ViewContainerRef, static: false }) previewPanelContainer: ViewContainerRef; + + @ViewChild('content', { static: false }) public content; constructor( public pageHeaderService: PageHeaderService, @@ -59,6 +67,7 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { private endpointsService: EndpointsService, public tabNavService: TabNavService, private ngZone: NgZone, + public panelPreviewService: PanelPreviewService, private cs: CustomizationService ) { this.noMargin$ = this.router.events.pipe( @@ -89,24 +98,11 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { } }) ); - this.rightNavState$ = this.dashboardState$.pipe( - map(state => ({ - opened: !!state.sideHelpDocument && state.sideHelpOpen, - documentUrl: state.sideHelpDocument - })) - ); + this.mobileSub = this.isMobile$ .subscribe(isMobile => isMobile ? this.store.dispatch(new EnableMobileNav()) : this.store.dispatch(new DisableMobileNav())); } - public helpDocumentUrl: string; - - private closeSub: Subscription; - - public noMargin$: Observable; - - private mobileSub: Subscription; - @ViewChild('sidenav', { static: false }) set sidenav(drawer: MatDrawer) { this.drawer = drawer; if (!this.closeSub) { @@ -119,15 +115,6 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { } } - @ViewChild('content', { static: true }) public content; - - sideNavTabs: SideNavItem[] = this.getNavigationRoutes(); - - sideNaveMode = 'side'; - - public iconModeOpen = false; - public sideNavWidth = 54; - public redrawSideNav() { // We need to do this to ensure there isn't a space left behind // when going from mobile to desktop @@ -140,6 +127,14 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { this.store.dispatch(new GetCurrentUsersRelations()); } + sideHelpClosed() { + this.panelPreviewService.hide(); + } + + ngAfterViewInit() { + this.panelPreviewService.setContainer(this.previewPanelContainer); + } + ngOnInit() { this.subNavData$ = combineLatest( this.tabNavService.getCurrentTabHeaderObservable().pipe( @@ -173,10 +168,6 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { return false; } - public sideHelpClosed() { - this.store.dispatch(new CloseSideHelp()); - } - private getNavigationRoutes(): SideNavItem[] { let navItems = this.collectNavigationRoutes('', this.router.config); diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index e418b05aff..b4c554eba4 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -3,7 +3,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; -import { ShowSideHelp } from '../../../../../store/src/actions/dashboard-actions'; import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; @@ -38,7 +37,8 @@ export class ConnectEndpointDialogComponent implements OnDestroy { } showHelp() { - this.store.dispatch(new ShowSideHelp(this.helpDocumentUrl)); + // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); + // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); } ngOnDestroy(): void { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index d513432c29..b2282192bb 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -3,7 +3,6 @@ import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { ShowSideHelp } from '../../../../../../store/src/actions/dashboard-actions'; import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; @@ -31,7 +30,8 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { } showHelp() { - this.store.dispatch(new ShowSideHelp(this.helpDocumentUrl)); + // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); + // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); } onEnter = (data: ConnectEndpointConfig) => { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html index 53a2b5e295..9af11474b9 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html @@ -1,5 +1,5 @@ diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index a252d8b69e..29c8e373ef 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -7,10 +7,15 @@ import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { RemoveUserFavoriteAction, } from '../../../../../store/src/actions/user-favourites-actions/remove-user-favorite-action'; -import { endpointEntitiesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; +import { + endpointEntitiesSelector, + endpointsEntityRequestDataSelector, +} from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; +import { PanelPreviewService } from '../../services/panel-preview.service'; import { ComponentEntityMonitorConfig, StratosStatus } from '../../shared.types'; import { ConfirmationDialogConfig } from '../confirmation-dialog.config'; import { ConfirmationDialogService } from '../confirmation-dialog.service'; @@ -97,7 +102,33 @@ export class FavoritesMetaCardComponent { } } - constructor(private store: Store, private confirmDialog: ConfirmationDialogService) { } + constructor( + private store: Store, + private confirmDialog: ConfirmationDialogService, + private panelPreviewService: PanelPreviewService + ) { } + + previewPanel() { + const catalogueEntity = entityCatalogue.getEntity(this.favorite.endpointType, this.favorite.entityType); + const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); + + // TODO: use 'endpoint' as constant + if (this.favorite.entityType === 'endpoint') { + const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + entity$, + cfGuid: this.favorite.endpointId + }); + } else { + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + cfGuid: this.favorite.metadata.cfGuid, + orgGuid: this.favorite.metadata.orgGuid, + guid: this.favorite.metadata.guid + }); + } + } public setConfirmation(prettyName: string, favorite: UserFavorite) { this.confirmation = new ConfirmationDialogConfig( diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html index 4ad4077847..972ac85319 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html @@ -1,6 +1,3 @@ -
-
-

{{ title }}

-
+
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss index 2a7eaa882f..e69de29bb2 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss @@ -1,21 +0,0 @@ -.markdown-preview { - display: flex; - flex-direction: column; - height: 100vh; - &__header { - display: flex; - flex: 0 0 56px; - height: 56px; - - h1 { - align-self: center; - font-size: 20px; - margin-left: 24px; - } - } - &__content { - flex: 1; - overflow: auto; - padding: 10px 24px; - } -} diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index 97384669d0..dab1792c71 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -4,13 +4,14 @@ import { DomSanitizer } from '@angular/platform-browser'; import * as markdown from 'marked'; import { LoggerService } from '../../../core/logger.service'; +import { PreviewableComponent } from '../../previewable-component'; @Component({ selector: 'app-markdown-preview', templateUrl: './markdown-preview.component.html', styleUrls: ['./markdown-preview.component.scss'] }) -export class MarkdownPreviewComponent { +export class MarkdownPreviewComponent implements PreviewableComponent { markdownHtml: string; documentUrl: string; @@ -27,7 +28,16 @@ export class MarkdownPreviewComponent { @ViewChild('markdown', { static: true }) public markdown: ElementRef; - constructor(private httpClient: HttpClient, private logger: LoggerService, private domSanitizer: DomSanitizer) { } + constructor( + private httpClient: HttpClient, + private logger: LoggerService, + private domSanitizer: DomSanitizer + ) { } + + setProps(props: { [key: string]: any }) { + console.log('props', props); + this.setDocumentUrl = props.documentUrl; + } private loadDocument() { this.httpClient.get(this.documentUrl, { responseType: 'text' }).subscribe( diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html new file mode 100644 index 0000000000..9e95cac0d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html @@ -0,0 +1,8 @@ +
+
+

{{ title }}

+
+
+ +
+
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss new file mode 100644 index 0000000000..c3afff37d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss @@ -0,0 +1,23 @@ +.sidepanel-preview { + display: flex; + flex-direction: column; + height: 100vh; + &__header { + display: flex; + flex: 0 0 56px; + height: 56px; + + h1 { + align-self: center; + font-size: 20px; + margin-left: 24px; + } + } + &__content { + flex: 1; + overflow-x: hidden; + overflow-y: auto; + padding: 12px 15px; + position: relative; + } +} diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts new file mode 100644 index 0000000000..2eb1505ae2 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { SidepanelPreviewComponent } from './sidepanel-preview.component'; + +describe('SidepanelPreviewComponent', () => { + let component: SidepanelPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SidepanelPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SidepanelPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss similarity index 67% rename from src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss rename to src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss index 926eb8127e..c84bed25dc 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss @@ -1,12 +1,14 @@ -@mixin app-markdown-preview-theme($theme, $app-theme) { +@mixin app-sidepanel-preview-theme($theme, $app-theme) { $primary: map-get($theme, primary); - .markdown-preview__header { + .sidepanel-preview__header { background-color: mat-color($primary); color: mat-contrast($primary, 500); } - .markdown-preview__content { + .sidepanel-preview__content { + background-color: map-get($app-theme, app-background-color); + > h1:first-child { height: 0; margin: 0; diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts new file mode 100644 index 0000000000..315d11c160 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts @@ -0,0 +1,14 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-sidepanel-preview', + templateUrl: './sidepanel-preview.component.html', + styleUrls: ['./sidepanel-preview.component.scss'] +}) +export class SidepanelPreviewComponent { + + @Input() + title: string; + + constructor() { } +} diff --git a/src/frontend/packages/core/src/shared/previewable-component.ts b/src/frontend/packages/core/src/shared/previewable-component.ts new file mode 100644 index 0000000000..40410d6f07 --- /dev/null +++ b/src/frontend/packages/core/src/shared/previewable-component.ts @@ -0,0 +1,3 @@ +export interface PreviewableComponent { + setProps(props: { [key: string]: any }): void; +} diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts new file mode 100644 index 0000000000..dda1d3e374 --- /dev/null +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -0,0 +1,67 @@ +import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; +import { observeOn, publishReplay, refCount } from 'rxjs/operators'; + +@Injectable() +export class PanelPreviewService { + private openedSubject: BehaviorSubject; + public opened$: Observable; + + private container: ViewContainerRef; + + constructor(private componentFactoryResolver: ComponentFactoryResolver) { + this.openedSubject = new BehaviorSubject(false); + this.opened$ = this.observeSubject(this.openedSubject); + } + + public setContainer(container: ViewContainerRef) { + if (this.container) { + throw new Error('PanelPreviewService: container already set'); + } + + this.container = container; + } + + public show(component: object, props?: { [key: string]: any }) { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.render(component, props); + this.openedSubject.next(true); + } + + public hide() { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.openedSubject.next(false); + } + + render(component: object, props: { [key: string]: any }) { + if (this.container.length) { + this.container.remove(0); + } + + const factory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(component as any); + const componentRef: ComponentRef = this.container.createComponent(factory); + + if (props) { + componentRef.instance.setProps(props); + } + } + + public clear() { + this.container.clear(); + this.openedSubject.next(false); + } + + private observeSubject(subject: Subject) { + return subject.asObservable().pipe( + publishReplay(1), + refCount(), + observeOn(asapScheduler) + ); + } +} diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 12aae65f83..a1d52f874c 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -58,8 +58,12 @@ import { TableCellStatusDirective } from './components/list/list-table/table-cel import { TableComponent } from './components/list/list-table/table.component'; import { listTableComponents } from './components/list/list-table/table.types'; import { EndpointCardComponent } from './components/list/list-types/endpoint/endpoint-card/endpoint-card.component'; +import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; +import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; import { ListComponent } from './components/list/list.component'; import { ListConfig } from './components/list/list.component.types'; +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'; import { LogViewerComponent } from './components/log-viewer/log-viewer.component'; import { MarkdownContentObserverDirective } from './components/markdown-preview/markdown-content-observer.directive'; @@ -78,6 +82,7 @@ import { PageSubNavComponent } from './components/page-sub-nav/page-sub-nav.comp import { PollingIndicatorComponent } from './components/polling-indicator/polling-indicator.component'; import { RingChartComponent } from './components/ring-chart/ring-chart.component'; import { RoutingIndicatorComponent } from './components/routing-indicator/routing-indicator.component'; +import { SidepanelPreviewComponent } from './components/sidepanel-preview/sidepanel-preview.component'; import { SimpleUsageChartComponent } from './components/simple-usage-chart/simple-usage-chart.component'; import { SnackBarReturnComponent } from './components/snackbar-return/snackbar-return.component'; import { SshViewerComponent } from './components/ssh-viewer/ssh-viewer.component'; @@ -111,11 +116,8 @@ import { UsageBytesPipe } from './pipes/usage-bytes.pipe'; import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; +import { PanelPreviewService } from './services/panel-preview.service'; import { UserPermissionDirective } from './user-permission.directive'; -import { SimpleListComponent } from './components/list/simple-list/simple-list.component'; -import { ListHostDirective } from './components/list/simple-list/list-host.directive'; -import { EndpointListHelper } from './components/list/list-types/endpoint/endpoint-list.helpers'; -import { EndpointsListConfigService } from './components/list/list-types/endpoint/endpoints-list-config.service'; /* tslint:disable:max-line-length */ @@ -211,13 +213,13 @@ import { EndpointsListConfigService } from './components/list/list-types/endpoin BreadcrumbsComponent, PageSubNavSectionComponent, EntitySummaryTitleComponent, - MarkdownPreviewComponent, MarkdownContentObserverDirective, SnackBarReturnComponent, PollingIndicatorComponent, UnlimitedInputComponent, SimpleListComponent, ListHostDirective, + SidepanelPreviewComponent ], exports: [ ApplicationStateIconPipe, @@ -300,24 +302,26 @@ import { EndpointsListConfigService } from './components/list/list-types/endpoin AppNameUniqueDirective, SimpleUsageChartComponent, EntitySummaryTitleComponent, - MarkdownPreviewComponent, MarkdownContentObserverDirective, AppNameUniqueDirective, PollingIndicatorComponent, UnlimitedInputComponent, SimpleListComponent, - ListHostDirective + ListHostDirective, + SidepanelPreviewComponent, ], entryComponents: [ DialogConfirmComponent, EnvVarViewComponent, - SnackBarReturnComponent + SnackBarReturnComponent, + MarkdownPreviewComponent, ], providers: [ ListConfig, ApplicationStateService, EndpointListHelper, EndpointsListConfigService, + PanelPreviewService, // CfUserService, ConfirmationDialogService, EntityMonitorFactory, diff --git a/src/frontend/packages/store/src/actions/dashboard-actions.ts b/src/frontend/packages/store/src/actions/dashboard-actions.ts index 88c8f8019c..08e6b3fb42 100644 --- a/src/frontend/packages/store/src/actions/dashboard-actions.ts +++ b/src/frontend/packages/store/src/actions/dashboard-actions.ts @@ -10,10 +10,6 @@ export const SET_HEADER_EVENT = '[Dashboard] Set header event'; export const ENABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Enable mobile nav'; export const DISABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Disable mobile nav'; -export const SHOW_SIDE_HELP = '[Dashboard] Show side help'; -export const CLOSE_SIDE_HELP = '[Dashboard] Close side help'; - - export const TIMEOUT_SESSION = '[Dashboard] Timeout Session'; export const ENABLE_POLLING = '[Dashboard] Enable Polling'; @@ -36,15 +32,6 @@ export class ToggleSideNav implements Action { type = TOGGLE_SIDE_NAV; } -export class ShowSideHelp implements Action { - constructor(public document: string) { } - type = SHOW_SIDE_HELP; -} - -export class CloseSideHelp implements Action { - type = CLOSE_SIDE_HELP; -} - export class SetHeaderEvent implements Action { constructor(public minimised = false) { } type = SET_HEADER_EVENT; diff --git a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts index 5963b912b9..788c8307a2 100644 --- a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts +++ b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts @@ -1,5 +1,4 @@ import { - CLOSE_SIDE_HELP, CLOSE_SIDE_NAV, DISABLE_SIDE_NAV_MOBILE_MODE, ENABLE_POLLING, @@ -7,9 +6,10 @@ import { HYDRATE_DASHBOARD_STATE, HydrateDashboardStateAction, OPEN_SIDE_NAV, + SET_HEADER_EVENT, + SetHeaderEvent, SetPollingEnabledAction, SetSessionTimeoutAction, - SHOW_SIDE_HELP, TIMEOUT_SESSION, TOGGLE_SIDE_NAV, } from '../actions/dashboard-actions'; @@ -21,8 +21,7 @@ export interface DashboardState { isMobile: boolean; isMobileNavOpen: boolean; sideNavPinned: boolean; - sideHelpOpen: boolean; - sideHelpDocument: string; + headerEventMinimized: boolean; } export const defaultDashboardState: DashboardState = { @@ -32,8 +31,7 @@ export const defaultDashboardState: DashboardState = { isMobile: false, isMobileNavOpen: false, sideNavPinned: true, - sideHelpOpen: false, - sideHelpDocument: null, + headerEventMinimized: false, }; export function dashboardReducer(state: DashboardState = defaultDashboardState, action): DashboardState { @@ -57,10 +55,11 @@ export function dashboardReducer(state: DashboardState = defaultDashboardState, return { ...state, isMobile: true, isMobileNavOpen: false }; case DISABLE_SIDE_NAV_MOBILE_MODE: return { ...state, isMobile: false, isMobileNavOpen: false }; - case SHOW_SIDE_HELP: - return { ...state, sideHelpOpen: true, sideHelpDocument: action.document }; - case CLOSE_SIDE_HELP: - return { ...state, sideHelpOpen: false, sideHelpDocument: '' }; + case SET_HEADER_EVENT: + const setHeaderEvent = action as SetHeaderEvent; + return { + ...state, headerEventMinimized: setHeaderEvent.minimised + }; case TIMEOUT_SESSION: const timeoutSessionAction = action as SetSessionTimeoutAction; return { From 66e2c1881fcc03fd28727593208c1f26aef6b028 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 29 Nov 2019 17:16:07 +0000 Subject: [PATCH 07/31] Fix issue where clicking on org and then app fav failed - Issue 1 (slightly unrelated) - app service getspace action used space entity without an org - Issue 2 - app entity validation found it was missing the space - validation process fetched space with custom action - custom action did not contain schema key to use space with org schema - org was not stored correctly in store (contained in space rather than seperatly) - Still to do - Fix for issue 2 would need to be expanded to ALL schema's with inline entities - These new schemas would need to be added to their entities - All usages would have to provide the overriding schemaKey --- .../src/actions/relation.actions.ts | 2 + .../actions/user-provided-service.actions.ts | 6 +-- .../cloud-foundry/src/cf-entity-factory.ts | 38 +++++++++---------- .../src/cf-entity-schema-types.ts | 10 +++-- .../applications/application.service.ts | 29 +++++++------- ...aces-user-service-instances-data-source.ts | 12 +++--- 6 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index 985214b8dd..d686404b22 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -21,6 +21,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit ) { super(); this.entityType = child.entityType; + this.schemaKey = child.entity.schemaKey; this.options = new HttpRequest( 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, @@ -32,6 +33,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit } entity: RequestActionEntity; entityType: string; + schemaKey: string; isId = relationActionId; actions = [ '[Fetch Relations] Start', 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 09713ee768..bdedde7d05 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,3 +1,5 @@ +import { HttpRequest } from '@angular/common/http'; + import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; @@ -10,7 +12,6 @@ import { organizationEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../cf-entity-types'; import { @@ -19,10 +20,9 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; -import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ - createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), + createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType), createEntityRelationKey(userProvidedServiceInstanceEntityType, serviceBindingEntityType), createEntityRelationKey(serviceBindingEntityType, applicationEntityType) diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 5ed43bd63c..1da855c14e 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -244,7 +244,7 @@ const SpaceWithOrgsEntitySchema = new CFSpaceEntitySchema({ apps: [ApplicationWithoutSpaceEntitySchema], organization: OrganizationsWithoutSpaces, } -}, spaceWithOrgEntityType); +}, null, spaceWithOrgEntityType); entityCache[spaceWithOrgEntityType] = SpaceWithOrgsEntitySchema; @@ -286,7 +286,7 @@ const ApplicationEntitySchema = new CFApplicationEntitySchema( service_instances: [ServiceInstancesWithNoBindingsSchema], organization: OrganizationsWithoutSpaces, } - }), + }, null, spaceWithOrgEntityType), routes: [RouteNoAppsSchema], service_bindings: [ServiceBindingsSchema] } @@ -328,24 +328,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity - }; + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; } - }); + const entity = { + ...user.entity, + username: user.metadata.guid + }; + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } +}); entityCache[cfUserEntityType] = CFUserSchema; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts index 5e5ccbe303..ec7741c151 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts @@ -33,9 +33,10 @@ export class CFEntitySchema extends EntitySchema { definition?: Schema, options?: schema.EntityOptions, relationKey?: string, - excludeFromRecursiveDelete?: string[] + excludeFromRecursiveDelete?: string[], + schemaKey?: string ) { - super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, null, excludeFromRecursiveDelete); + super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, schemaKey, excludeFromRecursiveDelete); } } @@ -124,7 +125,8 @@ export class CFApplicationEntitySchema extends CFEntitySchema { export class CFSpaceEntitySchema extends CFEntitySchema { constructor( definition?: Schema, - relationKey?: string + relationKey?: string, + schemaKey?: string ) { super(spaceEntityType, definition, { idAttribute: getAPIResourceGuid }, relationKey, [ domainEntityType, @@ -133,6 +135,6 @@ export class CFSpaceEntitySchema extends CFEntitySchema { servicePlanEntityType, // App Related stackEntityType - ]); + ], schemaKey); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index ab5e375edd..c854e5f873 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; -import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,6 +47,7 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; +import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -59,13 +60,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -186,6 +187,8 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); + getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); + getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -198,13 +201,9 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(app => this.appSpace$.pipe( - map(space => space.entity.organization_guid), - switchMap(orgGuid => { - return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); - }), - filter(org => !!org) - )) + switchMap(() => this.appSpace$), + map(space => space.entity.organization), + filter(org => !!org) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts index 4c88d174d8..6769675f44 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts @@ -1,19 +1,18 @@ import { Store } from '@ngrx/store'; -import { GetAllUserProvidedServices } from '../../../../../../../cloud-foundry/src/actions/user-provided-service.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; +import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -22,11 +21,12 @@ import { IListConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; +import { + UserProvidedServiceActionBuilder, +} from '../../../../../entity-action-builders/user-provided-service.action-builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; -import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; -import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; -import { UserProvidedServiceActionBuilder } from '../../../../../entity-action-builders/user-provided-service.action-builders'; export class CfSpacesUserServiceInstancesDataSource extends ListDataSource { constructor(cfGuid: string, spaceGuid: string, store: Store, listConfig?: IListConfig) { @@ -38,7 +38,7 @@ export class CfSpacesUserServiceInstancesDataSource extends ListDataSource Date: Fri, 29 Nov 2019 18:04:12 +0000 Subject: [PATCH 08/31] Revert "Fix issue where clicking on org and then app fav failed" This reverts commit 0a16203284cc0b42d172972047f5d08cddfecc99. --- .../src/actions/relation.actions.ts | 2 - .../actions/user-provided-service.actions.ts | 6 +-- .../cloud-foundry/src/cf-entity-factory.ts | 38 +++++++++---------- .../src/cf-entity-schema-types.ts | 10 ++--- .../applications/application.service.ts | 29 +++++++------- ...aces-user-service-instances-data-source.ts | 12 +++--- 6 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index d686404b22..985214b8dd 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -21,7 +21,6 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit ) { super(); this.entityType = child.entityType; - this.schemaKey = child.entity.schemaKey; this.options = new HttpRequest( 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, @@ -33,7 +32,6 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit } entity: RequestActionEntity; entityType: string; - schemaKey: string; isId = relationActionId; actions = [ '[Fetch Relations] Start', 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 bdedde7d05..09713ee768 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,5 +1,3 @@ -import { HttpRequest } from '@angular/common/http'; - import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; @@ -12,6 +10,7 @@ import { organizationEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../cf-entity-types'; import { @@ -20,9 +19,10 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; +import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ - createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceEntityType), + createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType), createEntityRelationKey(userProvidedServiceInstanceEntityType, serviceBindingEntityType), createEntityRelationKey(serviceBindingEntityType, applicationEntityType) diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 1da855c14e..5ed43bd63c 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -244,7 +244,7 @@ const SpaceWithOrgsEntitySchema = new CFSpaceEntitySchema({ apps: [ApplicationWithoutSpaceEntitySchema], organization: OrganizationsWithoutSpaces, } -}, null, spaceWithOrgEntityType); +}, spaceWithOrgEntityType); entityCache[spaceWithOrgEntityType] = SpaceWithOrgsEntitySchema; @@ -286,7 +286,7 @@ const ApplicationEntitySchema = new CFApplicationEntitySchema( service_instances: [ServiceInstancesWithNoBindingsSchema], organization: OrganizationsWithoutSpaces, } - }, null, spaceWithOrgEntityType), + }), routes: [RouteNoAppsSchema], service_bindings: [ServiceBindingsSchema] } @@ -328,24 +328,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; + } + const entity = { + ...user.entity, + username: user.metadata.guid }; - } -}); + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } + }); entityCache[cfUserEntityType] = CFUserSchema; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts index ec7741c151..5e5ccbe303 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts @@ -33,10 +33,9 @@ export class CFEntitySchema extends EntitySchema { definition?: Schema, options?: schema.EntityOptions, relationKey?: string, - excludeFromRecursiveDelete?: string[], - schemaKey?: string + excludeFromRecursiveDelete?: string[] ) { - super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, schemaKey, excludeFromRecursiveDelete); + super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, null, excludeFromRecursiveDelete); } } @@ -125,8 +124,7 @@ export class CFApplicationEntitySchema extends CFEntitySchema { export class CFSpaceEntitySchema extends CFEntitySchema { constructor( definition?: Schema, - relationKey?: string, - schemaKey?: string + relationKey?: string ) { super(spaceEntityType, definition, { idAttribute: getAPIResourceGuid }, relationKey, [ domainEntityType, @@ -135,6 +133,6 @@ export class CFSpaceEntitySchema extends CFEntitySchema { servicePlanEntityType, // App Related stackEntityType - ], schemaKey); + ]); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index c854e5f873..ab5e375edd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; +import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,7 +47,6 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; -import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -60,13 +59,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -187,8 +186,6 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); - getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); - getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -201,9 +198,13 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(() => this.appSpace$), - map(space => space.entity.organization), - filter(org => !!org) + switchMap(app => this.appSpace$.pipe( + map(space => space.entity.organization_guid), + switchMap(orgGuid => { + return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); + }), + filter(org => !!org) + )) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts index 6769675f44..4c88d174d8 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts @@ -1,18 +1,19 @@ import { Store } from '@ngrx/store'; +import { GetAllUserProvidedServices } from '../../../../../../../cloud-foundry/src/actions/user-provided-service.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -21,12 +22,11 @@ import { IListConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { - UserProvidedServiceActionBuilder, -} from '../../../../../entity-action-builders/user-provided-service.action-builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; +import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; +import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; +import { UserProvidedServiceActionBuilder } from '../../../../../entity-action-builders/user-provided-service.action-builders'; export class CfSpacesUserServiceInstancesDataSource extends ListDataSource { constructor(cfGuid: string, spaceGuid: string, store: Store, listConfig?: IListConfig) { @@ -38,7 +38,7 @@ export class CfSpacesUserServiceInstancesDataSource extends ListDataSource Date: Fri, 29 Nov 2019 18:16:15 +0000 Subject: [PATCH 09/31] Fix issue where clicking on org and then app fav failed - Issue 1 (slightly unrelated) - app service getspace action used space schema without an org - Issue 2 - app entity validation found it was missing the space - validation process fetched space with custom action - custom action did not contain schema key linked to space schema with org - this lead to org being not stored correctly in store (contained in space rather than seperatly) - Simple fix (see 0a16203284c for harder) - When normalizing prioritise the action's schema over attempting to fetch via entity catalogue + schemaKey (avoids A LOT of plumbing) --- .../entity-relations/entity-relation-tree.ts | 4 +- .../applications/application.service.ts | 29 +++++---- .../entity-catalogue-entity.ts | 13 +--- .../map-multi-endpoint.pipes.ts | 62 +++++++++++-------- .../normalize-entity-request-response.pipe.ts | 14 ----- 5 files changed, 55 insertions(+), 67 deletions(-) delete mode 100644 src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts diff --git a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts index a60a65894e..d758718c6c 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts @@ -1,5 +1,5 @@ -import { EntitySchema } from '../../../store/src/helpers/entity-schema'; import { entityCatalogue } from '../../../core/src/core/entity-catalogue/entity-catalogue.service'; +import { EntitySchema } from '../../../store/src/helpers/entity-schema'; /** * A structure which represents the tree like layout of entity dependencies. For example organization --> space --> routes @@ -18,7 +18,7 @@ export class EntityTreeRelation { /** * Creates an instance of EntityTreeRelation. - * @param [isArray=false] is this a collection of entities (should be paginationed) or not + * @param [isArray=false] is this a collection of entities (should be paginated) or not * @param paramName parameter name of the entity within the schema. For example `space` may be `spaces` (entity.spaces) * @param [path=''] location of the entity within the parent. For example `space` entity maybe be `entity.spaces` */ diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index ab5e375edd..c854e5f873 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; -import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,6 +47,7 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; +import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -59,13 +60,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -186,6 +187,8 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); + getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); + getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -198,13 +201,9 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(app => this.appSpace$.pipe( - map(space => space.entity.organization_guid), - switchMap(orgGuid => { - return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); - }), - filter(org => !!org) - )) + switchMap(() => this.appSpace$), + map(space => space.entity.organization), + filter(org => !!org) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts index 11de55d636..64fa92b6c6 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts @@ -1,10 +1,8 @@ import { ActionReducer, Store } from '@ngrx/store'; -import { normalize } from 'normalizr'; import { AppState, IRequestEntityTypeState } from '../../../../store/src/app-state'; import { EntityPipelineEntity, stratosEndpointGuidKey } from '../../../../store/src/entity-request-pipeline/pipeline.types'; import { EntitySchema } from '../../../../store/src/helpers/entity-schema'; -import { NormalizedResponse } from '../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { APISuccessOrFailedAction, EntityRequestAction } from '../../../../store/src/types/request.types'; import { IEndpointFavMetadata } from '../../../../store/src/types/user-favorites.types'; @@ -89,8 +87,8 @@ export class StratosBaseCatalogueEntity< } return newSchema; }, { - default: entitySchemas.default - }); + default: entitySchemas.default + }); } private getEndpointType(definition: IStratosBaseEntityDefinition) { @@ -217,13 +215,6 @@ export class StratosBaseCatalogueEntity< } - public getNormalizedEntityData(entities: Y | Y[], schemaKey?: string): NormalizedResponse { - const schema = this.getSchema(schemaKey); - if (Array.isArray(entities)) { - return normalize(entities, [schema]); - } - return normalize(entities, schema); - } } export class StratosCatalogueEntity< diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts index 00a2a1f541..d08d04483d 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts @@ -1,4 +1,5 @@ import { Action } from '@ngrx/store'; +import { normalize } from 'normalizr'; import { StratosBaseCatalogueEntity } from '../../../../core/src/core/entity-catalogue/entity-catalogue-entity'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; @@ -10,9 +11,8 @@ import { PipelineResult } from '../entity-request-pipeline.types'; import { getSuccessMapper } from '../pipeline-helpers'; import { endpointErrorsHandlerFactory } from './endpoint-errors.handler'; import { patchActionWithForcedConfig } from './forced-action-type.helpers'; -import { HandledMultiEndpointResponse, JetstreamError } from './handle-multi-endpoints.pipe'; +import { HandledMultiEndpointResponse, JetstreamError, MultiEndpointResponse } from './handle-multi-endpoints.pipe'; import { multiEndpointResponseMergePipe } from './merge-multi-endpoint-data.pipe'; -import { normalizeEntityPipeFactory } from './normalize-entity-request-response.pipe'; const baseErrorHandler = () => 'Api Request Failed'; @@ -56,6 +56,19 @@ function getEntities( }, {}); } +// TODO: Type the output of this pipe. #3976 +function getNormalizedEntityData( + entities: any[], + action: EntityRequestAction, + catalogueEntity: StratosBaseCatalogueEntity) { + // Can patchActionWithForcedConfig be done outside of the pipe? + // This pipe shouldn't have to worry about the multi entity lists. + const patchedAction = patchActionWithForcedConfig(action); + const schema = patchedAction.entity || catalogueEntity.getSchema(patchedAction.schemaKey); + const arraySafeSchema = Array.isArray(schema) ? schema[0] : schema; + return normalize(entities, Array.isArray(entities) ? [arraySafeSchema] : arraySafeSchema); +} + export function mapMultiEndpointResponses( action: EntityRequestAction, catalogueEntity: StratosBaseCatalogueEntity, @@ -63,12 +76,6 @@ export function mapMultiEndpointResponses( multiEndpointResponses: HandledMultiEndpointResponse, actionDispatcher: (actionToDispatch: Action) => void ): PipelineResult { - const normalizeEntityPipe = normalizeEntityPipeFactory( - catalogueEntity, - // Can this be done outside of the pipe? - // This pipe shouldn't have to worry about the multi entity lists. - patchActionWithForcedConfig(action).schemaKey - ); const endpointErrorHandler = endpointErrorsHandlerFactory(actionDispatcher); endpointErrorHandler( action, @@ -84,23 +91,28 @@ export function mapMultiEndpointResponses( errorMessage }; } else { - const responses = multiEndpointResponses.successes.map(normalizeEntityPipe); - const mapped = responses.map(endpointResponse => { - const entities = getEntities(endpointResponse, action); - const parentEntities = entities[catalogueEntity.entityKey]; - return { - response: { - entities, - // If we changed the guid of the entities then make sure this is reflected in the result array. - result: parentEntities ? Object.keys(parentEntities) : endpointResponse.normalizedEntities.result, - }, - totalPages: endpointResponse.totalPages, - totalResults: endpointResponse.totalResults, - success: null - }; - }); - // NormalizedResponse - const response = multiEndpointResponseMergePipe(mapped); + const responses = multiEndpointResponses.successes + .map((responseData: MultiEndpointResponse) => ({ + normalizedEntities: getNormalizedEntityData(responseData.entities, action, catalogueEntity), + endpointGuid: responseData.endpointGuid, + totalResults: responseData.totalResults, + totalPages: responseData.totalPages + })) + .map(endpointResponse => { + const entities = getEntities(endpointResponse, action); + const parentEntities = entities[catalogueEntity.entityKey]; + return { + response: { + entities, + // If we changed the guid of the entities then make sure this is reflected in the result array. + result: parentEntities ? Object.keys(parentEntities) : endpointResponse.normalizedEntities.result, + }, + totalPages: endpointResponse.totalPages, + totalResults: endpointResponse.totalResults, + success: null + }; + }); + const response = multiEndpointResponseMergePipe(responses); return { ...response, success: true, diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts deleted file mode 100644 index ee538455b8..0000000000 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StratosBaseCatalogueEntity } from '../../../../core/src/core/entity-catalogue/entity-catalogue-entity'; -import { MultiEndpointResponse } from './handle-multi-endpoints.pipe'; - -// TODO: Type the output of this pipe. #3976 -export const normalizeEntityPipeFactory = (catalogueEntity: StratosBaseCatalogueEntity, schemaKey?: string) => { - return (responseData: MultiEndpointResponse) => { - return { - normalizedEntities: catalogueEntity.getNormalizedEntityData(responseData.entities, schemaKey), - endpointGuid: responseData.endpointGuid, - totalResults: responseData.totalResults, - totalPages: responseData.totalPages - }; - }; -}; From 2967293b570ccb02cfeaa5016cbe16d297e6b478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Mon, 2 Dec 2019 14:18:03 -0300 Subject: [PATCH 10/31] sidepanel preview: cleanup and minor improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../services/cloud-foundry-space.service.ts | 4 + ...cloud-foundry-space-summary.component.html | 2 +- .../application-preview.component.html | 126 +++++++++--------- .../application-preview.component.ts | 27 ++-- .../organization-preview.component.html | 2 +- .../packages/core/src/base-entity-schemas.ts | 3 +- .../entity-catalogue-entity.ts | 2 +- .../entity-catalogue.types.ts | 1 + .../dashboard-base.component.scss | 5 + .../connect-endpoint-dialog.component.ts | 6 +- .../create-endpoint-connect.component.ts | 8 +- .../favorites-meta-card.component.ts | 4 +- .../markdown-preview.component.ts | 1 - .../shared/services/panel-preview.service.ts | 17 ++- 14 files changed, 121 insertions(+), 87 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 371b9874d9..a3a230d5c7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -103,6 +103,10 @@ export class CloudFoundrySpaceService { this.usersCount$ = this.cfUserService.fetchTotalUsers(this.cfGuid, this.orgGuid, this.spaceGuid); } + public fetchApps() { + this.cfEndpointService.fetchApps(); + } + private initialiseSpaceObservables() { this.space$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html index 1f03e7bdb5..3ec288a9fa 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html @@ -75,7 +75,7 @@ + [loading$]="cfSpaceService.loadingApps$" (refresh)="cfSpaceService.fetchApps()"> diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html index 9900b6a612..f47a8f18fe 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -1,68 +1,70 @@ - - -
- - - Summary - - - - - - {{ appSvc.cf?.name}} - - - {{ appSvc.cf?.name}} - - - - - - - -
-
-
+ + + +
+ + + Summary + + + + + + {{ appSvc.cf?.name}} + + + {{ appSvc.cf?.name}} + + + + + + + +
+
+
- - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + +
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts index 5aa2772e67..649e38af6f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts @@ -1,30 +1,27 @@ import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; -import { combineLatest, map } from 'rxjs/operators'; +import { combineLatest, distinct, map } from 'rxjs/operators'; +import { IAppSummary } from '../../../../../core/src/core/cf-api.types'; import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; -import { ApplicationService } from '../../../features/applications/application.service'; -import { getGuids } from '../../../features/applications/application/application-base.component'; +import { EntityInfo } from '../../../../../store/src/types/api.types'; +import { ApplicationData, ApplicationService } from '../../../features/applications/application.service'; @Component({ selector: 'app-application-preview-component', templateUrl: './application-preview.component.html', styleUrls: ['./application-preview.component.scss'], - // useless, necessary to reuse ApplicationService providers: [ ApplicationService, { provide: CF_GUID, - useFactory: getGuids('cf'), - deps: [ActivatedRoute] + useValue: '', }, { provide: APP_GUID, - useFactory: getGuids(), - deps: [ActivatedRoute] + useValue: '', }, ] }) @@ -33,6 +30,7 @@ export class ApplicationPreviewComponent implements PreviewableComponent { title = null; cfEndpointService: object; sshStatus$: Observable; + detailsLoading$: Observable; getFullEndpointApiUrl = getFullEndpointApiUrl; @@ -54,8 +52,15 @@ export class ApplicationPreviewComponent implements PreviewableComponent { }) ); - // this.detailsLoading$ = combineLatest([ - // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now + this.detailsLoading$ = this.applicationService.application$.pipe( + combineLatest( + this.applicationService.appSummary$ + ), + map(([app, appSummary]: [ApplicationData, EntityInfo]) => { + return app.fetching || appSummary.entityRequestInfo.fetching; + }), distinct()); + + // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now // this.cfEndpointService.appsPagObs.fetchingEntities$.pipe( // filter(loading => !loading) // ), diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html index 94359a1d9b..6387b13274 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html @@ -94,7 +94,7 @@ + [loading$]="cfOrgService.loadingApps$" (refresh)="cfOrgService.fetchApps()"> diff --git a/src/frontend/packages/core/src/base-entity-schemas.ts b/src/frontend/packages/core/src/base-entity-schemas.ts index 6a1f2c9ee8..4242afd616 100644 --- a/src/frontend/packages/core/src/base-entity-schemas.ts +++ b/src/frontend/packages/core/src/base-entity-schemas.ts @@ -1,13 +1,14 @@ import { endpointSchemaKey, entityFactory, + systemInfoSchemaKey, userFavouritesSchemaKey, userProfileSchemaKey, - systemInfoSchemaKey, } from '../../store/src/helpers/entity-factory'; import { EntitySchema } from '../../store/src/helpers/entity-schema'; export const STRATOS_ENDPOINT_TYPE = 'stratos'; +export const ENDPOINT_TYPE = 'endpoint'; class StratosEntitySchema extends EntitySchema { constructor(entityType: string) { diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts index 64fa92b6c6..4ca21ea92d 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts @@ -255,7 +255,7 @@ export class StratosCatalogueEndpointEntity extends StratosBaseCatalogueEntity string, - // TODO: attach to PreviewableComponent + // TODO find a way to attach this to PreviewableComponent getPreviewableComponent?: () => object ) { const fullEntity = { diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts index 241c7d5643..6e948950f9 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts @@ -149,6 +149,7 @@ export interface IStratosEntityBuilder { getStatusObservable?(entity: Y): Observable; // TODO This should be used in the entities schema. getGuid(entityMetadata: T): string; + // TODO find a way to attach this to PreviewableComponent getPreviewableComponent?(): object; getLink?(entityMetadata: T): string; getLines?(): EntityRowBuilder[]; diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 82277d48cb..41575a238a 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -55,7 +55,12 @@ $app-sub-header-height: 48px; padding: 0; } &__side-help { + max-width: 600px; min-width: 600px; + + @include breakpoint(mobileonly) { + min-width: auto; + } } } diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index b4c554eba4..788d2bc4ae 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -6,6 +6,8 @@ import { Subscription } from 'rxjs'; import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../shared/components/markdown-preview/markdown-preview.component'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../connect.service'; @@ -27,6 +29,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { @Inject(MAT_DIALOG_DATA) public data: ConnectEndpointConfig, private store: Store, endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { this.connectService = new ConnectEndpointService(store, endpointsService, data); @@ -37,8 +40,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { } showHelp() { - // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); - // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } ngOnDestroy(): void { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index b2282192bb..ff128ef73c 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -5,7 +5,9 @@ import { map } from 'rxjs/operators'; import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../../shared/components/markdown-preview/markdown-preview.component'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; +import { PanelPreviewService } from '../../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../../connect.service'; @@ -25,13 +27,13 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { constructor( private store: Store, - private endpointsService: EndpointsService + private endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { } showHelp() { - // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); - // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } onEnter = (data: ConnectEndpointConfig) => { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index 29c8e373ef..5a91f03f22 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -12,7 +12,7 @@ import { endpointsEntityRequestDataSelector, } from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; -import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { ENDPOINT_TYPE, userFavoritesEntitySchema } from '../../../base-entity-schemas'; import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; import { PanelPreviewService } from '../../services/panel-preview.service'; @@ -113,7 +113,7 @@ export class FavoritesMetaCardComponent { const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); // TODO: use 'endpoint' as constant - if (this.favorite.entityType === 'endpoint') { + if (this.favorite.entityType === ENDPOINT_TYPE) { const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); this.panelPreviewService.show(previewComponent, { title: this.favorite.metadata.name, diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index dab1792c71..9dba74b1d5 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -35,7 +35,6 @@ export class MarkdownPreviewComponent implements PreviewableComponent { ) { } setProps(props: { [key: string]: any }) { - console.log('props', props); this.setDocumentUrl = props.documentUrl; } diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts index dda1d3e374..04b42e4010 100644 --- a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -1,6 +1,7 @@ import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { Router } from '@angular/router'; import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; -import { observeOn, publishReplay, refCount } from 'rxjs/operators'; +import { filter, observeOn, publishReplay, refCount, tap } from 'rxjs/operators'; @Injectable() export class PanelPreviewService { @@ -9,9 +10,14 @@ export class PanelPreviewService { private container: ViewContainerRef; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private router: Router, + ) { this.openedSubject = new BehaviorSubject(false); this.opened$ = this.observeSubject(this.openedSubject); + + this.setupRouterListener(); } public setContainer(container: ViewContainerRef) { @@ -57,6 +63,13 @@ export class PanelPreviewService { this.openedSubject.next(false); } + private setupRouterListener() { + this.router.events.pipe( + filter(() => !!this.container), + tap((e) => this.hide())) + .subscribe(); + } + private observeSubject(subject: Subject) { return subject.asObservable().pipe( publishReplay(1), From f4bb9d5a568126e56a77862fde67e4c6f3e28b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 6 Dec 2019 08:23:08 -0300 Subject: [PATCH 11/31] k8s endpoint: added sidepanel preview component --- ...kubernetes-endpoint-preview.component.html | 72 +++++++++ ...kubernetes-endpoint-preview.component.scss | 0 ...ernetes-endpoint-preview.component.spec.ts | 37 +++++ .../kubernetes-endpoint-preview.component.ts | 74 ++++++++++ .../kubernetes/kubernetes-entity-generator.ts | 2 + .../kubernetes/kubernetes.setup.module.ts | 11 +- .../services/kubernetes-endpoint.service.ts | 110 +++++++++++++- .../kubernetes-summary.component.ts | 139 +++++------------- 8 files changed, 334 insertions(+), 111 deletions(-) create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.ts diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.html b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.html new file mode 100644 index 0000000000..e216ce4ad7 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.html @@ -0,0 +1,72 @@ + + + + + +
+ + + Summary + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.scss b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.spec.ts new file mode 100644 index 0000000000..6f1006656d --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { KubernetesEndpointPreviewComponent } from './kubernetes-endpoint-preview.component'; + +describe('KubernetesEndpointPreviewComponent', () => { + let component: KubernetesEndpointPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [KubernetesEndpointPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesEndpointPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.ts new file mode 100644 index 0000000000..3f8047cf0c --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-endpoint-preview/kubernetes-endpoint-preview.component.ts @@ -0,0 +1,74 @@ +import { Component } from '@angular/core'; +import { combineLatest, Observable } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; + +import { + ISimpleUsageChartData, +} from '../../../../../core/src/shared/components/simple-usage-chart/simple-usage-chart.types'; +import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; + +@Component({ + selector: 'app-kubernetes-endpoint-preview-component', + templateUrl: './kubernetes-endpoint-preview.component.html', + styleUrls: ['./kubernetes-endpoint-preview.component.scss'] +}) +export class KubernetesEndpointPreviewComponent implements PreviewableComponent { + + title: string = null; + detailsLoading$: Observable; + kubeVersion$: Observable; + podCount$: Observable; + nodeCount$: Observable; + appCount$: Observable; + podCapacity$: Observable; + diskPressure$: Observable; + memoryPressure$: Observable; + outOfDisk$: Observable; + nodesReady$: Observable; + podsLink: string; + nodesLink: string; + appsLink: string; + + constructor( + public kubeEndpointService: KubernetesEndpointService, + ) { } + + setProps(props: { [key: string]: any }) { + const kubeGuid = props.cfGuid; + + this.title = props.title; + + this.kubeEndpointService.initialize(kubeGuid); + const nodes$ = this.kubeEndpointService.nodes$; + + this.kubeVersion$ = this.kubeEndpointService.getNodeKubeVersions(); + this.podCount$ = this.kubeEndpointService.getCountObservable(this.kubeEndpointService.pods$); + this.nodeCount$ = this.kubeEndpointService.getCountObservable(nodes$); + this.appCount$ = this.kubeEndpointService.getCountObservable(this.kubeEndpointService.apps$); + this.podCapacity$ = this.kubeEndpointService.getPodCapacity(); + this.diskPressure$ = this.kubeEndpointService.getNodeStatusCount(this.kubeEndpointService.nodes$, 'DiskPressure'); + this.memoryPressure$ = this.kubeEndpointService.getNodeStatusCount(this.kubeEndpointService.nodes$, 'MemoryPressure'); + this.outOfDisk$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'OutOfDisk'); + this.nodesReady$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'Ready'); + + this.podsLink = `/kubernetes/${kubeGuid}/pods`; + this.nodesLink = `/kubernetes/${kubeGuid}/nodes`; + this.appsLink = `/kubernetes/${kubeGuid}/apps`; + + this.detailsLoading$ = combineLatest([ + this.podCount$, + this.nodeCount$, + this.appCount$, + this.podCapacity$, + this.diskPressure$, + this.memoryPressure$, + this.outOfDisk$, + this.nodesReady$, + // this.networkUnavailable$, + ]).pipe( + map(() => false), + startWith(true), + ); + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts index c6e79f120b..0b9ea19dbe 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-entity-generator.ts @@ -17,6 +17,7 @@ import { KubernetesConfigAuthFormComponent, } from './auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component'; import { KubernetesGKEAuthFormComponent } from './auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component'; +import { KubernetesEndpointPreviewComponent } from './kubernetes-endpoint-preview/kubernetes-endpoint-preview.component'; import { KUBERNETES_ENDPOINT_TYPE, kubernetesAppsEntityType, @@ -154,6 +155,7 @@ function generateEndpointEntity(endpointDefinition: StratosEndpointExtensionDefi return new StratosCatalogueEndpointEntity( endpointDefinition, metadata => `/kubernetes/${metadata.guid}`, + () => KubernetesEndpointPreviewComponent, ); } diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts index 5ade43114c..78170c7b79 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts @@ -16,9 +16,12 @@ import { KubernetesConfigAuthFormComponent, } from './auth-forms/kubernetes-config-auth-form/kubernetes-config-auth-form.component'; import { KubernetesGKEAuthFormComponent } from './auth-forms/kubernetes-gke-auth-form/kubernetes-gke-auth-form.component'; +import { KubernetesEndpointPreviewComponent } from './kubernetes-endpoint-preview/kubernetes-endpoint-preview.component'; import { KUBERNETES_ENDPOINT_TYPE } from './kubernetes-entity-factory'; import { generateKubernetesEntities } from './kubernetes-entity-generator'; +import { BaseKubeGuid } from './kubernetes-page.types'; import { KubernetesStoreModule } from './kubernetes.store.module'; +import { KubernetesEndpointService } from './services/kubernetes-endpoint.service'; import { KubeHealthCheck } from './store/kubernetes.actions'; @@ -28,19 +31,25 @@ import { KubeHealthCheck } from './store/kubernetes.actions'; CoreModule, CommonModule, SharedModule, - KubernetesStoreModule, + KubernetesStoreModule ], declarations: [ KubernetesCertsAuthFormComponent, KubernetesAWSAuthFormComponent, KubernetesConfigAuthFormComponent, KubernetesGKEAuthFormComponent, + KubernetesEndpointPreviewComponent, + ], + providers: [ + BaseKubeGuid, + KubernetesEndpointService, ], entryComponents: [ KubernetesCertsAuthFormComponent, KubernetesAWSAuthFormComponent, KubernetesConfigAuthFormComponent, KubernetesGKEAuthFormComponent, + KubernetesEndpointPreviewComponent, ] }) export class KubernetesSetupModule { diff --git a/custom-src/frontend/app/custom/kubernetes/services/kubernetes-endpoint.service.ts b/custom-src/frontend/app/custom/kubernetes/services/kubernetes-endpoint.service.ts index 9816a4bbea..99c8fe47ba 100644 --- a/custom-src/frontend/app/custom/kubernetes/services/kubernetes-endpoint.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/services/kubernetes-endpoint.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; +import { combineLatest, Observable } from 'rxjs'; import { filter, first, map, shareReplay, startWith, switchMap } from 'rxjs/operators'; import { GetAllEndpoints } from '../../../../../store/src/actions/endpoint.actions'; @@ -12,16 +12,27 @@ import { EntityService } from '../../../core/entity-service'; import { EntityServiceFactory } from '../../../core/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../shared/monitors/pagination-monitor.factory'; import { + kubernetesAppsEntityType, kubernetesDeploymentsEntityType, + kubernetesNodesEntityType, kubernetesPodsEntityType, kubernetesServicesEntityType, kubernetesStatefulSetsEntityType, } from '../kubernetes-entity-factory'; import { BaseKubeGuid } from '../kubernetes-page.types'; -import { KubernetesDeployment, KubernetesPod, KubernetesStatefulSet, KubeService } from '../store/kube.types'; +import { + KubernetesApp, + KubernetesDeployment, + KubernetesNode, + KubernetesPod, + KubernetesStatefulSet, + KubeService, +} from '../store/kube.types'; import { GeKubernetesDeployments, + GetKubernetesApps, GetKubernetesDashboard, + GetKubernetesNodes, GetKubernetesPods, GetKubernetesServices, GetKubernetesStatefulSets, @@ -42,6 +53,8 @@ export class KubernetesEndpointService { statefulSets$: Observable; services$: Observable; pods$: Observable; + apps$: Observable; + nodes$: Observable; kubeDashboardEnabled$: Observable; constructor( @@ -50,7 +63,16 @@ export class KubernetesEndpointService { private entityServiceFactory: EntityServiceFactory, private paginationMonitorFactory: PaginationMonitorFactory ) { - this.kubeGuid = baseKube.guid; + const kubeGuid = baseKube.guid; + + if (kubeGuid) { + this.initialize(kubeGuid); + } + } + + initialize(kubeGuid) { + this.kubeGuid = kubeGuid; + this.kubeEndpointEntityService = this.entityServiceFactory.create( this.kubeGuid, new GetAllEndpoints() @@ -59,7 +81,77 @@ export class KubernetesEndpointService { this.constructCoreObservables(); } - constructCoreObservables() { + getNodeKubeVersions(nodes$: Observable = this.nodes$) { + return nodes$.pipe( + map(nodes => { + const versions = {}; + nodes.forEach(node => { + const v = node.status.nodeInfo.kubeletVersion; + if (!versions[v]) { + versions[v] = v; + } + }); + return Object.keys(versions).join(','); + }) + ); + } + + getCountObservable(entities$: Observable) { + return entities$.pipe( + map(entities => entities.length), + startWith(null) + ); + } + + getPodCapacity(nodes$: Observable = this.nodes$, pods$: Observable = this.pods$) { + return combineLatest(nodes$, pods$).pipe( + map(([nodes, pods]) => ({ + total: nodes.reduce((cap, node) => { + return cap + parseInt(node.status.capacity.pods, 10); + }, 0), + used: pods.length + })) + ); + } + + getNodeStatusCount( + nodes$: Observable, + conditionType: string, + valueLabels: object = {}, + countStatus = 'True' + ) { + return nodes$.pipe( + map(nodes => { + const total = nodes.length; + const { unknown, unavailable, used } = nodes.reduce((cap, node) => { + const conditionStatus = node.status.conditions.find(con => con.type === conditionType); + if (!conditionStatus || !conditionStatus.status) { + ++cap.unavailable; + } else { + if (conditionStatus.status === countStatus) { + ++cap.used; + } else if (conditionStatus.status === 'Unknown') { + ++cap.unknown; + } + } + return cap; + }, { unavailable: 0, used: 0, unknown: 0 }); + const result = { + total, + supported: total !== unavailable, + // Depends on K8S version as to what is supported + unavailable, + used, + unknown, + ...valueLabels + }; + result.supported = result.total !== result.unavailable; + return result; + }) + ); + } + + private constructCoreObservables() { this.endpoint$ = this.kubeEndpointEntityService.waitForEntity$; this.connected$ = this.endpoint$.pipe( @@ -78,6 +170,16 @@ export class KubernetesEndpointService { kubernetesPodsEntityType ); + this.nodes$ = this.getObservable( + new GetKubernetesNodes(this.kubeGuid), + kubernetesNodesEntityType + ); + + this.apps$ = this.getObservable( + new GetKubernetesApps(this.kubeGuid), + kubernetesAppsEntityType + ); + this.statefulSets$ = this.getObservable( new GetKubernetesStatefulSets(this.kubeGuid), kubernetesStatefulSetsEntityType diff --git a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts index 5b56862e8b..754b504b63 100644 --- a/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/tabs/kubernetes-summary-tab/kubernetes-summary.component.ts @@ -17,8 +17,6 @@ import { import { PaginationMonitorFactory } from '../../../../shared/monitors/pagination-monitor.factory'; import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; import { GetKubernetesNodes, GetKubernetesPods } from '../../store/kubernetes.actions'; -import { KubernetesNode } from './../../../../../../../../../custom-src/frontend/app/custom/kubernetes/store/kube.types'; -import { KubernetesPod } from './../../store/kube.types'; import { GetKubernetesApps } from './../../store/kubernetes.actions'; interface IValueLabels { @@ -95,7 +93,6 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { public isLoading$: Observable; - constructor( public kubeEndpointService: KubernetesEndpointService, public httpClient: HttpClient, @@ -105,99 +102,6 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { ) { } - private getPaginationObservable(action: PaginatedAction) { - const paginationMonitor = this.paginationMonitorFactory.create( - action.paginationKey, - action - ); - - this.ngZone.runOutsideAngular(() => { - this.polls.push( - interval(10000).subscribe(() => { - this.ngZone.run(() => { - this.store.dispatch(action); - }); - }) - ); - }); - - return getPaginationObservables({ - store: this.store, - action, - paginationMonitor - }).entities$; - } - - private getCountObservable(entities$: Observable) { - return entities$.pipe( - map(entities => entities.length), - startWith(null) - ); - } - private getPodCapacity(nodes$: Observable, pods$: Observable) { - return combineLatest(nodes$, pods$).pipe( - map(([nodes, pods]) => ({ - total: nodes.reduce((cap, node) => { - return cap + parseInt(node.status.capacity.pods, 10); - }, 0), - used: pods.length - })) - ); - } - - private getNodeStatusCount( - nodes$: Observable, - conditionType: string, - valueLabels: IValueLabels = {}, - countStatus = 'True' - ) { - return nodes$.pipe( - map(nodes => { - const total = nodes.length; - const { unknown, unavailable, used } = nodes.reduce((cap, node) => { - const conditionStatus = node.status.conditions.find(con => con.type === conditionType); - if (!conditionStatus || !conditionStatus.status) { - ++cap.unavailable; - } else { - if (conditionStatus.status === countStatus) { - ++cap.used; - } else if (conditionStatus.status === 'Unknown') { - ++cap.unknown; - } - } - return cap; - }, { unavailable: 0, used: 0, unknown: 0 }); - const result = { - total, - supported: total !== unavailable, - // Depends on K8S version as to what is supported - unavailable, - used, - unknown, - ...valueLabels - }; - result.supported = result.total !== result.unavailable; - return result; - }) - ); - } - - private getNodeKubeVersions(nodes$: Observable) { - return nodes$.pipe( - map(nodes => { - const versions = {}; - nodes.forEach(node => { - const v = node.status.nodeInfo.kubeletVersion; - if (!versions[v]) { - versions[v] = v; - } - }); - return Object.keys(versions).join(','); - }) - ); - } - - ngOnInit() { const guid = this.kubeEndpointService.baseKube.guid; @@ -208,36 +112,36 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { const pods$ = this.getPaginationObservable(podCountAction); const nodes$ = this.getPaginationObservable(nodeCountAction); - this.podCount$ = this.getCountObservable(pods$); - this.nodeCount$ = this.getCountObservable(nodes$); - this.appCount$ = this.getCountObservable(applications$); + this.podCount$ = this.kubeEndpointService.getCountObservable(pods$); + this.nodeCount$ = this.kubeEndpointService.getCountObservable(nodes$); + this.appCount$ = this.kubeEndpointService.getCountObservable(applications$); - this.podCapacity$ = this.getPodCapacity(nodes$, pods$); - this.diskPressure$ = this.getNodeStatusCount(nodes$, 'DiskPressure', { + this.podCapacity$ = this.kubeEndpointService.getPodCapacity(nodes$, pods$); + this.diskPressure$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'DiskPressure', { usedLabel: 'Nodes with disk pressure', remainingLabel: 'Nodes with no disk pressure', unknownLabel: 'Nodes with unknown disk pressure', warningText: 'Nodes with unknown disk pressure found' }); - this.memoryPressure$ = this.getNodeStatusCount(nodes$, 'MemoryPressure', { + this.memoryPressure$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'MemoryPressure', { usedLabel: 'Nodes with memory pressure', remainingLabel: 'Nodes with no memory pressure', unknownLabel: 'Nodes with unknown memory pressure', warningText: 'Nodes with unknown memory pressure found' }); - this.outOfDisk$ = this.getNodeStatusCount(nodes$, 'OutOfDisk', { + this.outOfDisk$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'OutOfDisk', { usedLabel: 'Nodes that are out of disk space', remainingLabel: 'Nodes that have disk space remaining', unknownLabel: 'Nodes with unknown remaining disk space', warningText: 'Nodes with unknown remaining disk space found' }); - this.networkUnavailable$ = this.getNodeStatusCount(nodes$, 'NetworkUnavailable', { + this.networkUnavailable$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'NetworkUnavailable', { usedLabel: 'Nodes with available networks', remainingLabel: 'Nodes with unavailable networks', unknownLabel: 'Nodes with unknown networks availability', warningText: 'Nodes with unknown networks availability found' }, 'False'); - this.nodesReady$ = this.getNodeStatusCount(nodes$, 'Ready', { + this.nodesReady$ = this.kubeEndpointService.getNodeStatusCount(nodes$, 'Ready', { usedLabel: 'Nodes are ready', remainingLabel: 'Nodes are not ready', unknownLabel: 'Nodes with unknown ready status', @@ -245,7 +149,7 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { }); this.dashboardLink = `/kubernetes/${guid}/dashboard`; - this.kubeNodeVersions$ = this.getNodeKubeVersions(nodes$).pipe(startWith('-')); + this.kubeNodeVersions$ = this.kubeEndpointService.getNodeKubeVersions(nodes$).pipe(startWith('-')); this.isLoading$ = combineLatest([ this.endpointDetails$, @@ -264,6 +168,29 @@ export class KubernetesSummaryTabComponent implements OnInit, OnDestroy { ); } + private getPaginationObservable(action: PaginatedAction) { + const paginationMonitor = this.paginationMonitorFactory.create( + action.paginationKey, + action + ); + + this.ngZone.runOutsideAngular(() => { + this.polls.push( + interval(10000).subscribe(() => { + this.ngZone.run(() => { + this.store.dispatch(action); + }); + }) + ); + }); + + return getPaginationObservables({ + store: this.store, + action, + paginationMonitor + }).entities$; + } + ngOnDestroy() { safeUnsubscribe(...(this.polls || [])); } From b8a65dfb44d7214ac9f038ec2411e7e3cbfd76f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 08:59:20 -0300 Subject: [PATCH 12/31] sidepanel preview: added k8s node and pod components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../kubernetes-node-preview.component.html | 57 +++++++++++++++++ .../kubernetes-node-preview.component.scss | 0 .../kubernetes-node-preview.component.spec.ts | 37 +++++++++++ .../kubernetes-node-preview.component.ts | 61 +++++++++++++++++++ .../kubernetes-pod-preview.component.html | 29 +++++++++ .../kubernetes-pod-preview.component.scss | 0 .../kubernetes-pod-preview.component.spec.ts | 37 +++++++++++ .../kubernetes-pod-preview.component.ts | 59 ++++++++++++++++++ .../kubernetes/kubernetes.setup.module.ts | 8 +++ .../kubernetes-node-link.component.html | 3 +- .../kubernetes-node-link.component.ts | 16 ++++- .../pod-name-link.component.html | 2 +- .../pod-name-link/pod-name-link.component.ts | 16 ++++- .../services/kubernetes-node.service.ts | 15 +++-- 14 files changed, 332 insertions(+), 8 deletions(-) create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.html create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.scss create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.spec.ts create mode 100644 custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.ts diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.html b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.html new file mode 100644 index 0000000000..4345287b4d --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.html @@ -0,0 +1,57 @@ + + + + + +
+ + + Summary + + + + + +
+
+
+ + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.scss b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.spec.ts new file mode 100644 index 0000000000..aebe53afad --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { KubernetesNodePreviewComponent } from './kubernetes-node-preview.component'; + +describe('KubernetesNodePreviewComponent', () => { + let component: KubernetesNodePreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [KubernetesNodePreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesNodePreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.ts new file mode 100644 index 0000000000..0509ec489b --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; + +import { PreviewableComponent } from '../../../../../../core/src/shared/previewable-component'; +import { KubernetesEndpointService } from '../../services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from '../../services/kubernetes-node.service'; + +@Component({ + selector: 'app-kubernetes-node-preview-component', + templateUrl: './kubernetes-node-preview.component.html', + styleUrls: ['./kubernetes-node-preview.component.scss'] +}) +export class KubernetesNodePreviewComponent implements PreviewableComponent { + + title: string = null; + detailsLoading$: Observable; + kubeVersion$: Observable; + podCount$: Observable; + memoryCapacity$: Observable; + podsLink: string; + + constructor( + public kubeEndpointService: KubernetesEndpointService, + public kubeNodeService: KubernetesNodeService, + ) { + } + + setProps(props: { [key: string]: any }) { + const nodeName = props.nodeName; + const kubeGuid = props.kubeGuid; + + this.title = nodeName; + + this.kubeEndpointService.initialize(kubeGuid); + this.kubeNodeService.initialize(nodeName, kubeGuid); + + this.podCount$ = this.kubeEndpointService.pods$.pipe( + map(pods => pods.filter(p => p.spec.nodeName === nodeName).length) + ); + + this.memoryCapacity$ = this.kubeNodeService.nodeEntity$.pipe( + map(node => this.getMemory(node.status.capacity.memory)) + ); + + this.podsLink = `/kubernetes/${kubeGuid}/nodes/minikube/pods`; + + this.detailsLoading$ = this.kubeNodeService.nodeEntity$.pipe( + map((entity) => !entity), + startWith(true), + ); + } + + private getMemory(memoryCapacity: string) { + if (memoryCapacity.endsWith('Ki')) { + const value = parseInt(memoryCapacity, 10); + return (value * 1024); + } + return memoryCapacity; + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.html b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.html new file mode 100644 index 0000000000..aa937d21ba --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.html @@ -0,0 +1,29 @@ + + + + + +
+ + + Summary + + + + + +
+
+
+
+
+
\ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.scss b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.spec.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.spec.ts new file mode 100644 index 0000000000..34af9ac2cd --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { KubernetesPodPreviewComponent } from './kubernetes-pod-preview.component'; + +describe('KubernetesPodPreviewComponent', () => { + let component: KubernetesPodPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [KubernetesPodPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(KubernetesPodPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.ts new file mode 100644 index 0000000000..09fd24afa8 --- /dev/null +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes-pod-preview/kubernetes-pod-preview.component.ts @@ -0,0 +1,59 @@ +import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; +import { filter, first, map, shareReplay, tap } from 'rxjs/operators'; + +import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; +import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { EntityInfo } from '../../../../../store/src/types/api.types'; +import { KubernetesEndpointService } from '../services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from '../services/kubernetes-node.service'; +import { KubernetesPod } from '../store/kube.types'; +import { GetKubernetesPod } from '../store/kubernetes.actions'; + +@Component({ + selector: 'app-kubernetes-pod-preview-component', + templateUrl: './kubernetes-pod-preview.component.html', + styleUrls: ['./kubernetes-pod-preview.component.scss'] +}) +export class KubernetesPodPreviewComponent implements PreviewableComponent { + + title: string = null; + detailsLoading$: Observable; + pod$: Observable>; + podEntity$: Observable; + + constructor( + public kubeEndpointService: KubernetesEndpointService, + public kubeNodeService: KubernetesNodeService, + private entityServiceFactory: EntityServiceFactory, + ) { + } + + setProps(props: { [key: string]: any }) { + const { podName, namespaceName, kubeGuid, podGuid } = props; + + this.title = podName; + + const podEntityService = this.entityServiceFactory.create( + podGuid, + new GetKubernetesPod(podName, namespaceName, kubeGuid), + ); + + this.pod$ = podEntityService.entityObs$.pipe( + tap(p => console.log(1, p)), + filter(p => !!p && !!p.entity), + first(), + shareReplay(1), + ); + + this.podEntity$ = this.pod$.pipe( + tap(p => console.log(2, p)), + map(p => p.entity) + ); + + // this.detailsLoading$ = this.kubeNodeService.nodeEntity$.pipe( + // map((entity) => !entity), + // startWith(false), + // ); + } +} diff --git a/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts b/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts index 78170c7b79..abe3f4c317 100644 --- a/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts +++ b/custom-src/frontend/app/custom/kubernetes/kubernetes.setup.module.ts @@ -19,9 +19,12 @@ import { KubernetesGKEAuthFormComponent } from './auth-forms/kubernetes-gke-auth import { KubernetesEndpointPreviewComponent } from './kubernetes-endpoint-preview/kubernetes-endpoint-preview.component'; import { KUBERNETES_ENDPOINT_TYPE } from './kubernetes-entity-factory'; import { generateKubernetesEntities } from './kubernetes-entity-generator'; +import { KubernetesNodePreviewComponent } from './kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component'; import { BaseKubeGuid } from './kubernetes-page.types'; +import { KubernetesPodPreviewComponent } from './kubernetes-pod-preview/kubernetes-pod-preview.component'; import { KubernetesStoreModule } from './kubernetes.store.module'; import { KubernetesEndpointService } from './services/kubernetes-endpoint.service'; +import { KubernetesNodeService } from './services/kubernetes-node.service'; import { KubeHealthCheck } from './store/kubernetes.actions'; @@ -39,10 +42,13 @@ import { KubeHealthCheck } from './store/kubernetes.actions'; KubernetesConfigAuthFormComponent, KubernetesGKEAuthFormComponent, KubernetesEndpointPreviewComponent, + KubernetesNodePreviewComponent, + KubernetesPodPreviewComponent, ], providers: [ BaseKubeGuid, KubernetesEndpointService, + KubernetesNodeService, ], entryComponents: [ KubernetesCertsAuthFormComponent, @@ -50,6 +56,8 @@ import { KubeHealthCheck } from './store/kubernetes.actions'; KubernetesConfigAuthFormComponent, KubernetesGKEAuthFormComponent, KubernetesEndpointPreviewComponent, + KubernetesNodePreviewComponent, + KubernetesPodPreviewComponent, ] }) export class KubernetesSetupModule { diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html index 99161c715c..453d1491c3 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.html @@ -1 +1,2 @@ -{{row.metadata.name}} + +{{ row.metadata.name }} diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts index 21d7d00432..fcf6c775d7 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-nodes/kubernetes-node-link/kubernetes-node-link.component.ts @@ -1,6 +1,10 @@ import { Component, OnInit } from '@angular/core'; +import { PanelPreviewService } from '../../../../../../../core/src/shared/services/panel-preview.service'; import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { + KubernetesNodePreviewComponent, +} from '../../../kubernetes-node/kubernetes-node-preview/kubernetes-node-preview.component'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesNode } from '../../../store/kube.types'; @@ -13,7 +17,8 @@ export class KubernetesNodeLinkComponent extends TableCellCustom public nodeLink; constructor( - private kubeEndpointService: KubernetesEndpointService + private kubeEndpointService: KubernetesEndpointService, + private panelPreviewService: PanelPreviewService, ) { super(); } @@ -22,4 +27,13 @@ export class KubernetesNodeLinkComponent extends TableCellCustom this.nodeLink = `/kubernetes/${this.kubeEndpointService.kubeGuid}/nodes/${this.row.metadata.name}`; } + openSidepanelPreview($event: MouseEvent) { + $event.preventDefault(); + $event.stopPropagation(); + this.panelPreviewService.show(KubernetesNodePreviewComponent, { + nodeName: this.row.metadata.name, + kubeGuid: this.kubeEndpointService.kubeGuid, + }); + } + } diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.html b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.html index c035910aa9..3fa3aa832a 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.html +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.html @@ -1,2 +1,2 @@ {{row.metadata.name}} -{{row.metadata.name}} \ No newline at end of file +{{row.metadata.name}} \ No newline at end of file diff --git a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.ts b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.ts index 0b7495c562..8da4f6e361 100644 --- a/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.ts +++ b/custom-src/frontend/app/custom/kubernetes/list-types/kubernetes-pods/pod-name-link/pod-name-link.component.ts @@ -3,9 +3,11 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; +import { PanelPreviewService } from '../../../../../../../core/src/shared/services/panel-preview.service'; import { EndpointsService } from '../../../../../core/endpoints.service'; import { getIdFromRoute } from '../../../../../core/utils.service'; import { TableCellCustom } from '../../../../../shared/components/list/list.types'; +import { KubernetesPodPreviewComponent } from '../../../kubernetes-pod-preview/kubernetes-pod-preview.component'; import { KubernetesEndpointService } from '../../../services/kubernetes-endpoint.service'; import { KubernetesPod } from '../../../store/kube.types'; @@ -20,7 +22,8 @@ export class PodNameLinkComponent extends TableCellCustom impleme constructor( private activatedRoute: ActivatedRoute, private endpointsService: EndpointsService, - private kubeEndpointService: KubernetesEndpointService + private kubeEndpointService: KubernetesEndpointService, + private panelPreviewService: PanelPreviewService, ) { super(); } @@ -36,4 +39,15 @@ export class PodNameLinkComponent extends TableCellCustom impleme this.routerLink = [this.row.metadata.name]; } } + + openSidepanelPreview($event: MouseEvent) { + $event.preventDefault(); + $event.stopPropagation(); + this.panelPreviewService.show(KubernetesPodPreviewComponent, { + podGuid: this.row.metadata.uid, + podName: this.row.metadata.name, + namespaceName: this.row.metadata.namespace, + kubeGuid: this.kubeEndpointService.kubeGuid, + }); + } } diff --git a/custom-src/frontend/app/custom/kubernetes/services/kubernetes-node.service.ts b/custom-src/frontend/app/custom/kubernetes/services/kubernetes-node.service.ts index 7d8a349294..e4815ae7c4 100644 --- a/custom-src/frontend/app/custom/kubernetes/services/kubernetes-node.service.ts +++ b/custom-src/frontend/app/custom/kubernetes/services/kubernetes-node.service.ts @@ -37,8 +37,17 @@ export class KubernetesNodeService { public entityServiceFactory: EntityServiceFactory, public entityMonitorFactory: EntityMonitorFactory ) { - this.nodeName = getIdFromRoute(activatedRoute, 'nodeName'); - this.kubeGuid = kubeEndpointService.kubeGuid; + const nodeName = getIdFromRoute(activatedRoute, 'nodeName'); + const kubeGuid = kubeEndpointService.kubeGuid; + + if (nodeName && kubeGuid) { + this.initialize(nodeName, kubeGuid); + } + } + + initialize(nodeName, kubeGuid) { + this.nodeName = nodeName; + this.kubeGuid = kubeGuid; const nodeEntityService = this.entityServiceFactory.create( this.nodeName, @@ -56,8 +65,6 @@ export class KubernetesNodeService { ); } - - public setupMetricObservable(metric: KubeNodeMetric, metricStatistic: MetricStatistic) { const query = `${metricStatistic}(${metricStatistic}_over_time(${metric}{kubernetes_io_hostname="${this.nodeName}"}[1h]))`; From 68d9356891463100d58fe858bdb758df9dd95c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 07:48:17 -0300 Subject: [PATCH 13/31] sidepanel preview: moved service from shared to app module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- src/frontend/packages/core/src/app.module.ts | 4 +++- src/frontend/packages/core/src/shared/shared.module.ts | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/packages/core/src/app.module.ts b/src/frontend/packages/core/src/app.module.ts index 81584b7822..366810993b 100644 --- a/src/frontend/packages/core/src/app.module.ts +++ b/src/frontend/packages/core/src/app.module.ts @@ -21,6 +21,7 @@ import { AppStoreModule } from '../../store/src/store.module'; import { EndpointModel } from '../../store/src/types/endpoint.types'; import { IFavoriteMetadata, UserFavorite } from '../../store/src/types/user-favorites.types'; import { TabNavService } from '../tab-nav.service'; +import { XSRFModule } from '../xsrf.module'; import { AppComponent } from './app.component'; import { RouteModule } from './app.routing'; import { STRATOS_ENDPOINT_TYPE } from './base-entity-schemas'; @@ -45,8 +46,8 @@ import { LoggedInService } from './logged-in.service'; import { CustomReuseStrategy } from './route-reuse-stragegy'; import { FavoritesConfigMapper } from './shared/components/favorites-meta-card/favorite-config-mapper'; import { endpointEventKey, GlobalEventData, GlobalEventService } from './shared/global-events.service'; +import { PanelPreviewService } from './shared/services/panel-preview.service'; import { SharedModule } from './shared/shared.module'; -import { XSRFModule } from '../xsrf.module'; // Create action for router navigation. See // - https://github.com/ngrx/platform/issues/68 @@ -110,6 +111,7 @@ export class CustomRouterStateSerializer LoggedInService, ExtensionService, DynamicExtensionRoutes, + PanelPreviewService, { provide: GITHUB_API_URL, useFactory: getGitHubAPIURL }, { provide: RouterStateSerializer, useClass: CustomRouterStateSerializer }, // Create action for router navigation { provide: RouteReuseStrategy, useClass: CustomReuseStrategy } diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index a1d52f874c..7f438d72ae 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -116,7 +116,6 @@ import { UsageBytesPipe } from './pipes/usage-bytes.pipe'; import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; -import { PanelPreviewService } from './services/panel-preview.service'; import { UserPermissionDirective } from './user-permission.directive'; /* tslint:disable:max-line-length */ @@ -321,7 +320,6 @@ import { UserPermissionDirective } from './user-permission.directive'; ApplicationStateService, EndpointListHelper, EndpointsListConfigService, - PanelPreviewService, // CfUserService, ConfirmationDialogService, EntityMonitorFactory, From 8ff117aa3c5bfd10a852533296ef0df967a18b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 07:49:28 -0300 Subject: [PATCH 14/31] sidepanel preview: componentFactoryResolver as optional on render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../src/shared/services/panel-preview.service.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts index 04b42e4010..dd31ddcbc2 100644 --- a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -28,12 +28,12 @@ export class PanelPreviewService { this.container = container; } - public show(component: object, props?: { [key: string]: any }) { + public show(component: object, props?: { [key: string]: any }, componentFactoryResolver?: ComponentFactoryResolver) { if (!this.container) { throw new Error('PanelPreviewService: container must be set'); } - this.render(component, props); + this.render(component, props, componentFactoryResolver); this.openedSubject.next(true); } @@ -45,12 +45,16 @@ export class PanelPreviewService { this.openedSubject.next(false); } - render(component: object, props: { [key: string]: any }) { + render( + component: object, + props: { [key: string]: any }, + componentFactoryResolver: ComponentFactoryResolver = this.componentFactoryResolver + ) { if (this.container.length) { this.container.remove(0); } - const factory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(component as any); + const factory: ComponentFactory = componentFactoryResolver.resolveComponentFactory(component as any); const componentRef: ComponentRef = this.container.createComponent(factory); if (props) { From 4f1b164cba05604c274b8cdf35c267a87061f422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 07:49:52 -0300 Subject: [PATCH 15/31] space sidepanel preview: removed delay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../shared/components/space-preview/space-preview.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts index 727611d5c9..2829cc866e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { combineLatest, Observable } from 'rxjs'; -import { delay, filter, map, startWith } from 'rxjs/operators'; +import { filter, map, startWith } from 'rxjs/operators'; import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; import { CloudFoundryEndpointService } from '../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; @@ -40,7 +40,6 @@ export class SpacePreviewComponent implements PreviewableComponent { this.cfSpaceService.userProvidedServiceInstancesCount$ ]).pipe( map(() => false), - delay(50000), startWith(true) ); } From 0835e8b31703508b9958c4d33e72d6dc915cf04e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 09:11:03 -0300 Subject: [PATCH 16/31] store test helper: added object missing property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- src/frontend/packages/core/test-framework/store-test-helper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/core/test-framework/store-test-helper.ts b/src/frontend/packages/core/test-framework/store-test-helper.ts index c2383e2937..8e25d3517a 100644 --- a/src/frontend/packages/core/test-framework/store-test-helper.ts +++ b/src/frontend/packages/core/test-framework/store-test-helper.ts @@ -164,7 +164,8 @@ function getDefaultInitialTestStratosStoreState() { isMobile: false, isMobileNavOpen: false, sideNavPinned: false, - pollingEnabled: true + pollingEnabled: true, + headerEventMinimized: true, }, actionHistory: [], lists: {}, From bebf825f82c9515fb2c1a2be34a19e40e516b65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 09:18:34 -0300 Subject: [PATCH 17/31] sidepanel preview: removed service from shared module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- src/frontend/packages/core/src/shared/shared.module.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index a1d52f874c..7f438d72ae 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -116,7 +116,6 @@ import { UsageBytesPipe } from './pipes/usage-bytes.pipe'; import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; -import { PanelPreviewService } from './services/panel-preview.service'; import { UserPermissionDirective } from './user-permission.directive'; /* tslint:disable:max-line-length */ @@ -321,7 +320,6 @@ import { UserPermissionDirective } from './user-permission.directive'; ApplicationStateService, EndpointListHelper, EndpointsListConfigService, - PanelPreviewService, // CfUserService, ConfirmationDialogService, EntityMonitorFactory, From 5f1462182816cd360f3587501db5c6942fef9621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Tue, 26 Nov 2019 17:09:58 -0300 Subject: [PATCH 18/31] ui: sidepanel preview widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../src/actions/relation.actions.ts | 10 +- .../cloud-foundry/src/cf-entity-generator.ts | 12 ++- .../applications/application.service.ts | 39 ++++--- .../cloud-foundry/cloud-foundry.module.ts | 8 +- .../cloud-foundry-endpoint.service.ts | 42 +++++++- .../cloud-foundry-organization.service.ts | 25 +++-- .../services/cloud-foundry-space.service.ts | 34 +++--- ...cloud-foundry-space-summary.component.html | 2 +- .../application-preview.component.html | 68 ++++++++++++ .../application-preview.component.scss | 0 .../application-preview.component.spec.ts | 37 +++++++ .../application-preview.component.ts | 67 ++++++++++++ .../card-cf-info/card-cf-info.component.html | 4 +- .../card-cf-info/card-cf-info.component.ts | 48 +-------- .../card-cf-space-details.component.html | 2 +- .../card-cf-space-details.component.ts | 7 +- .../cf-endpoint-preview.component.html | 76 +++++++++++++ .../cf-endpoint-preview.component.scss | 0 .../cf-endpoint-preview.component.spec.ts | 37 +++++++ .../cf-endpoint-preview.component.ts | 45 ++++++++ .../shared/components/components.module.ts | 23 +++- .../organization-preview.component.html | 102 ++++++++++++++++++ .../organization-preview.component.scss | 0 .../organization-preview.component.spec.ts | 37 +++++++ .../organization-preview.component.ts | 50 +++++++++ .../space-preview.component.html | 101 +++++++++++++++++ .../space-preview.component.scss | 0 .../space-preview.component.spec.ts | 37 +++++++ .../space-preview/space-preview.component.ts | 47 ++++++++ .../packages/core/sass/_all-theme.scss | 6 +- .../entity-catalogue-entity.ts | 9 +- .../entity-catalogue.types.ts | 5 +- .../dashboard-base.component.html | 8 +- .../dashboard-base.component.scss | 6 +- .../dashboard-base.component.ts | 63 +++++------ .../connect-endpoint-dialog.component.ts | 4 +- .../create-endpoint-connect.component.ts | 4 +- .../favorites-meta-card.component.html | 2 +- .../favorites-meta-card.component.ts | 35 +++++- .../markdown-preview.component.html | 7 +- .../markdown-preview.component.scss | 21 ---- .../markdown-preview.component.ts | 14 ++- .../sidepanel-preview.component.html | 8 ++ .../sidepanel-preview.component.scss | 23 ++++ .../sidepanel-preview.component.spec.ts | 37 +++++++ .../sidepanel-preview.component.theme.scss} | 8 +- .../sidepanel-preview.component.ts | 14 +++ .../core/src/shared/previewable-component.ts | 3 + .../shared/services/panel-preview.service.ts | 67 ++++++++++++ .../packages/core/src/shared/shared.module.ts | 12 ++- .../store/src/actions/dashboard-actions.ts | 13 --- .../store/src/reducers/dashboard-reducer.ts | 21 ++-- 52 files changed, 1125 insertions(+), 225 deletions(-) create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/cf-endpoint-preview/cf-endpoint-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.html create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.scss create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.spec.ts create mode 100644 src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts rename src/frontend/packages/core/src/shared/components/{markdown-preview/markdown-preview.component.theme.scss => sidepanel-preview/sidepanel-preview.component.theme.scss} (67%) create mode 100644 src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts create mode 100644 src/frontend/packages/core/src/shared/previewable-component.ts create mode 100644 src/frontend/packages/core/src/shared/services/panel-preview.service.ts diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index bbf82324c9..92d06c9320 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -1,13 +1,9 @@ +import { HttpParams, HttpRequest } from '@angular/common/http'; + import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; -import { - EntityInlineChildAction, - EntityInlineParentAction, -} from '../entity-relations/entity-relations.types'; import { PaginatedAction } from '../../../store/src/types/pagination.types'; -import { RequestEntityLocation, RequestActionEntity } from '../../../store/src/types/request.types'; -import { CFStartAction } from './cf-action.types'; +import { RequestActionEntity, RequestEntityLocation } from '../../../store/src/types/request.types'; import { EntityTreeRelation } from '../entity-relations/entity-relation-tree'; -import { HttpRequest, HttpParams } from '@angular/common/http'; const relationActionId = 'FetchRelationAction'; 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 bd567c0332..d585f91bae 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,5 @@ import * as moment from 'moment'; + import { IService, IServiceBinding, @@ -39,6 +40,7 @@ import { JetstreamError, } 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 { endpointDisconnectRemoveEntitiesReducer } from '../../store/src/reducers/endpoint-disconnect-application.reducer'; import { APIResource } from '../../store/src/types/api.types'; import { IFavoriteMetadata } from '../../store/src/types/user-favorites.types'; @@ -117,7 +119,11 @@ import { spaceActionBuilders } from './entity-action-builders/space.action-build import { stackActionBuilders } from './entity-action-builders/stack-action-builders'; import { userProvidedServiceActionBuilder } from './entity-action-builders/user-provided-service.action-builders'; import { userActionBuilders } from './entity-action-builders/user.action-builders'; +import { ApplicationPreviewComponent } from './shared/components/application-preview/application-preview.component'; import { CfEndpointDetailsComponent } from './shared/components/cf-endpoint-details/cf-endpoint-details.component'; +import { CfEndpointPreviewComponent } from './shared/components/cf-endpoint-preview/cf-endpoint-preview.component'; +import { OrganizationPreviewComponent } from './shared/components/organization-preview/organization-preview.component'; +import { SpacePreviewComponent } from './shared/components/space-preview/space-preview.component'; import { updateApplicationRoutesReducer } from './store/reducers/application-route.reducer'; import { updateOrganizationQuotaReducer } from './store/reducers/organization-quota.reducer'; import { updateOrganizationSpaceReducer } from './store/reducers/organization-space.reducer'; @@ -129,8 +135,6 @@ import { AppStat } from './store/types/app-metadata.types'; import { CFResponse } from './store/types/cf-api.types'; import { GitBranch, GitCommit, GitRepo } from './store/types/git.types'; import { CfUser } from './store/types/user.types'; -import { CfApplicationState } from './store/types/application.types'; -import { EntitySchema } from '../../store/src/helpers/entity-schema'; export interface CFBasePipelineRequestActionMeta { includeRelations?: string[]; @@ -860,6 +864,7 @@ function generateCfEndpointEntity(endpointDefinition: StratosEndpointExtensionDe return new StratosCatalogueEndpointEntity( endpointDefinition, metadata => `/cloud-foundry/${metadata.guid}`, + () => CfEndpointPreviewComponent, ); } @@ -894,6 +899,7 @@ function generateCfApplicationEntity(endpointDefinition: StratosEndpointExtensio }), getLink: metadata => `/applications/${metadata.cfGuid}/${metadata.guid}/summary`, getGuid: metadata => metadata.guid, + getPreviewableComponent: () => ApplicationPreviewComponent, getLines: () => ([ ['Creation Date', (meta) => meta.createdAt] ]) @@ -930,6 +936,7 @@ function generateCfSpaceEntity(endpointDefinition: StratosEndpointExtensionDefin name: space.entity.name, cfGuid: space.entity.cfGuid, }), + getPreviewableComponent: () => SpacePreviewComponent, getLines: () => ([ ['Name', (meta) => meta.name], ]), @@ -965,6 +972,7 @@ function generateCfOrgEntity(endpointDefinition: StratosEndpointExtensionDefinit name: org.entity.name, cfGuid: org.entity.cfGuid, }), + getPreviewableComponent: () => OrganizationPreviewComponent, getLink: metadata => `/cloud-foundry/${metadata.cfGuid}/organizations/${metadata.guid}`, getGuid: metadata => metadata.guid } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index dd94db5dfe..ab5e375edd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -91,21 +91,9 @@ export class ApplicationService { private appEnvVarsService: ApplicationEnvVarsHelper, private paginationMonitorFactory: PaginationMonitorFactory, ) { - this.appEntityService = this.entityServiceFactory.create>( - appGuid, - createGetApplicationAction(appGuid, cfGuid) - ); - const appSummaryEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, appSummaryEntityType); - const actionBuilder = appSummaryEntity.actionOrchestrator.getActionBuilder('get'); - const getAppSummaryAction = actionBuilder(appGuid, cfGuid); - this.appSummaryEntityService = this.entityServiceFactory.create( - appGuid, - getAppSummaryAction - ); - - this.constructCoreObservables(); - this.constructAmalgamatedObservables(); - this.constructStatusObservables(); + if (cfGuid && appGuid) { + this.initialize(cfGuid, appGuid); + } } // NJ: This needs to be cleaned up. So much going on! @@ -161,6 +149,27 @@ export class ApplicationService { ).pipe(publishReplay(1), refCount()); } + public initialize(cfGuid, appGuid) { + this.cfGuid = cfGuid; + this.appGuid = appGuid; + + this.appEntityService = this.entityServiceFactory.create>( + appGuid, + createGetApplicationAction(appGuid, cfGuid) + ); + const appSummaryEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, appSummaryEntityType); + const actionBuilder = appSummaryEntity.actionOrchestrator.getActionBuilder('get'); + const getAppSummaryAction = actionBuilder(appGuid, cfGuid); + this.appSummaryEntityService = this.entityServiceFactory.create( + appGuid, + getAppSummaryAction + ); + + this.constructCoreObservables(); + this.constructAmalgamatedObservables(); + this.constructStatusObservables(); + } + private constructCoreObservables() { // First set up all the base observables this.app$ = this.appEntityService.waitForEntity$; diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts index 3fae3aaa5a..700f7d5053 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/cloud-foundry.module.ts @@ -36,15 +36,12 @@ import { import { CloudFoundryQuotasComponent, } from '../../../../core/src/features/cloud-foundry/tabs/cloud-foundry-quotas/cloud-foundry-quotas.component'; -import { EndpointListHelper } from '../../../../core/src/shared/components/list/list-types/endpoint/endpoint-list.helpers'; -import { - EndpointsListConfigService, -} from '../../../../core/src/shared/components/list/list-types/endpoint/endpoints-list-config.service'; import { SharedModule } from '../../../../core/src/shared/shared.module'; import { CloudFoundryComponentsModule } from '../../shared/components/components.module'; import { CFEndpointsListConfigService, } from '../../shared/components/list/list-types/cf-endpoints/cf-endpoints-list-config.service'; +import { ApplicationService } from '../applications/application.service'; import { AddOrganizationComponent } from './add-organization/add-organization.component'; import { CreateOrganizationStepComponent, @@ -64,6 +61,7 @@ import { EditSpaceComponent } from './edit-space/edit-space.component'; import { QuotaDefinitionComponent } from './quota-definition/quota-definition.component'; import { CloudFoundryEndpointService } from './services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from './services/cloud-foundry-organization.service'; +import { CloudFoundrySpaceService } from './services/cloud-foundry-space.service'; import { SpaceQuotaDefinitionComponent } from './space-quota-definition/space-quota-definition.component'; import { CfAdminAddUserWarningComponent } from './tabs/cf-admin-add-user-warning/cf-admin-add-user-warning.component'; import { CloudFoundryBuildPacksComponent } from './tabs/cloud-foundry-build-packs/cloud-foundry-build-packs.component'; @@ -228,6 +226,8 @@ import { RemoveUserComponent } from './users/remove-user/remove-user.component'; provide: ActiveRouteCfCell, useValue: {} }, + ApplicationService, + CloudFoundrySpaceService, CloudFoundryOrganizationService, CloudFoundryEndpointService, // CfRolesService, diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts index 20d3bffaa7..146b48f738 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-endpoint.service.ts @@ -65,6 +65,8 @@ export class CloudFoundryEndpointService { hasSSHAccess$: Observable; totalMem$: Observable; + description$: Observable; + apiUrl$: Observable; paginationSubscription: any; appsPagObs: PaginationObservables>; usersCount$: Observable; @@ -100,6 +102,7 @@ export class CloudFoundryEndpointService { }) as PaginatedAction; return getAllOrganizationsAction; } + static createGetAllOrganizationsLimitedSchema(cfGuid: string) { const paginationKey = cfGuid ? createEntityRelationPaginationKey(endpointSchemaKey, cfGuid) @@ -140,7 +143,14 @@ export class CloudFoundryEndpointService { private endpointService: EndpointsService, private paginationMonitorFactory: PaginationMonitorFactory ) { - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; + if (cfGuid) { + this.initialize(cfGuid); + } + } + + public initialize(cfGuid) { + this.cfGuid = cfGuid; this.getAllOrgsAction = CloudFoundryEndpointService.createGetAllOrganizations(this.cfGuid) as GetAllOrganizations; this.getAllAppsAction = new GetAllApplications(createEntityRelationPaginationKey('cf', this.cfGuid), this.cfGuid); @@ -199,6 +209,14 @@ export class CloudFoundryEndpointService { ); this.totalMem$ = this.appsPagObs.entities$.pipe(map(apps => this.getMetricFromApps(apps, 'memory'))); + this.apiUrl$ = this.endpoint$.pipe( + map(endpoint => this.getApiEndpointUrl(endpoint.entity.api_endpoint)) + ); + + this.description$ = this.info$.pipe( + map(entity => this.getDescription(entity)) + ); + this.connected$ = this.endpoint$.pipe( map(p => p.entity.connectionStatus === 'connected') ); @@ -258,6 +276,28 @@ export class CloudFoundryEndpointService { this.store.dispatch(this.getAllAppsAction); } + private getApiEndpointUrl(apiEndpoint) { + const path = apiEndpoint.Path ? `/${apiEndpoint.Path}` : ''; + return `${apiEndpoint.Scheme}://${apiEndpoint.Host}${path}`; + } + + private getMetadataFromInfo(entity: EntityInfo>) { + return entity && entity.entity && entity.entity.entity ? entity.entity.entity : null; + } + + private getDescription(entity: EntityInfo>): string { + const metadata = this.getMetadataFromInfo(entity); + if (metadata) { + if (metadata.description) { + return metadata.description + (metadata.build ? ` (${metadata.build})` : ''); + } + if (metadata.support === 'pcfdev@pivotal.io') { + return 'PCF Dev'; + } + } + return '-'; + } + hasCellMetrics(endpointId: string): Observable { return this.endpointService.hasMetrics(endpointId).pipe( switchMap(hasMetrics => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts index a581a419d1..fe0356517e 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-organization.service.ts @@ -24,19 +24,19 @@ import { ISpaceQuotaDefinition, } from '../../../../../core/src/core/cf-api.types'; import { getEntityFlattenedList, getStartedAppInstanceCount } from '../../../../../core/src/core/cf.helpers'; +import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { CloudFoundryUserProvidedServicesService, } from '../../../../../core/src/shared/services/cloud-foundry-user-provided-services.service'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getOrgRolesString } from '../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; -import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; -import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; export const createOrgQuotaDefinition = (): IOrgQuotaDefinition => ({ memory_limit: -1, @@ -85,7 +85,7 @@ export class CloudFoundryOrganizationService { usersCount$: Observable; constructor( - public activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, + activeRouteCfOrgSpace: ActiveRouteCfOrgSpace, private store: Store, private entityServiceFactory: EntityServiceFactory, private cfUserService: CfUserService, @@ -93,10 +93,12 @@ export class CloudFoundryOrganizationService { private cfEndpointService: CloudFoundryEndpointService, private cfUserProvidedServicesService: CloudFoundryUserProvidedServicesService ) { - this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const orgGuid = activeRouteCfOrgSpace.orgGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; - this.initialiseObservables(); + if (cfGuid && orgGuid) { + this.initialize(cfGuid, orgGuid); + } } public deleteSpace(spaceGuid: string, orgGuid: string, endpointGuid: string) { @@ -110,7 +112,10 @@ export class CloudFoundryOrganizationService { this.cfEndpointService.fetchApps(); } - private initialiseObservables() { + public initialize(cfGuid, orgGuid) { + this.cfGuid = cfGuid; + this.orgGuid = orgGuid; + this.org$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { const relations = [ @@ -133,7 +138,7 @@ export class CloudFoundryOrganizationService { } const orgEntity = entityCatalogue.getEntity(CF_ENDPOINT_TYPE, organizationEntityType); const getOrgActionBuilder = orgEntity.actionOrchestrator.getActionBuilder('get'); - const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid, relations); + const getOrgAction = getOrgActionBuilder(this.orgGuid, this.cfGuid, { includeRelations: relations }); const orgEntityService = this.entityServiceFactory.create>( this.orgGuid, getOrgAction @@ -194,8 +199,8 @@ export class CloudFoundryOrganizationService { return CloudFoundryEndpointService.fetchAppCount( this.store, this.paginationMonitorFactory, - this.activeRouteCfOrgSpace.cfGuid, - this.activeRouteCfOrgSpace.orgGuid + this.cfGuid, + this.orgGuid ); } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 4ba4505590..371b9874d9 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { combineLatest, Observable, of } from 'rxjs'; -import { filter, map, publishReplay, refCount, switchMap } from 'rxjs/operators'; +import { map, publishReplay, refCount, switchMap } from 'rxjs/operators'; import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { @@ -15,12 +15,14 @@ import { import { SpaceUserRoleNames } from '../../../../../cloud-foundry/src/store/types/user.types'; import { IApp, IOrgQuotaDefinition, IRoute, ISpace, ISpaceQuotaDefinition } from '../../../../../core/src/core/cf-api.types'; import { getStartedAppInstanceCount } from '../../../../../core/src/core/cf.helpers'; +import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; import { PaginationMonitorFactory } from '../../../../../core/src/shared/monitors/pagination-monitor.factory'; import { CloudFoundryUserProvidedServicesService, } from '../../../../../core/src/shared/services/cloud-foundry-user-provided-services.service'; import { APIResource, EntityInfo } from '../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; import { createEntityRelationKey } from '../../../entity-relations/entity-relations.types'; import { CfUserService } from '../../../shared/data-services/cf-user.service'; import { fetchServiceInstancesCount } from '../../service-catalog/services-helper'; @@ -28,8 +30,6 @@ import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { getSpaceRolesString } from '../cf.helpers'; import { CloudFoundryEndpointService } from './cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService, createOrgQuotaDefinition } from './cloud-foundry-organization.service'; -import { entityCatalogue } from '../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; -import { CF_ENDPOINT_TYPE } from '../../../../cf-types'; @Injectable() export class CloudFoundrySpaceService { @@ -48,6 +48,7 @@ export class CloudFoundrySpaceService { */ spaceQuotaDefinition$: Observable; allowSsh$: Observable; + allowSshStatus$: Observable; totalMem$: Observable; routes$: Observable[]>; serviceInstancesCount$: Observable; @@ -71,18 +72,20 @@ export class CloudFoundrySpaceService { private cfOrgService: CloudFoundryOrganizationService ) { - this.spaceGuid = activeRouteCfOrgSpace.spaceGuid; - this.orgGuid = activeRouteCfOrgSpace.orgGuid; - this.cfGuid = activeRouteCfOrgSpace.cfGuid; + const spaceGuid = activeRouteCfOrgSpace.spaceGuid; + const orgGuid = activeRouteCfOrgSpace.orgGuid; + const cfGuid = activeRouteCfOrgSpace.cfGuid; - this.initialiseObservables(); + if (cfGuid && orgGuid && spaceGuid) { + this.initialize(cfGuid, orgGuid, spaceGuid); + } } - public fetchApps() { - this.cfEndpointService.fetchApps(); - } + public initialize(cfGuid: string, orgGuid: string, spaceGuid: string) { + this.cfGuid = cfGuid; + this.orgGuid = orgGuid; + this.spaceGuid = spaceGuid; - private initialiseObservables() { this.initialiseSpaceObservables(); this.initialiseAppObservables(); @@ -126,7 +129,7 @@ export class CloudFoundrySpaceService { this.spaceGuid, getSpaceAction ); - return spaceEntityService.entityObs$.pipe(filter(o => !!o && !!o.entity)); + return spaceEntityService.waitForEntity$; }), publishReplay(1), refCount() @@ -142,6 +145,7 @@ export class CloudFoundrySpaceService { this.cfUserProvidedServicesService.fetchUserProvidedServiceInstancesCount(this.cfGuid, this.orgGuid, this.spaceGuid); this.routes$ = this.space$.pipe(map(o => o.entity.entity.routes)); this.allowSsh$ = this.space$.pipe(map(o => o.entity.entity.allow_ssh ? 'true' : 'false')); + this.allowSshStatus$ = this.allowSsh$.pipe(map(status => status === 'false' ? 'Disabled' : 'Enabled')); this.spaceQuotaDefinition$ = this.space$.pipe( map(q => q.entity.entity.space_quota_definition ? q.entity.entity.space_quota_definition.entity : null) ); @@ -211,9 +215,9 @@ export class CloudFoundrySpaceService { return CloudFoundryEndpointService.fetchAppCount( this.store, this.paginationMonitorFactory, - this.activeRouteCfOrgSpace.cfGuid, - this.activeRouteCfOrgSpace.orgGuid, - this.activeRouteCfOrgSpace.spaceGuid + this.cfGuid, + this.orgGuid, + this.spaceGuid ); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html index 3ec288a9fa..1f03e7bdb5 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html @@ -75,7 +75,7 @@ + [loading$]="cfSpaceService.loadingApps$" (refresh)="cfEndpointService.fetchApps()"> diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html new file mode 100644 index 0000000000..9900b6a612 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -0,0 +1,68 @@ + + + + +
+ + + Summary + + + + + + {{ appSvc.cf?.name}} + + + {{ appSvc.cf?.name}} + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts new file mode 100644 index 0000000000..2d3d25d230 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { ApplicationPreviewComponent } from './application-preview.component'; + +describe('ApplicationPreviewComponent', () => { + let component: ApplicationPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ApplicationPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ApplicationPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts new file mode 100644 index 0000000000..5aa2772e67 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts @@ -0,0 +1,67 @@ +import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { combineLatest, map } from 'rxjs/operators'; + +import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; +import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; +import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; +import { ApplicationService } from '../../../features/applications/application.service'; +import { getGuids } from '../../../features/applications/application/application-base.component'; + +@Component({ + selector: 'app-application-preview-component', + templateUrl: './application-preview.component.html', + styleUrls: ['./application-preview.component.scss'], + // useless, necessary to reuse ApplicationService + providers: [ + ApplicationService, + { + provide: CF_GUID, + useFactory: getGuids('cf'), + deps: [ActivatedRoute] + }, + { + provide: APP_GUID, + useFactory: getGuids(), + deps: [ActivatedRoute] + }, + ] +}) +export class ApplicationPreviewComponent implements PreviewableComponent { + + title = null; + cfEndpointService: object; + sshStatus$: Observable; + + getFullEndpointApiUrl = getFullEndpointApiUrl; + + constructor(public applicationService: ApplicationService) { + } + + setProps(props: { [key: string]: any }) { + this.title = props.title; + + this.applicationService.initialize(props.cfGuid, props.guid); + this.sshStatus$ = this.applicationService.application$.pipe( + combineLatest(this.applicationService.appSpace$), + map(([app, space]) => { + if (!space.entity.allow_ssh) { + return 'Disabled by the space'; + } else { + return app.app.entity.enable_ssh ? 'Yes' : 'No'; + } + }) + ); + + // this.detailsLoading$ = combineLatest([ + // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now + // this.cfEndpointService.appsPagObs.fetchingEntities$.pipe( + // filter(loading => !loading) + // ), + // ]).pipe( + // map(() => false), + // startWith(true) + // ); + } +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html index 9ce13dc103..e8a2a95463 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/cards/card-cf-info/card-cf-info.component.html @@ -6,8 +6,8 @@ \ No newline at end of file diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index 8c10e82191..db2916d0c7 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -15,6 +15,10 @@ $app-sub-header-height: 48px; } &__side-help-outer { z-index: 9999; + + @include breakpoint(mobileonly) { + left: 0; + } } &__side-help-button { position: absolute; @@ -50,7 +54,7 @@ $app-sub-header-height: 48px; padding: 0; } &__side-help { - max-width: 600px; + min-width: 600px; } } diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts index ad8a819158..df6b6dd0e5 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.ts @@ -1,6 +1,6 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; import { Portal } from '@angular/cdk/portal'; -import { Component, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import { MatDrawer } from '@angular/material'; import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router } from '@angular/router'; import { Store } from '@ngrx/store'; @@ -13,12 +13,7 @@ import { cfInfoEntityType } from '../../../../../cloud-foundry/src/cf-entity-typ import { CfInfoDefinitionActionBuilders, } from '../../../../../cloud-foundry/src/entity-action-builders/cf-info.action-builders'; -import { - CloseSideHelp, - CloseSideNav, - DisableMobileNav, - EnableMobileNav, -} from '../../../../../store/src/actions/dashboard-actions'; +import { CloseSideNav, DisableMobileNav, EnableMobileNav } from '../../../../../store/src/actions/dashboard-actions'; import { GetUserFavoritesAction } from '../../../../../store/src/actions/user-favourites-actions/get-user-favorites-action'; import { DashboardOnlyAppState } from '../../../../../store/src/app-state'; import { DashboardState } from '../../../../../store/src/reducers/dashboard-reducer'; @@ -29,6 +24,7 @@ import { CustomizationService } from '../../../core/customizations.types'; import { EndpointsService } from '../../../core/endpoints.service'; import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IEntityMetadata } from '../../../core/entity-catalogue/entity-catalogue.types'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { PageHeaderService } from './../../../core/page-header-service/page-header.service'; import { SideNavItem } from './../side-nav/side-nav.component'; @@ -39,16 +35,28 @@ import { SideNavItem } from './../side-nav/side-nav.component'; styleUrls: ['./dashboard-base.component.scss'] }) -export class DashboardBaseComponent implements OnInit, OnDestroy { +export class DashboardBaseComponent implements OnInit, OnDestroy, AfterViewInit { public activeTabLabel$: Observable; public subNavData$: Observable<[string, Portal]>; public isMobile$: Observable; public sideNavMode$: Observable; public sideNavMode: string; public mainNavState$: Observable<{ mode: string; opened: boolean; iconMode: boolean }>; - public rightNavState$: Observable<{ opened: boolean; documentUrl: string; }>; + public rightNavState$: Observable<{ opened: boolean, component?: object, props?: object }>; private dashboardState$: Observable; + public noMargin$: Observable; + private closeSub: Subscription; + private mobileSub: Subscription; private drawer: MatDrawer; + public iconModeOpen = false; + public sideNavWidth = 54; + + sideNavTabs: SideNavItem[] = this.getNavigationRoutes(); + sideNaveMode = 'side'; + + @ViewChild('previewPanelContainer', { read: ViewContainerRef, static: false }) previewPanelContainer: ViewContainerRef; + + @ViewChild('content', { static: false }) public content; constructor( public pageHeaderService: PageHeaderService, @@ -59,6 +67,7 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { private endpointsService: EndpointsService, public tabNavService: TabNavService, private ngZone: NgZone, + public panelPreviewService: PanelPreviewService, private cs: CustomizationService ) { this.noMargin$ = this.router.events.pipe( @@ -89,24 +98,11 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { } }) ); - this.rightNavState$ = this.dashboardState$.pipe( - map(state => ({ - opened: !!state.sideHelpDocument && state.sideHelpOpen, - documentUrl: state.sideHelpDocument - })) - ); + this.mobileSub = this.isMobile$ .subscribe(isMobile => isMobile ? this.store.dispatch(new EnableMobileNav()) : this.store.dispatch(new DisableMobileNav())); } - public helpDocumentUrl: string; - - private closeSub: Subscription; - - public noMargin$: Observable; - - private mobileSub: Subscription; - @ViewChild('sidenav', { static: false }) set sidenav(drawer: MatDrawer) { this.drawer = drawer; if (!this.closeSub) { @@ -119,15 +115,6 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { } } - @ViewChild('content', { static: true }) public content; - - sideNavTabs: SideNavItem[] = this.getNavigationRoutes(); - - sideNaveMode = 'side'; - - public iconModeOpen = false; - public sideNavWidth = 54; - public redrawSideNav() { // We need to do this to ensure there isn't a space left behind // when going from mobile to desktop @@ -140,6 +127,14 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { this.store.dispatch(new GetCurrentUsersRelations()); } + sideHelpClosed() { + this.panelPreviewService.hide(); + } + + ngAfterViewInit() { + this.panelPreviewService.setContainer(this.previewPanelContainer); + } + ngOnInit() { this.subNavData$ = combineLatest( this.tabNavService.getCurrentTabHeaderObservable().pipe( @@ -172,10 +167,6 @@ export class DashboardBaseComponent implements OnInit, OnDestroy { return false; } - public sideHelpClosed() { - this.store.dispatch(new CloseSideHelp()); - } - private getNavigationRoutes(): SideNavItem[] { let navItems = this.collectNavigationRoutes('', this.router.config); diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index e418b05aff..b4c554eba4 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -3,7 +3,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { Subscription } from 'rxjs'; -import { ShowSideHelp } from '../../../../../store/src/actions/dashboard-actions'; import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; @@ -38,7 +37,8 @@ export class ConnectEndpointDialogComponent implements OnDestroy { } showHelp() { - this.store.dispatch(new ShowSideHelp(this.helpDocumentUrl)); + // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); + // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); } ngOnDestroy(): void { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index d513432c29..b2282192bb 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -3,7 +3,6 @@ import { Store } from '@ngrx/store'; import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { ShowSideHelp } from '../../../../../../store/src/actions/dashboard-actions'; import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; @@ -31,7 +30,8 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { } showHelp() { - this.store.dispatch(new ShowSideHelp(this.helpDocumentUrl)); + // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); + // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); } onEnter = (data: ConnectEndpointConfig) => { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html index 53a2b5e295..9af11474b9 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.html @@ -1,5 +1,5 @@ diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index a252d8b69e..29c8e373ef 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -7,10 +7,15 @@ import { CFAppState } from '../../../../../cloud-foundry/src/cf-app-state'; import { RemoveUserFavoriteAction, } from '../../../../../store/src/actions/user-favourites-actions/remove-user-favorite-action'; -import { endpointEntitiesSelector } from '../../../../../store/src/selectors/endpoint.selectors'; +import { + endpointEntitiesSelector, + endpointsEntityRequestDataSelector, +} from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; +import { PanelPreviewService } from '../../services/panel-preview.service'; import { ComponentEntityMonitorConfig, StratosStatus } from '../../shared.types'; import { ConfirmationDialogConfig } from '../confirmation-dialog.config'; import { ConfirmationDialogService } from '../confirmation-dialog.service'; @@ -97,7 +102,33 @@ export class FavoritesMetaCardComponent { } } - constructor(private store: Store, private confirmDialog: ConfirmationDialogService) { } + constructor( + private store: Store, + private confirmDialog: ConfirmationDialogService, + private panelPreviewService: PanelPreviewService + ) { } + + previewPanel() { + const catalogueEntity = entityCatalogue.getEntity(this.favorite.endpointType, this.favorite.entityType); + const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); + + // TODO: use 'endpoint' as constant + if (this.favorite.entityType === 'endpoint') { + const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + entity$, + cfGuid: this.favorite.endpointId + }); + } else { + this.panelPreviewService.show(previewComponent, { + title: this.favorite.metadata.name, + cfGuid: this.favorite.metadata.cfGuid, + orgGuid: this.favorite.metadata.orgGuid, + guid: this.favorite.metadata.guid + }); + } + } public setConfirmation(prettyName: string, favorite: UserFavorite) { this.confirmation = new ConfirmationDialogConfig( diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html index 4ad4077847..972ac85319 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.html @@ -1,6 +1,3 @@ -
-
-

{{ title }}

-
+
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss index 2a7eaa882f..e69de29bb2 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.scss @@ -1,21 +0,0 @@ -.markdown-preview { - display: flex; - flex-direction: column; - height: 100vh; - &__header { - display: flex; - flex: 0 0 56px; - height: 56px; - - h1 { - align-self: center; - font-size: 20px; - margin-left: 24px; - } - } - &__content { - flex: 1; - overflow: auto; - padding: 10px 24px; - } -} diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index 97384669d0..dab1792c71 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -4,13 +4,14 @@ import { DomSanitizer } from '@angular/platform-browser'; import * as markdown from 'marked'; import { LoggerService } from '../../../core/logger.service'; +import { PreviewableComponent } from '../../previewable-component'; @Component({ selector: 'app-markdown-preview', templateUrl: './markdown-preview.component.html', styleUrls: ['./markdown-preview.component.scss'] }) -export class MarkdownPreviewComponent { +export class MarkdownPreviewComponent implements PreviewableComponent { markdownHtml: string; documentUrl: string; @@ -27,7 +28,16 @@ export class MarkdownPreviewComponent { @ViewChild('markdown', { static: true }) public markdown: ElementRef; - constructor(private httpClient: HttpClient, private logger: LoggerService, private domSanitizer: DomSanitizer) { } + constructor( + private httpClient: HttpClient, + private logger: LoggerService, + private domSanitizer: DomSanitizer + ) { } + + setProps(props: { [key: string]: any }) { + console.log('props', props); + this.setDocumentUrl = props.documentUrl; + } private loadDocument() { this.httpClient.get(this.documentUrl, { responseType: 'text' }).subscribe( diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html new file mode 100644 index 0000000000..9e95cac0d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.html @@ -0,0 +1,8 @@ +
+
+

{{ title }}

+
+
+ +
+
\ No newline at end of file diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss new file mode 100644 index 0000000000..c3afff37d8 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.scss @@ -0,0 +1,23 @@ +.sidepanel-preview { + display: flex; + flex-direction: column; + height: 100vh; + &__header { + display: flex; + flex: 0 0 56px; + height: 56px; + + h1 { + align-self: center; + font-size: 20px; + margin-left: 24px; + } + } + &__content { + flex: 1; + overflow-x: hidden; + overflow-y: auto; + padding: 12px 15px; + position: relative; + } +} diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts new file mode 100644 index 0000000000..2eb1505ae2 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.spec.ts @@ -0,0 +1,37 @@ +import { HttpClient, HttpClientModule, HttpHandler } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoreTestingModule } from '../../../../test-framework/core-test.modules'; +import { createBasicStoreModule } from '../../../../test-framework/store-test-helper'; +import { LoggerService } from '../../../core/logger.service'; +import { SidepanelPreviewComponent } from './sidepanel-preview.component'; + +describe('SidepanelPreviewComponent', () => { + let component: SidepanelPreviewComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SidepanelPreviewComponent], + providers: [LoggerService, HttpClient, HttpHandler], + imports: [ + HttpClientModule, + HttpClientTestingModule, + CoreTestingModule, + createBasicStoreModule() + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SidepanelPreviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss similarity index 67% rename from src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss rename to src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss index 926eb8127e..c84bed25dc 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.theme.scss +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.theme.scss @@ -1,12 +1,14 @@ -@mixin app-markdown-preview-theme($theme, $app-theme) { +@mixin app-sidepanel-preview-theme($theme, $app-theme) { $primary: map-get($theme, primary); - .markdown-preview__header { + .sidepanel-preview__header { background-color: mat-color($primary); color: mat-contrast($primary, 500); } - .markdown-preview__content { + .sidepanel-preview__content { + background-color: map-get($app-theme, app-background-color); + > h1:first-child { height: 0; margin: 0; diff --git a/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts new file mode 100644 index 0000000000..315d11c160 --- /dev/null +++ b/src/frontend/packages/core/src/shared/components/sidepanel-preview/sidepanel-preview.component.ts @@ -0,0 +1,14 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-sidepanel-preview', + templateUrl: './sidepanel-preview.component.html', + styleUrls: ['./sidepanel-preview.component.scss'] +}) +export class SidepanelPreviewComponent { + + @Input() + title: string; + + constructor() { } +} diff --git a/src/frontend/packages/core/src/shared/previewable-component.ts b/src/frontend/packages/core/src/shared/previewable-component.ts new file mode 100644 index 0000000000..40410d6f07 --- /dev/null +++ b/src/frontend/packages/core/src/shared/previewable-component.ts @@ -0,0 +1,3 @@ +export interface PreviewableComponent { + setProps(props: { [key: string]: any }): void; +} diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts new file mode 100644 index 0000000000..dda1d3e374 --- /dev/null +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -0,0 +1,67 @@ +import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; +import { observeOn, publishReplay, refCount } from 'rxjs/operators'; + +@Injectable() +export class PanelPreviewService { + private openedSubject: BehaviorSubject; + public opened$: Observable; + + private container: ViewContainerRef; + + constructor(private componentFactoryResolver: ComponentFactoryResolver) { + this.openedSubject = new BehaviorSubject(false); + this.opened$ = this.observeSubject(this.openedSubject); + } + + public setContainer(container: ViewContainerRef) { + if (this.container) { + throw new Error('PanelPreviewService: container already set'); + } + + this.container = container; + } + + public show(component: object, props?: { [key: string]: any }) { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.render(component, props); + this.openedSubject.next(true); + } + + public hide() { + if (!this.container) { + throw new Error('PanelPreviewService: container must be set'); + } + + this.openedSubject.next(false); + } + + render(component: object, props: { [key: string]: any }) { + if (this.container.length) { + this.container.remove(0); + } + + const factory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(component as any); + const componentRef: ComponentRef = this.container.createComponent(factory); + + if (props) { + componentRef.instance.setProps(props); + } + } + + public clear() { + this.container.clear(); + this.openedSubject.next(false); + } + + private observeSubject(subject: Subject) { + return subject.asObservable().pipe( + publishReplay(1), + refCount(), + observeOn(asapScheduler) + ); + } +} diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index 4447caf735..a1d52f874c 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -82,6 +82,7 @@ import { PageSubNavComponent } from './components/page-sub-nav/page-sub-nav.comp import { PollingIndicatorComponent } from './components/polling-indicator/polling-indicator.component'; import { RingChartComponent } from './components/ring-chart/ring-chart.component'; import { RoutingIndicatorComponent } from './components/routing-indicator/routing-indicator.component'; +import { SidepanelPreviewComponent } from './components/sidepanel-preview/sidepanel-preview.component'; import { SimpleUsageChartComponent } from './components/simple-usage-chart/simple-usage-chart.component'; import { SnackBarReturnComponent } from './components/snackbar-return/snackbar-return.component'; import { SshViewerComponent } from './components/ssh-viewer/ssh-viewer.component'; @@ -115,6 +116,7 @@ import { UsageBytesPipe } from './pipes/usage-bytes.pipe'; import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; +import { PanelPreviewService } from './services/panel-preview.service'; import { UserPermissionDirective } from './user-permission.directive'; /* tslint:disable:max-line-length */ @@ -211,13 +213,13 @@ import { UserPermissionDirective } from './user-permission.directive'; BreadcrumbsComponent, PageSubNavSectionComponent, EntitySummaryTitleComponent, - MarkdownPreviewComponent, MarkdownContentObserverDirective, SnackBarReturnComponent, PollingIndicatorComponent, UnlimitedInputComponent, SimpleListComponent, ListHostDirective, + SidepanelPreviewComponent ], exports: [ ApplicationStateIconPipe, @@ -300,24 +302,26 @@ import { UserPermissionDirective } from './user-permission.directive'; AppNameUniqueDirective, SimpleUsageChartComponent, EntitySummaryTitleComponent, - MarkdownPreviewComponent, MarkdownContentObserverDirective, AppNameUniqueDirective, PollingIndicatorComponent, UnlimitedInputComponent, SimpleListComponent, - ListHostDirective + ListHostDirective, + SidepanelPreviewComponent, ], entryComponents: [ DialogConfirmComponent, EnvVarViewComponent, - SnackBarReturnComponent + SnackBarReturnComponent, + MarkdownPreviewComponent, ], providers: [ ListConfig, ApplicationStateService, EndpointListHelper, EndpointsListConfigService, + PanelPreviewService, // CfUserService, ConfirmationDialogService, EntityMonitorFactory, diff --git a/src/frontend/packages/store/src/actions/dashboard-actions.ts b/src/frontend/packages/store/src/actions/dashboard-actions.ts index 3918585f85..b06a5d043a 100644 --- a/src/frontend/packages/store/src/actions/dashboard-actions.ts +++ b/src/frontend/packages/store/src/actions/dashboard-actions.ts @@ -11,10 +11,6 @@ export const SET_HEADER_EVENT = '[Dashboard] Set header event'; export const ENABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Enable mobile nav'; export const DISABLE_SIDE_NAV_MOBILE_MODE = '[Dashboard] Disable mobile nav'; -export const SHOW_SIDE_HELP = '[Dashboard] Show side help'; -export const CLOSE_SIDE_HELP = '[Dashboard] Close side help'; - - export const TIMEOUT_SESSION = '[Dashboard] Timeout Session'; export const ENABLE_POLLING = '[Dashboard] Enable Polling'; export const SET_STRATOS_THEME = '[Dashboard] Set Theme'; @@ -38,15 +34,6 @@ export class ToggleSideNav implements Action { type = TOGGLE_SIDE_NAV; } -export class ShowSideHelp implements Action { - constructor(public document: string) { } - type = SHOW_SIDE_HELP; -} - -export class CloseSideHelp implements Action { - type = CLOSE_SIDE_HELP; -} - export class SetHeaderEvent implements Action { constructor(public minimised = false) { } type = SET_HEADER_EVENT; diff --git a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts index b5f606d521..9b8baa772a 100644 --- a/src/frontend/packages/store/src/reducers/dashboard-reducer.ts +++ b/src/frontend/packages/store/src/reducers/dashboard-reducer.ts @@ -1,5 +1,4 @@ import { - CLOSE_SIDE_HELP, CLOSE_SIDE_NAV, DISABLE_SIDE_NAV_MOBILE_MODE, ENABLE_POLLING, @@ -7,11 +6,12 @@ import { HYDRATE_DASHBOARD_STATE, HydrateDashboardStateAction, OPEN_SIDE_NAV, + SET_HEADER_EVENT, SET_STRATOS_THEME, + SetHeaderEvent, SetPollingEnabledAction, SetSessionTimeoutAction, SetThemeAction, - SHOW_SIDE_HELP, TIMEOUT_SESSION, TOGGLE_SIDE_NAV, } from '../actions/dashboard-actions'; @@ -23,9 +23,8 @@ export interface DashboardState { isMobile: boolean; isMobileNavOpen: boolean; sideNavPinned: boolean; - sideHelpOpen: boolean; - sideHelpDocument: string; themeKey: string; + headerEventMinimized: boolean; } export const defaultDashboardState: DashboardState = { @@ -35,9 +34,8 @@ export const defaultDashboardState: DashboardState = { isMobile: false, isMobileNavOpen: false, sideNavPinned: true, - sideHelpOpen: false, - sideHelpDocument: null, - themeKey: null + themeKey: null, + headerEventMinimized: false, }; export function dashboardReducer(state: DashboardState = defaultDashboardState, action): DashboardState { @@ -61,10 +59,11 @@ export function dashboardReducer(state: DashboardState = defaultDashboardState, return { ...state, isMobile: true, isMobileNavOpen: false }; case DISABLE_SIDE_NAV_MOBILE_MODE: return { ...state, isMobile: false, isMobileNavOpen: false }; - case SHOW_SIDE_HELP: - return { ...state, sideHelpOpen: true, sideHelpDocument: action.document }; - case CLOSE_SIDE_HELP: - return { ...state, sideHelpOpen: false, sideHelpDocument: '' }; + case SET_HEADER_EVENT: + const setHeaderEvent = action as SetHeaderEvent; + return { + ...state, headerEventMinimized: setHeaderEvent.minimised + }; case TIMEOUT_SESSION: const timeoutSessionAction = action as SetSessionTimeoutAction; return { From 192f5a145778318386b952740326b0a778b2dc52 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Fri, 29 Nov 2019 17:16:07 +0000 Subject: [PATCH 19/31] Fix issue where clicking on org and then app fav failed - Issue 1 (slightly unrelated) - app service getspace action used space entity without an org - Issue 2 - app entity validation found it was missing the space - validation process fetched space with custom action - custom action did not contain schema key to use space with org schema - org was not stored correctly in store (contained in space rather than seperatly) - Still to do - Fix for issue 2 would need to be expanded to ALL schema's with inline entities - These new schemas would need to be added to their entities - All usages would have to provide the overriding schemaKey --- .../src/actions/relation.actions.ts | 2 + .../actions/user-provided-service.actions.ts | 6 +-- .../cloud-foundry/src/cf-entity-factory.ts | 38 +++++++++---------- .../src/cf-entity-schema-types.ts | 10 +++-- .../applications/application.service.ts | 29 +++++++------- ...aces-user-service-instances-data-source.ts | 12 +++--- 6 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index 92d06c9320..c03bbd6e20 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -19,6 +19,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit ) { super(); this.entityType = child.entityType; + this.schemaKey = child.entity.schemaKey; this.options = new HttpRequest( 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, @@ -30,6 +31,7 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit } entity: RequestActionEntity; entityType: string; + schemaKey: string; isId = relationActionId; actions = [ '[Fetch Relations] Start', 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 09713ee768..bdedde7d05 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,3 +1,5 @@ +import { HttpRequest } from '@angular/common/http'; + import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; @@ -10,7 +12,6 @@ import { organizationEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../cf-entity-types'; import { @@ -19,10 +20,9 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; -import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ - createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), + createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType), createEntityRelationKey(userProvidedServiceInstanceEntityType, serviceBindingEntityType), createEntityRelationKey(serviceBindingEntityType, applicationEntityType) diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 5ed43bd63c..1da855c14e 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -244,7 +244,7 @@ const SpaceWithOrgsEntitySchema = new CFSpaceEntitySchema({ apps: [ApplicationWithoutSpaceEntitySchema], organization: OrganizationsWithoutSpaces, } -}, spaceWithOrgEntityType); +}, null, spaceWithOrgEntityType); entityCache[spaceWithOrgEntityType] = SpaceWithOrgsEntitySchema; @@ -286,7 +286,7 @@ const ApplicationEntitySchema = new CFApplicationEntitySchema( service_instances: [ServiceInstancesWithNoBindingsSchema], organization: OrganizationsWithoutSpaces, } - }), + }, null, spaceWithOrgEntityType), routes: [RouteNoAppsSchema], service_bindings: [ServiceBindingsSchema] } @@ -328,24 +328,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity - }; + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; } - }); + const entity = { + ...user.entity, + username: user.metadata.guid + }; + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } +}); entityCache[cfUserEntityType] = CFUserSchema; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts index 5e5ccbe303..ec7741c151 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts @@ -33,9 +33,10 @@ export class CFEntitySchema extends EntitySchema { definition?: Schema, options?: schema.EntityOptions, relationKey?: string, - excludeFromRecursiveDelete?: string[] + excludeFromRecursiveDelete?: string[], + schemaKey?: string ) { - super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, null, excludeFromRecursiveDelete); + super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, schemaKey, excludeFromRecursiveDelete); } } @@ -124,7 +125,8 @@ export class CFApplicationEntitySchema extends CFEntitySchema { export class CFSpaceEntitySchema extends CFEntitySchema { constructor( definition?: Schema, - relationKey?: string + relationKey?: string, + schemaKey?: string ) { super(spaceEntityType, definition, { idAttribute: getAPIResourceGuid }, relationKey, [ domainEntityType, @@ -133,6 +135,6 @@ export class CFSpaceEntitySchema extends CFEntitySchema { servicePlanEntityType, // App Related stackEntityType - ]); + ], schemaKey); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index ab5e375edd..c854e5f873 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; -import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,6 +47,7 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; +import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -59,13 +60,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -186,6 +187,8 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); + getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); + getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -198,13 +201,9 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(app => this.appSpace$.pipe( - map(space => space.entity.organization_guid), - switchMap(orgGuid => { - return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); - }), - filter(org => !!org) - )) + switchMap(() => this.appSpace$), + map(space => space.entity.organization), + filter(org => !!org) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts index 4c88d174d8..6769675f44 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts @@ -1,19 +1,18 @@ import { Store } from '@ngrx/store'; -import { GetAllUserProvidedServices } from '../../../../../../../cloud-foundry/src/actions/user-provided-service.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; +import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -22,11 +21,12 @@ import { IListConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; +import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; +import { + UserProvidedServiceActionBuilder, +} from '../../../../../entity-action-builders/user-provided-service.action-builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; -import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; -import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; -import { UserProvidedServiceActionBuilder } from '../../../../../entity-action-builders/user-provided-service.action-builders'; export class CfSpacesUserServiceInstancesDataSource extends ListDataSource { constructor(cfGuid: string, spaceGuid: string, store: Store, listConfig?: IListConfig) { @@ -38,7 +38,7 @@ export class CfSpacesUserServiceInstancesDataSource extends ListDataSource Date: Fri, 29 Nov 2019 18:04:12 +0000 Subject: [PATCH 20/31] Revert "Fix issue where clicking on org and then app fav failed" This reverts commit 0a16203284cc0b42d172972047f5d08cddfecc99. --- .../src/actions/relation.actions.ts | 2 - .../actions/user-provided-service.actions.ts | 6 +-- .../cloud-foundry/src/cf-entity-factory.ts | 38 +++++++++---------- .../src/cf-entity-schema-types.ts | 10 ++--- .../applications/application.service.ts | 29 +++++++------- ...aces-user-service-instances-data-source.ts | 12 +++--- 6 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index c03bbd6e20..92d06c9320 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -19,7 +19,6 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit ) { super(); this.entityType = child.entityType; - this.schemaKey = child.entity.schemaKey; this.options = new HttpRequest( 'GET', url.startsWith('/v2/') ? url.substring(4, url.length) : url, @@ -31,7 +30,6 @@ export abstract class FetchRelationAction extends CFStartAction implements Entit } entity: RequestActionEntity; entityType: string; - schemaKey: string; isId = relationActionId; actions = [ '[Fetch Relations] Start', 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 bdedde7d05..09713ee768 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,5 +1,3 @@ -import { HttpRequest } from '@angular/common/http'; - import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catalogue/entity-catalogue.types'; import { getActions } from '../../../store/src/actions/action.helper'; import { endpointSchemaKey } from '../../../store/src/helpers/entity-factory'; @@ -12,6 +10,7 @@ import { organizationEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../cf-entity-types'; import { @@ -20,9 +19,10 @@ import { EntityInlineParentAction, } from '../entity-relations/entity-relations.types'; import { CFStartAction } from './cf-action.types'; +import { HttpRequest } from '@angular/common/http'; export const getUserProvidedServiceInstanceRelations = [ - createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceEntityType), + createEntityRelationKey(userProvidedServiceInstanceEntityType, spaceWithOrgEntityType), createEntityRelationKey(spaceEntityType, organizationEntityType), createEntityRelationKey(userProvidedServiceInstanceEntityType, serviceBindingEntityType), createEntityRelationKey(serviceBindingEntityType, applicationEntityType) diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts index 1da855c14e..5ed43bd63c 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-factory.ts @@ -244,7 +244,7 @@ const SpaceWithOrgsEntitySchema = new CFSpaceEntitySchema({ apps: [ApplicationWithoutSpaceEntitySchema], organization: OrganizationsWithoutSpaces, } -}, null, spaceWithOrgEntityType); +}, spaceWithOrgEntityType); entityCache[spaceWithOrgEntityType] = SpaceWithOrgsEntitySchema; @@ -286,7 +286,7 @@ const ApplicationEntitySchema = new CFApplicationEntitySchema( service_instances: [ServiceInstancesWithNoBindingsSchema], organization: OrganizationsWithoutSpaces, } - }, null, spaceWithOrgEntityType), + }), routes: [RouteNoAppsSchema], service_bindings: [ServiceBindingsSchema] } @@ -328,24 +328,24 @@ const CFUserSchema = new CFUserEntitySchema({ audited_spaces: [createUserOrgSpaceSchema(spaceEntityType, {}, CfUserRoleParams.AUDITED_SPACES)], } }, { - idAttribute: getAPIResourceGuid, - processStrategy: (user: APIResource) => { - if (user.entity.username) { - return user; - } - const entity = { - ...user.entity, - username: user.metadata.guid - }; - - return user.metadata ? { - entity, - metadata: user.metadata - } : { - entity + idAttribute: getAPIResourceGuid, + processStrategy: (user: APIResource) => { + if (user.entity.username) { + return user; + } + const entity = { + ...user.entity, + username: user.metadata.guid }; - } -}); + + return user.metadata ? { + entity, + metadata: user.metadata + } : { + entity + }; + } + }); entityCache[cfUserEntityType] = CFUserSchema; diff --git a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts index ec7741c151..5e5ccbe303 100644 --- a/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts +++ b/src/frontend/packages/cloud-foundry/src/cf-entity-schema-types.ts @@ -33,10 +33,9 @@ export class CFEntitySchema extends EntitySchema { definition?: Schema, options?: schema.EntityOptions, relationKey?: string, - excludeFromRecursiveDelete?: string[], - schemaKey?: string + excludeFromRecursiveDelete?: string[] ) { - super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, schemaKey, excludeFromRecursiveDelete); + super(entityKey, CF_ENDPOINT_TYPE, definition, options, relationKey, null, excludeFromRecursiveDelete); } } @@ -125,8 +124,7 @@ export class CFApplicationEntitySchema extends CFEntitySchema { export class CFSpaceEntitySchema extends CFEntitySchema { constructor( definition?: Schema, - relationKey?: string, - schemaKey?: string + relationKey?: string ) { super(spaceEntityType, definition, { idAttribute: getAPIResourceGuid }, relationKey, [ domainEntityType, @@ -135,6 +133,6 @@ export class CFSpaceEntitySchema extends CFEntitySchema { servicePlanEntityType, // App Related stackEntityType - ], schemaKey); + ]); } } diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index c854e5f873..ab5e375edd 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, - spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; +import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,7 +47,6 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; -import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -60,13 +59,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -187,8 +186,6 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); - getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); - getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -201,9 +198,13 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(() => this.appSpace$), - map(space => space.entity.organization), - filter(org => !!org) + switchMap(app => this.appSpace$.pipe( + map(space => space.entity.organization_guid), + switchMap(orgGuid => { + return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); + }), + filter(org => !!org) + )) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts index 6769675f44..4c88d174d8 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/cf-spaces-user-service-instances-data-source.ts @@ -1,18 +1,19 @@ import { Store } from '@ngrx/store'; +import { GetAllUserProvidedServices } from '../../../../../../../cloud-foundry/src/actions/user-provided-service.actions'; import { CFAppState } from '../../../../../../../cloud-foundry/src/cf-app-state'; import { applicationEntityType, organizationEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, userProvidedServiceInstanceEntityType, } from '../../../../../../../cloud-foundry/src/cf-entity-types'; import { createEntityRelationKey, createEntityRelationPaginationKey, } from '../../../../../../../cloud-foundry/src/entity-relations/entity-relations.types'; -import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { ListDataSource, } from '../../../../../../../core/src/shared/components/list/data-sources-controllers/list-data-source'; @@ -21,12 +22,11 @@ import { IListConfig, } from '../../../../../../../core/src/shared/components/list/list.component.types'; import { APIResource } from '../../../../../../../store/src/types/api.types'; -import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; import { cfEntityFactory } from '../../../../../cf-entity-factory'; -import { - UserProvidedServiceActionBuilder, -} from '../../../../../entity-action-builders/user-provided-service.action-builders'; import { getRowMetadata } from '../../../../../features/cloud-foundry/cf.helpers'; +import { entityCatalogue } from '../../../../../../../core/src/core/entity-catalogue/entity-catalogue.service'; +import { CF_ENDPOINT_TYPE } from '../../../../../../cf-types'; +import { UserProvidedServiceActionBuilder } from '../../../../../entity-action-builders/user-provided-service.action-builders'; export class CfSpacesUserServiceInstancesDataSource extends ListDataSource { constructor(cfGuid: string, spaceGuid: string, store: Store, listConfig?: IListConfig) { @@ -38,7 +38,7 @@ export class CfSpacesUserServiceInstancesDataSource extends ListDataSource Date: Fri, 29 Nov 2019 18:16:15 +0000 Subject: [PATCH 21/31] Fix issue where clicking on org and then app fav failed - Issue 1 (slightly unrelated) - app service getspace action used space schema without an org - Issue 2 - app entity validation found it was missing the space - validation process fetched space with custom action - custom action did not contain schema key linked to space schema with org - this lead to org being not stored correctly in store (contained in space rather than seperatly) - Simple fix (see 0a16203284c for harder) - When normalizing prioritise the action's schema over attempting to fetch via entity catalogue + schemaKey (avoids A LOT of plumbing) --- .../entity-relations/entity-relation-tree.ts | 4 +- .../applications/application.service.ts | 29 +++++---- .../entity-catalogue-entity.ts | 13 +--- .../map-multi-endpoint.pipes.ts | 62 +++++++++++-------- .../normalize-entity-request-response.pipe.ts | 14 ----- 5 files changed, 55 insertions(+), 67 deletions(-) delete mode 100644 src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts diff --git a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts index a60a65894e..d758718c6c 100644 --- a/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts +++ b/src/frontend/packages/cloud-foundry/src/entity-relations/entity-relation-tree.ts @@ -1,5 +1,5 @@ -import { EntitySchema } from '../../../store/src/helpers/entity-schema'; import { entityCatalogue } from '../../../core/src/core/entity-catalogue/entity-catalogue.service'; +import { EntitySchema } from '../../../store/src/helpers/entity-schema'; /** * A structure which represents the tree like layout of entity dependencies. For example organization --> space --> routes @@ -18,7 +18,7 @@ export class EntityTreeRelation { /** * Creates an instance of EntityTreeRelation. - * @param [isArray=false] is this a collection of entities (should be paginationed) or not + * @param [isArray=false] is this a collection of entities (should be paginated) or not * @param paramName parameter name of the entity within the schema. For example `space` may be `spaces` (entity.spaces) * @param [path=''] location of the entity within the parent. For example `space` entity maybe be `entity.spaces` */ diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts index ab5e375edd..c854e5f873 100644 --- a/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/applications/application.service.ts @@ -22,9 +22,9 @@ import { routeEntityType, serviceBindingEntityType, spaceEntityType, + spaceWithOrgEntityType, stackEntityType, } from '../../../../cloud-foundry/src/cf-entity-types'; -import { selectCfEntity } from '../../../../cloud-foundry/src/store/selectors/api.selectors'; import { IApp, IAppSummary, IDomain, IOrganization, ISpace } from '../../../../core/src/core/cf-api.types'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; import { EntityService } from '../../../../core/src/core/entity-service'; @@ -47,6 +47,7 @@ import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors' import { endpointEntitiesSelector } from '../../../../store/src/selectors/endpoint.selectors'; import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState } from '../../../../store/src/types/pagination.types'; +import { cfEntityFactory } from '../../cf-entity-factory'; import { createEntityRelationKey } from '../../entity-relations/entity-relations.types'; import { AppStat } from '../../store/types/app-metadata.types'; import { @@ -59,13 +60,13 @@ export function createGetApplicationAction(guid: string, endpointGuid: string) { return new GetApplication( guid, endpointGuid, [ - createEntityRelationKey(applicationEntityType, routeEntityType), - createEntityRelationKey(applicationEntityType, spaceEntityType), - createEntityRelationKey(applicationEntityType, stackEntityType), - createEntityRelationKey(applicationEntityType, serviceBindingEntityType), - createEntityRelationKey(routeEntityType, domainEntityType), - createEntityRelationKey(spaceEntityType, organizationEntityType), - ] + createEntityRelationKey(applicationEntityType, routeEntityType), + createEntityRelationKey(applicationEntityType, spaceEntityType), + createEntityRelationKey(applicationEntityType, stackEntityType), + createEntityRelationKey(applicationEntityType, serviceBindingEntityType), + createEntityRelationKey(routeEntityType, domainEntityType), + createEntityRelationKey(spaceEntityType, organizationEntityType), + ] ); } @@ -186,6 +187,8 @@ export class ApplicationService { app.cfGuid, { includeRelations: [createEntityRelationKey(spaceEntityType, organizationEntityType)], populateMissing: true } ); + getSpaceAction.entity = cfEntityFactory(spaceWithOrgEntityType); + getSpaceAction.schemaKey = spaceWithOrgEntityType; return this.entityServiceFactory.create>( app.space_guid, getSpaceAction @@ -198,13 +201,9 @@ export class ApplicationService { ); this.appOrg$ = moreWaiting$.pipe( first(), - switchMap(app => this.appSpace$.pipe( - map(space => space.entity.organization_guid), - switchMap(orgGuid => { - return this.store.select(selectCfEntity(organizationEntityType, orgGuid)); - }), - filter(org => !!org) - )) + switchMap(() => this.appSpace$), + map(space => space.entity.organization), + filter(org => !!org) ); this.isDeletingApp$ = this.appEntityService.isDeletingEntity$.pipe(publishReplay(1), refCount()); diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts index 11de55d636..64fa92b6c6 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts @@ -1,10 +1,8 @@ import { ActionReducer, Store } from '@ngrx/store'; -import { normalize } from 'normalizr'; import { AppState, IRequestEntityTypeState } from '../../../../store/src/app-state'; import { EntityPipelineEntity, stratosEndpointGuidKey } from '../../../../store/src/entity-request-pipeline/pipeline.types'; import { EntitySchema } from '../../../../store/src/helpers/entity-schema'; -import { NormalizedResponse } from '../../../../store/src/types/api.types'; import { EndpointModel } from '../../../../store/src/types/endpoint.types'; import { APISuccessOrFailedAction, EntityRequestAction } from '../../../../store/src/types/request.types'; import { IEndpointFavMetadata } from '../../../../store/src/types/user-favorites.types'; @@ -89,8 +87,8 @@ export class StratosBaseCatalogueEntity< } return newSchema; }, { - default: entitySchemas.default - }); + default: entitySchemas.default + }); } private getEndpointType(definition: IStratosBaseEntityDefinition) { @@ -217,13 +215,6 @@ export class StratosBaseCatalogueEntity< } - public getNormalizedEntityData(entities: Y | Y[], schemaKey?: string): NormalizedResponse { - const schema = this.getSchema(schemaKey); - if (Array.isArray(entities)) { - return normalize(entities, [schema]); - } - return normalize(entities, schema); - } } export class StratosCatalogueEntity< diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts index 00a2a1f541..d08d04483d 100644 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts +++ b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/map-multi-endpoint.pipes.ts @@ -1,4 +1,5 @@ import { Action } from '@ngrx/store'; +import { normalize } from 'normalizr'; import { StratosBaseCatalogueEntity } from '../../../../core/src/core/entity-catalogue/entity-catalogue-entity'; import { entityCatalogue } from '../../../../core/src/core/entity-catalogue/entity-catalogue.service'; @@ -10,9 +11,8 @@ import { PipelineResult } from '../entity-request-pipeline.types'; import { getSuccessMapper } from '../pipeline-helpers'; import { endpointErrorsHandlerFactory } from './endpoint-errors.handler'; import { patchActionWithForcedConfig } from './forced-action-type.helpers'; -import { HandledMultiEndpointResponse, JetstreamError } from './handle-multi-endpoints.pipe'; +import { HandledMultiEndpointResponse, JetstreamError, MultiEndpointResponse } from './handle-multi-endpoints.pipe'; import { multiEndpointResponseMergePipe } from './merge-multi-endpoint-data.pipe'; -import { normalizeEntityPipeFactory } from './normalize-entity-request-response.pipe'; const baseErrorHandler = () => 'Api Request Failed'; @@ -56,6 +56,19 @@ function getEntities( }, {}); } +// TODO: Type the output of this pipe. #3976 +function getNormalizedEntityData( + entities: any[], + action: EntityRequestAction, + catalogueEntity: StratosBaseCatalogueEntity) { + // Can patchActionWithForcedConfig be done outside of the pipe? + // This pipe shouldn't have to worry about the multi entity lists. + const patchedAction = patchActionWithForcedConfig(action); + const schema = patchedAction.entity || catalogueEntity.getSchema(patchedAction.schemaKey); + const arraySafeSchema = Array.isArray(schema) ? schema[0] : schema; + return normalize(entities, Array.isArray(entities) ? [arraySafeSchema] : arraySafeSchema); +} + export function mapMultiEndpointResponses( action: EntityRequestAction, catalogueEntity: StratosBaseCatalogueEntity, @@ -63,12 +76,6 @@ export function mapMultiEndpointResponses( multiEndpointResponses: HandledMultiEndpointResponse, actionDispatcher: (actionToDispatch: Action) => void ): PipelineResult { - const normalizeEntityPipe = normalizeEntityPipeFactory( - catalogueEntity, - // Can this be done outside of the pipe? - // This pipe shouldn't have to worry about the multi entity lists. - patchActionWithForcedConfig(action).schemaKey - ); const endpointErrorHandler = endpointErrorsHandlerFactory(actionDispatcher); endpointErrorHandler( action, @@ -84,23 +91,28 @@ export function mapMultiEndpointResponses( errorMessage }; } else { - const responses = multiEndpointResponses.successes.map(normalizeEntityPipe); - const mapped = responses.map(endpointResponse => { - const entities = getEntities(endpointResponse, action); - const parentEntities = entities[catalogueEntity.entityKey]; - return { - response: { - entities, - // If we changed the guid of the entities then make sure this is reflected in the result array. - result: parentEntities ? Object.keys(parentEntities) : endpointResponse.normalizedEntities.result, - }, - totalPages: endpointResponse.totalPages, - totalResults: endpointResponse.totalResults, - success: null - }; - }); - // NormalizedResponse - const response = multiEndpointResponseMergePipe(mapped); + const responses = multiEndpointResponses.successes + .map((responseData: MultiEndpointResponse) => ({ + normalizedEntities: getNormalizedEntityData(responseData.entities, action, catalogueEntity), + endpointGuid: responseData.endpointGuid, + totalResults: responseData.totalResults, + totalPages: responseData.totalPages + })) + .map(endpointResponse => { + const entities = getEntities(endpointResponse, action); + const parentEntities = entities[catalogueEntity.entityKey]; + return { + response: { + entities, + // If we changed the guid of the entities then make sure this is reflected in the result array. + result: parentEntities ? Object.keys(parentEntities) : endpointResponse.normalizedEntities.result, + }, + totalPages: endpointResponse.totalPages, + totalResults: endpointResponse.totalResults, + success: null + }; + }); + const response = multiEndpointResponseMergePipe(responses); return { ...response, success: true, diff --git a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts b/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts deleted file mode 100644 index ee538455b8..0000000000 --- a/src/frontend/packages/store/src/entity-request-pipeline/entity-request-base-handlers/normalize-entity-request-response.pipe.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StratosBaseCatalogueEntity } from '../../../../core/src/core/entity-catalogue/entity-catalogue-entity'; -import { MultiEndpointResponse } from './handle-multi-endpoints.pipe'; - -// TODO: Type the output of this pipe. #3976 -export const normalizeEntityPipeFactory = (catalogueEntity: StratosBaseCatalogueEntity, schemaKey?: string) => { - return (responseData: MultiEndpointResponse) => { - return { - normalizedEntities: catalogueEntity.getNormalizedEntityData(responseData.entities, schemaKey), - endpointGuid: responseData.endpointGuid, - totalResults: responseData.totalResults, - totalPages: responseData.totalPages - }; - }; -}; From fda6f8526daa8e0384b77a20d9a9e2e704346340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Mon, 2 Dec 2019 14:18:03 -0300 Subject: [PATCH 22/31] sidepanel preview: cleanup and minor improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../services/cloud-foundry-space.service.ts | 4 + ...cloud-foundry-space-summary.component.html | 2 +- .../application-preview.component.html | 126 +++++++++--------- .../application-preview.component.ts | 27 ++-- .../organization-preview.component.html | 2 +- .../packages/core/src/base-entity-schemas.ts | 3 +- .../entity-catalogue-entity.ts | 2 +- .../entity-catalogue.types.ts | 1 + .../dashboard-base.component.scss | 5 + .../connect-endpoint-dialog.component.ts | 6 +- .../create-endpoint-connect.component.ts | 8 +- .../favorites-meta-card.component.ts | 4 +- .../markdown-preview.component.ts | 1 - .../shared/services/panel-preview.service.ts | 17 ++- 14 files changed, 121 insertions(+), 87 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts index 371b9874d9..a3a230d5c7 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/services/cloud-foundry-space.service.ts @@ -103,6 +103,10 @@ export class CloudFoundrySpaceService { this.usersCount$ = this.cfUserService.fetchTotalUsers(this.cfGuid, this.orgGuid, this.spaceGuid); } + public fetchApps() { + this.cfEndpointService.fetchApps(); + } + private initialiseSpaceObservables() { this.space$ = this.cfUserService.isConnectedUserAdmin(this.cfGuid).pipe( switchMap(isAdmin => { diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html index 1f03e7bdb5..3ec288a9fa 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.html @@ -75,7 +75,7 @@ + [loading$]="cfSpaceService.loadingApps$" (refresh)="cfSpaceService.fetchApps()"> diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html index 9900b6a612..f47a8f18fe 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -1,68 +1,70 @@ - - -
- - - Summary - - - - - - {{ appSvc.cf?.name}} - - - {{ appSvc.cf?.name}} - - - - - - - -
-
-
+ + + +
+ + + Summary + + + + + + {{ appSvc.cf?.name}} + + + {{ appSvc.cf?.name}} + + + + + + + +
+
+
- - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + +
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts index 5aa2772e67..649e38af6f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.ts @@ -1,30 +1,27 @@ import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; -import { combineLatest, map } from 'rxjs/operators'; +import { combineLatest, distinct, map } from 'rxjs/operators'; +import { IAppSummary } from '../../../../../core/src/core/cf-api.types'; import { getFullEndpointApiUrl } from '../../../../../core/src/features/endpoints/endpoint-helpers'; import { APP_GUID, CF_GUID } from '../../../../../core/src/shared/entity.tokens'; import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; -import { ApplicationService } from '../../../features/applications/application.service'; -import { getGuids } from '../../../features/applications/application/application-base.component'; +import { EntityInfo } from '../../../../../store/src/types/api.types'; +import { ApplicationData, ApplicationService } from '../../../features/applications/application.service'; @Component({ selector: 'app-application-preview-component', templateUrl: './application-preview.component.html', styleUrls: ['./application-preview.component.scss'], - // useless, necessary to reuse ApplicationService providers: [ ApplicationService, { provide: CF_GUID, - useFactory: getGuids('cf'), - deps: [ActivatedRoute] + useValue: '', }, { provide: APP_GUID, - useFactory: getGuids(), - deps: [ActivatedRoute] + useValue: '', }, ] }) @@ -33,6 +30,7 @@ export class ApplicationPreviewComponent implements PreviewableComponent { title = null; cfEndpointService: object; sshStatus$: Observable; + detailsLoading$: Observable; getFullEndpointApiUrl = getFullEndpointApiUrl; @@ -54,8 +52,15 @@ export class ApplicationPreviewComponent implements PreviewableComponent { }) ); - // this.detailsLoading$ = combineLatest([ - // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now + this.detailsLoading$ = this.applicationService.application$.pipe( + combineLatest( + this.applicationService.appSummary$ + ), + map(([app, appSummary]: [ApplicationData, EntityInfo]) => { + return app.fetching || appSummary.entityRequestInfo.fetching; + }), distinct()); + + // // Wait for the apps to have been fetched, this will determine if multiple small cards are shown or now // this.cfEndpointService.appsPagObs.fetchingEntities$.pipe( // filter(loading => !loading) // ), diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html index 94359a1d9b..6387b13274 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html @@ -94,7 +94,7 @@ + [loading$]="cfOrgService.loadingApps$" (refresh)="cfOrgService.fetchApps()"> diff --git a/src/frontend/packages/core/src/base-entity-schemas.ts b/src/frontend/packages/core/src/base-entity-schemas.ts index 6a1f2c9ee8..4242afd616 100644 --- a/src/frontend/packages/core/src/base-entity-schemas.ts +++ b/src/frontend/packages/core/src/base-entity-schemas.ts @@ -1,13 +1,14 @@ import { endpointSchemaKey, entityFactory, + systemInfoSchemaKey, userFavouritesSchemaKey, userProfileSchemaKey, - systemInfoSchemaKey, } from '../../store/src/helpers/entity-factory'; import { EntitySchema } from '../../store/src/helpers/entity-schema'; export const STRATOS_ENDPOINT_TYPE = 'stratos'; +export const ENDPOINT_TYPE = 'endpoint'; class StratosEntitySchema extends EntitySchema { constructor(entityType: string) { diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts index 64fa92b6c6..4ca21ea92d 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue-entity.ts @@ -255,7 +255,7 @@ export class StratosCatalogueEndpointEntity extends StratosBaseCatalogueEntity string, - // TODO: attach to PreviewableComponent + // TODO find a way to attach this to PreviewableComponent getPreviewableComponent?: () => object ) { const fullEntity = { diff --git a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts index 241c7d5643..6e948950f9 100644 --- a/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts +++ b/src/frontend/packages/core/src/core/entity-catalogue/entity-catalogue.types.ts @@ -149,6 +149,7 @@ export interface IStratosEntityBuilder { getStatusObservable?(entity: Y): Observable; // TODO This should be used in the entities schema. getGuid(entityMetadata: T): string; + // TODO find a way to attach this to PreviewableComponent getPreviewableComponent?(): object; getLink?(entityMetadata: T): string; getLines?(): EntityRowBuilder[]; diff --git a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss index db2916d0c7..eebacc5611 100644 --- a/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss +++ b/src/frontend/packages/core/src/features/dashboard/dashboard-base/dashboard-base.component.scss @@ -54,7 +54,12 @@ $app-sub-header-height: 48px; padding: 0; } &__side-help { + max-width: 600px; min-width: 600px; + + @include breakpoint(mobileonly) { + min-width: auto; + } } } diff --git a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts index b4c554eba4..788d2bc4ae 100644 --- a/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/connect-endpoint-dialog/connect-endpoint-dialog.component.ts @@ -6,6 +6,8 @@ import { Subscription } from 'rxjs'; import { ShowSnackBar } from '../../../../../store/src/actions/snackBar.actions'; import { EndpointOnlyAppState } from '../../../../../store/src/app-state'; import { EndpointsService } from '../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../shared/components/markdown-preview/markdown-preview.component'; +import { PanelPreviewService } from '../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../connect.service'; @@ -27,6 +29,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { @Inject(MAT_DIALOG_DATA) public data: ConnectEndpointConfig, private store: Store, endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { this.connectService = new ConnectEndpointService(store, endpointsService, data); @@ -37,8 +40,7 @@ export class ConnectEndpointDialogComponent implements OnDestroy { } showHelp() { - // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); - // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } ngOnDestroy(): void { diff --git a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts index b2282192bb..ff128ef73c 100644 --- a/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts +++ b/src/frontend/packages/core/src/features/endpoints/create-endpoint/create-endpoint-connect/create-endpoint-connect.component.ts @@ -5,7 +5,9 @@ import { map } from 'rxjs/operators'; import { EndpointOnlyAppState } from '../../../../../../store/src/app-state'; import { EndpointsService } from '../../../../core/endpoints.service'; +import { MarkdownPreviewComponent } from '../../../../shared/components/markdown-preview/markdown-preview.component'; import { IStepperStep, StepOnNextResult } from '../../../../shared/components/stepper/step/step.component'; +import { PanelPreviewService } from '../../../../shared/services/panel-preview.service'; import { ConnectEndpointConfig, ConnectEndpointService } from '../../connect.service'; @@ -25,13 +27,13 @@ export class CreateEndpointConnectComponent implements OnDestroy, IStepperStep { constructor( private store: Store, - private endpointsService: EndpointsService + private endpointsService: EndpointsService, + private panelPreviewService: PanelPreviewService, ) { } showHelp() { - // this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); - // this.store.dispatch(new ShowSideHelp(MarkdownPreviewComponent, { setDocumentUrl: this.helpDocumentUrl })); + this.panelPreviewService.show(MarkdownPreviewComponent, { documentUrl: this.helpDocumentUrl }); } onEnter = (data: ConnectEndpointConfig) => { diff --git a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts index 29c8e373ef..5a91f03f22 100644 --- a/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts +++ b/src/frontend/packages/core/src/shared/components/favorites-meta-card/favorites-meta-card.component.ts @@ -12,7 +12,7 @@ import { endpointsEntityRequestDataSelector, } from '../../../../../store/src/selectors/endpoint.selectors'; import { IFavoriteMetadata, UserFavorite } from '../../../../../store/src/types/user-favorites.types'; -import { userFavoritesEntitySchema } from '../../../base-entity-schemas'; +import { ENDPOINT_TYPE, userFavoritesEntitySchema } from '../../../base-entity-schemas'; import { entityCatalogue } from '../../../core/entity-catalogue/entity-catalogue.service'; import { IFavoriteEntity } from '../../../core/user-favorite-manager'; import { PanelPreviewService } from '../../services/panel-preview.service'; @@ -113,7 +113,7 @@ export class FavoritesMetaCardComponent { const previewComponent = catalogueEntity.builders.entityBuilder.getPreviewableComponent(); // TODO: use 'endpoint' as constant - if (this.favorite.entityType === 'endpoint') { + if (this.favorite.entityType === ENDPOINT_TYPE) { const entity$ = this.store.select(endpointsEntityRequestDataSelector(this.favorite.endpointId)); this.panelPreviewService.show(previewComponent, { title: this.favorite.metadata.name, diff --git a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts index dab1792c71..9dba74b1d5 100644 --- a/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts +++ b/src/frontend/packages/core/src/shared/components/markdown-preview/markdown-preview.component.ts @@ -35,7 +35,6 @@ export class MarkdownPreviewComponent implements PreviewableComponent { ) { } setProps(props: { [key: string]: any }) { - console.log('props', props); this.setDocumentUrl = props.documentUrl; } diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts index dda1d3e374..04b42e4010 100644 --- a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -1,6 +1,7 @@ import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef } from '@angular/core'; +import { Router } from '@angular/router'; import { asapScheduler, BehaviorSubject, Observable, Subject } from 'rxjs'; -import { observeOn, publishReplay, refCount } from 'rxjs/operators'; +import { filter, observeOn, publishReplay, refCount, tap } from 'rxjs/operators'; @Injectable() export class PanelPreviewService { @@ -9,9 +10,14 @@ export class PanelPreviewService { private container: ViewContainerRef; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private router: Router, + ) { this.openedSubject = new BehaviorSubject(false); this.opened$ = this.observeSubject(this.openedSubject); + + this.setupRouterListener(); } public setContainer(container: ViewContainerRef) { @@ -57,6 +63,13 @@ export class PanelPreviewService { this.openedSubject.next(false); } + private setupRouterListener() { + this.router.events.pipe( + filter(() => !!this.container), + tap((e) => this.hide())) + .subscribe(); + } + private observeSubject(subject: Subject) { return subject.asObservable().pipe( publishReplay(1), From 06efa505801623e45444b2473aec9ca19e173414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 07:48:17 -0300 Subject: [PATCH 23/31] sidepanel preview: moved service from shared to app module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- src/frontend/packages/core/src/app.module.ts | 2 ++ src/frontend/packages/core/src/shared/shared.module.ts | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/packages/core/src/app.module.ts b/src/frontend/packages/core/src/app.module.ts index 7b110ef2e9..1ba2b4ac5e 100644 --- a/src/frontend/packages/core/src/app.module.ts +++ b/src/frontend/packages/core/src/app.module.ts @@ -46,6 +46,7 @@ import { LoggedInService } from './logged-in.service'; import { CustomReuseStrategy } from './route-reuse-stragegy'; import { FavoritesConfigMapper } from './shared/components/favorites-meta-card/favorite-config-mapper'; import { endpointEventKey, GlobalEventData, GlobalEventService } from './shared/global-events.service'; +import { PanelPreviewService } from './shared/services/panel-preview.service'; import { SharedModule } from './shared/shared.module'; // Create action for router navigation. See @@ -110,6 +111,7 @@ export class CustomRouterStateSerializer LoggedInService, ExtensionService, DynamicExtensionRoutes, + PanelPreviewService, { provide: GITHUB_API_URL, useFactory: getGitHubAPIURL }, { provide: RouterStateSerializer, useClass: CustomRouterStateSerializer }, // Create action for router navigation { provide: RouteReuseStrategy, useClass: CustomReuseStrategy } diff --git a/src/frontend/packages/core/src/shared/shared.module.ts b/src/frontend/packages/core/src/shared/shared.module.ts index a1d52f874c..7f438d72ae 100644 --- a/src/frontend/packages/core/src/shared/shared.module.ts +++ b/src/frontend/packages/core/src/shared/shared.module.ts @@ -116,7 +116,6 @@ import { UsageBytesPipe } from './pipes/usage-bytes.pipe'; import { ValuesPipe } from './pipes/values.pipe'; import { CloudFoundryUserProvidedServicesService } from './services/cloud-foundry-user-provided-services.service'; import { MetricsRangeSelectorService } from './services/metrics-range-selector.service'; -import { PanelPreviewService } from './services/panel-preview.service'; import { UserPermissionDirective } from './user-permission.directive'; /* tslint:disable:max-line-length */ @@ -321,7 +320,6 @@ import { UserPermissionDirective } from './user-permission.directive'; ApplicationStateService, EndpointListHelper, EndpointsListConfigService, - PanelPreviewService, // CfUserService, ConfirmationDialogService, EntityMonitorFactory, From 531ba60bde4e89b1673c1fb6141480db872e0280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 07:49:28 -0300 Subject: [PATCH 24/31] sidepanel preview: componentFactoryResolver as optional on render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../src/shared/services/panel-preview.service.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts index 04b42e4010..dd31ddcbc2 100644 --- a/src/frontend/packages/core/src/shared/services/panel-preview.service.ts +++ b/src/frontend/packages/core/src/shared/services/panel-preview.service.ts @@ -28,12 +28,12 @@ export class PanelPreviewService { this.container = container; } - public show(component: object, props?: { [key: string]: any }) { + public show(component: object, props?: { [key: string]: any }, componentFactoryResolver?: ComponentFactoryResolver) { if (!this.container) { throw new Error('PanelPreviewService: container must be set'); } - this.render(component, props); + this.render(component, props, componentFactoryResolver); this.openedSubject.next(true); } @@ -45,12 +45,16 @@ export class PanelPreviewService { this.openedSubject.next(false); } - render(component: object, props: { [key: string]: any }) { + render( + component: object, + props: { [key: string]: any }, + componentFactoryResolver: ComponentFactoryResolver = this.componentFactoryResolver + ) { if (this.container.length) { this.container.remove(0); } - const factory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(component as any); + const factory: ComponentFactory = componentFactoryResolver.resolveComponentFactory(component as any); const componentRef: ComponentRef = this.container.createComponent(factory); if (props) { From 5840c8488ac92155b8be956bc8dfa75ceeff37b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 07:49:52 -0300 Subject: [PATCH 25/31] space sidepanel preview: removed delay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../shared/components/space-preview/space-preview.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts index 727611d5c9..2829cc866e 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/space-preview/space-preview.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { combineLatest, Observable } from 'rxjs'; -import { delay, filter, map, startWith } from 'rxjs/operators'; +import { filter, map, startWith } from 'rxjs/operators'; import { PreviewableComponent } from '../../../../../core/src/shared/previewable-component'; import { CloudFoundryEndpointService } from '../../../features/cloud-foundry/services/cloud-foundry-endpoint.service'; @@ -40,7 +40,6 @@ export class SpacePreviewComponent implements PreviewableComponent { this.cfSpaceService.userProvidedServiceInstancesCount$ ]).pipe( map(() => false), - delay(50000), startWith(true) ); } From abd163dc2ec4eb34a2fafb34a22403f00500959e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Fri, 13 Dec 2019 09:11:03 -0300 Subject: [PATCH 26/31] store test helper: added object missing property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- src/frontend/packages/core/test-framework/store-test-helper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/core/test-framework/store-test-helper.ts b/src/frontend/packages/core/test-framework/store-test-helper.ts index 3a6277b913..83344dbdab 100644 --- a/src/frontend/packages/core/test-framework/store-test-helper.ts +++ b/src/frontend/packages/core/test-framework/store-test-helper.ts @@ -165,7 +165,8 @@ function getDefaultInitialTestStratosStoreState() { isMobileNavOpen: false, sideNavPinned: false, pollingEnabled: true, - themeKey: null + themeKey: null, + headerEventMinimized: true, }, actionHistory: [], lists: {}, From 66a2e1651aceb6796c3fccf77e869d171f03e096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Mon, 16 Dec 2019 08:42:55 -0300 Subject: [PATCH 27/31] fixed bad rebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../packages/cloud-foundry/src/actions/relation.actions.ts | 2 ++ src/frontend/packages/core/sass/_all-theme.scss | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts index 92d06c9320..985214b8dd 100644 --- a/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts +++ b/src/frontend/packages/cloud-foundry/src/actions/relation.actions.ts @@ -4,6 +4,8 @@ import { EntityCatalogueEntityConfig } from '../../../core/src/core/entity-catal import { PaginatedAction } from '../../../store/src/types/pagination.types'; import { RequestActionEntity, RequestEntityLocation } from '../../../store/src/types/request.types'; import { EntityTreeRelation } from '../entity-relations/entity-relation-tree'; +import { EntityInlineChildAction, EntityInlineParentAction } from '../entity-relations/entity-relations.types'; +import { CFStartAction } from './cf-action.types'; const relationActionId = 'FetchRelationAction'; diff --git a/src/frontend/packages/core/sass/_all-theme.scss b/src/frontend/packages/core/sass/_all-theme.scss index 3526d28675..b4e53750a8 100644 --- a/src/frontend/packages/core/sass/_all-theme.scss +++ b/src/frontend/packages/core/sass/_all-theme.scss @@ -44,7 +44,6 @@ @import './components/mat-snack-bar.theme'; @import './components/ngx-charts-gauge.theme'; @import '../src/shared/components/sidepanel-preview/sidepanel-preview.component.theme'; -@import './components/mat-tabs.theme'; @import './components/text-status.theme'; @import './components/hyperlinks.theme'; @import './mat-themes'; From 1139cbeb663709a755ad9a3b5428f464a660bec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Mon, 16 Dec 2019 09:13:16 -0300 Subject: [PATCH 28/31] fixed build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../organization-preview/organization-preview.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html index 6387b13274..4af58592b4 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/organization-preview/organization-preview.component.html @@ -80,12 +80,12 @@ value="{{ cfOrgService.serviceInstancesCount$ | async }}" limit="{{ (cfOrgService.quotaDefinition$ | async)?.total_services }}"> - + - + From 4c38e4623ad440b0097f021fc9d7d0bf56c47533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Avelino?= Date: Mon, 16 Dec 2019 12:47:57 -0300 Subject: [PATCH 29/31] sidepanel preview: fixed tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vítor Avelino --- .../edit-organization-step.component.spec.ts | 3 +- .../edit-organization.component.spec.ts | 9 +++-- .../edit-space-step.component.spec.ts | 2 +- .../edit-space/edit-space.component.spec.ts | 7 ++-- ...cloud-foundry-space-base.component.spec.ts | 2 +- ...ud-foundry-space-summary.component.spec.ts | 10 +++--- .../invite-users-create.component.spec.ts | 15 +++----- .../invite-users.component.spec.ts | 9 ++--- .../application-preview.component.html | 14 ++++---- .../application-preview.component.spec.ts | 26 +++++++------- .../card-cf-recent-apps.component.spec.ts | 6 ++-- .../cf-endpoint-preview.component.spec.ts | 26 +++++++------- .../organization-preview.component.spec.ts | 32 ++++++++++------- .../space-preview.component.spec.ts | 34 ++++++++++++------- .../cloud-foundry-endpoint-service.helper.ts | 14 ++++++++ ...api-endpoint-type-select-page.component.ts | 12 +++---- .../dashboard-base.component.spec.ts | 4 ++- .../create-endpoint-connect.component.spec.ts | 6 +++- .../favorites-meta-card.component.spec.ts | 2 ++ .../markdown-preview.component.spec.ts | 3 +- .../core/test-framework/core-test.helper.ts | 2 +- 21 files changed, 137 insertions(+), 101 deletions(-) diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts index fe3c60c481..9c64c4f2d8 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization-step/edit-organization-step.component.spec.ts @@ -4,7 +4,6 @@ import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../../cf-page.types'; import { EditOrganizationStepComponent } from './edit-organization-step.component'; describe('EditOrganizationStepComponent', () => { @@ -15,7 +14,7 @@ describe('EditOrganizationStepComponent', () => { TestBed.configureTestingModule({ declarations: [EditOrganizationStepComponent], imports: generateCfBaseTestModules(), - providers: [ActiveRouteCfOrgSpace, generateTestCfEndpointServiceProvider()] + providers: generateTestCfEndpointServiceProvider(), }) .compileComponents(); })); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts index ecead39a28..4582bdeab3 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-organization/edit-organization.component.spec.ts @@ -5,7 +5,7 @@ import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../cf-page.types'; +import { CloudFoundryOrganizationService } from '../services/cloud-foundry-organization.service'; import { EditOrganizationStepComponent } from './edit-organization-step/edit-organization-step.component'; import { EditOrganizationComponent } from './edit-organization.component'; @@ -17,8 +17,11 @@ describe('EditOrganizationComponent', () => { TestBed.configureTestingModule({ declarations: [EditOrganizationComponent, EditOrganizationStepComponent], imports: generateCfBaseTestModules(), - providers: [ActiveRouteCfOrgSpace, generateTestCfEndpointServiceProvider(), TabNavService] - + providers: [ + ...generateTestCfEndpointServiceProvider(), + CloudFoundryOrganizationService, + TabNavService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts index 4dd6fbacb2..ac25e3cc86 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space-step/edit-space-step.component.spec.ts @@ -18,7 +18,7 @@ describe('EditSpaceStepComponent', () => { imports: generateCfBaseTestModules(), providers: [ { provide: CloudFoundrySpaceService, useClass: CloudFoundrySpaceServiceMock }, - generateTestCfEndpointServiceProvider(), + ...generateTestCfEndpointServiceProvider(), ] }) .compileComponents(); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts index a1c01fa14c..25d884f194 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/edit-space/edit-space.component.spec.ts @@ -5,7 +5,6 @@ import { generateCfBaseTestModules, generateTestCfEndpointServiceProvider, } from '../../../../test-framework/cloud-foundry-endpoint-service.helper'; -import { ActiveRouteCfOrgSpace } from '../cf-page.types'; import { CloudFoundryOrganizationService } from '../services/cloud-foundry-organization.service'; import { EditSpaceStepComponent } from './edit-space-step/edit-space-step.component'; import { EditSpaceComponent } from './edit-space.component'; @@ -18,7 +17,11 @@ describe('EditSpaceComponent', () => { TestBed.configureTestingModule({ declarations: [EditSpaceComponent, EditSpaceStepComponent], imports: generateCfBaseTestModules(), - providers: [ActiveRouteCfOrgSpace, generateTestCfEndpointServiceProvider(), TabNavService, CloudFoundryOrganizationService] + providers: [ + ...generateTestCfEndpointServiceProvider(), + TabNavService, + CloudFoundryOrganizationService + ] }) .compileComponents(); })); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts index 38dfaf8ce3..261c5c7d3d 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/cloud-foundry-space-base/cloud-foundry-space-base.component.spec.ts @@ -15,7 +15,7 @@ describe('CloudFoundrySpaceBaseComponent', () => { TestBed.configureTestingModule({ declarations: [CloudFoundrySpaceBaseComponent], imports: generateCfBaseTestModules(), - providers: [generateTestCfEndpointServiceProvider(), TabNavService] + providers: [...generateTestCfEndpointServiceProvider(), TabNavService] }) .compileComponents(); })); diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts index 37401db631..1faa90a3fe 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/tabs/cloud-foundry-organizations/cloud-foundry-organization-spaces/tabs/cloud-foundry-space-summary/cloud-foundry-space-summary.component.spec.ts @@ -1,12 +1,13 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../../../../../../core/tab-nav.service'; -import { - generateCfBaseTestModules, -} from '../../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CloudFoundrySpaceServiceMock, } from '../../../../../../../../../core/test-framework/cloud-foundry-space.service.mock'; +import { + generateCfBaseTestModules, + generateTestCfEndpointServiceProvider, +} from '../../../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CardCfRecentAppsComponent, } from '../../../../../../../shared/components/cards/card-cf-recent-apps/card-cf-recent-apps.component'; @@ -17,7 +18,6 @@ import { CardCfSpaceDetailsComponent, } from '../../../../../../../shared/components/cards/card-cf-space-details/card-cf-space-details.component'; import { CfUserService } from '../../../../../../../shared/data-services/cf-user.service'; -import { ActiveRouteCfOrgSpace } from '../../../../../cf-page.types'; import { CloudFoundryEndpointService } from '../../../../../services/cloud-foundry-endpoint.service'; import { CloudFoundryOrganizationService } from '../../../../../services/cloud-foundry-organization.service'; import { CloudFoundrySpaceService } from '../../../../../services/cloud-foundry-space.service'; @@ -32,10 +32,10 @@ describe('CloudFoundrySpaceSummaryComponent', () => { declarations: [CloudFoundrySpaceSummaryComponent, CardCfSpaceDetailsComponent, CardCfRecentAppsComponent, CompactAppCardComponent], imports: generateCfBaseTestModules(), providers: [ - ActiveRouteCfOrgSpace, CloudFoundryEndpointService, { provide: CloudFoundrySpaceService, useClass: CloudFoundrySpaceServiceMock }, CloudFoundryOrganizationService, + ...generateTestCfEndpointServiceProvider(), TabNavService, CfUserService ] diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts index 2cf5ba6863..2be0341fab 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users-create/invite-users-create.component.spec.ts @@ -1,11 +1,10 @@ -import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { generateCfBaseTestModules } from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { + generateCfBaseTestModules, + generateTestCfEndpointServiceProvider, +} from '../../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { CfUserService } from '../../../../../shared/data-services/cf-user.service'; -import { ActiveRouteCfOrgSpace } from '../../../cf-page.types'; -import { CloudFoundryEndpointService } from '../../../services/cloud-foundry-endpoint.service'; -import { UserInviteService } from '../../../user-invites/user-invite.service'; import { InviteUsersCreateComponent } from './invite-users-create.component'; describe('InviteUsersCreateComponent', () => { @@ -17,11 +16,7 @@ describe('InviteUsersCreateComponent', () => { declarations: [InviteUsersCreateComponent], imports: generateCfBaseTestModules(), providers: [ - ActiveRouteCfOrgSpace, - CloudFoundryEndpointService, - UserInviteService, - HttpClient, - HttpHandler, + generateTestCfEndpointServiceProvider(), CfUserService ] }) diff --git a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts index 1976d2f4ad..c1fb430e63 100644 --- a/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts +++ b/src/frontend/packages/cloud-foundry/src/features/cloud-foundry/users/invite-users/invite-users.component.spec.ts @@ -1,8 +1,10 @@ -import { HttpClient, HttpHandler } from '@angular/common/http'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TabNavService } from '../../../../../../core/tab-nav.service'; -import { generateCfBaseTestModules } from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; +import { + generateCfBaseTestModules, + generateTestCfEndpointServiceProvider, +} from '../../../../../test-framework/cloud-foundry-endpoint-service.helper'; import { InviteUsersCreateComponent } from './invite-users-create/invite-users-create.component'; import { InviteUsersComponent } from './invite-users.component'; @@ -18,8 +20,7 @@ describe('InviteUsersComponent', () => { ], imports: generateCfBaseTestModules(), providers: [ - HttpClient, - HttpHandler, + ...generateTestCfEndpointServiceProvider(), TabNavService ] }) diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html index f47a8f18fe..9dbb965c20 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/application-preview/application-preview.component.html @@ -15,24 +15,22 @@ {{ appSvc.cf?.name}} - - {{ appSvc.cf?.name}} - - - + + - + +