diff --git a/CHANGELOG.md b/CHANGELOG.md index 161b42c8ad..3822b7ef27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,71 @@ # Change Log +## 4.0.0 + +[Full Changelog](https://github.com/cloudfoundry/stratos/compare/3.2.1...4.0.0) + +This release contains a number of fixes and improvements: + +**Improvements:** + +- Extensions: Remove the need for symlinks and improve the build process [\#4472](https://github.com/cloudfoundry/stratos/issues/4472) +- Extensions: Allow Themes to be published and installed to/from npm [\#4471](https://github.com/cloudfoundry/stratos/issues/4471) +- Extensions: Move to extensions and themes to be packages [\#4470](https://github.com/cloudfoundry/stratos/issues/4470) +- Show service broker space scope information in service wall list [\#4458](https://github.com/cloudfoundry/stratos/issues/4458) +- Client Secret is shown in the clear in the UI [\#4445](https://github.com/cloudfoundry/stratos/issues/4445) +- Improve sizing of UI elements on desktop browsers [\#4419](https://github.com/cloudfoundry/stratos/issues/4419) +- Theming: Allow more control over link and side navigation colors [\#4406](https://github.com/cloudfoundry/stratos/issues/4406) +- Update to latest set of icons [\#4403](https://github.com/cloudfoundry/stratos/issues/4403) +- Theming: Allow more control over page header colors and style [\#4396](https://github.com/cloudfoundry/stratos/issues/4396) +- Helm Chart: Remove encryption volume [\#4351](https://github.com/cloudfoundry/stratos/issues/4351) +- Improve app summary responsiveness [\#4348](https://github.com/cloudfoundry/stratos/issues/4348) +- Improve UI for the case when we can't determine cf app deployment info [\#4347](https://github.com/cloudfoundry/stratos/issues/4347) +- Change recent activity icon to avoid confusion with the refresh button [\#4346](https://github.com/cloudfoundry/stratos/issues/4346) +- Helm Chart: Change default image pull policy to Always [\#4342](https://github.com/cloudfoundry/stratos/issues/4342) +- Permissions: Org Managers: Disable org role checkboxes in roles stepper if not admin/org manager [\#4332](https://github.com/cloudfoundry/stratos/issues/4332) +- Autoscaler: Add support for custom metrics [\#4298](https://github.com/cloudfoundry/stratos/issues/4298) +- Add support for copying endpoint address in list view [\#4238](https://github.com/cloudfoundry/stratos/issues/4238) +- Update to Angular 9 framework [\#4214](https://github.com/cloudfoundry/stratos/issues/4214) +- Update docker logo in deploy app stepper [\#4133](https://github.com/cloudfoundry/stratos/issues/4133) +- Helm Chart: Remove need for --recreate-pods when upgrading [\#4132](https://github.com/cloudfoundry/stratos/issues/4132) +- Make permissions model extension friendly [\#3789](https://github.com/cloudfoundry/stratos/issues/3789) +- User Favourites: Add icons to cards [\#3409](https://github.com/cloudfoundry/stratos/issues/3409) +- Improve log out experience [\#2587](https://github.com/cloudfoundry/stratos/issues/2587) + +**Fixes:** + +- Ensure `cf push` works from Windows [\#4465](https://github.com/cloudfoundry/stratos/issues/4465) +- Visiting marketplace tab breaks service list [\#4397](https://github.com/cloudfoundry/stratos/issues/4397) +- CF Application reports error after restage [\#4392](https://github.com/cloudfoundry/stratos/issues/4392) +- Helm Chart: Icon is missing [\#4370](https://github.com/cloudfoundry/stratos/issues/4370) +- Permissions: Users with no developer roles can click on create app button [\#4361](https://github.com/cloudfoundry/stratos/issues/4361) +- Edit endpoint not available in table view [\#4349](https://github.com/cloudfoundry/stratos/issues/4349) +- CF: Routes List: Filter by org breaks when user is an org auditor [\#4343](https://github.com/cloudfoundry/stratos/issues/4343) +- Permissions: Only space developers should be able to see add service instance buttons [\#4331](https://github.com/cloudfoundry/stratos/issues/4331) +- Permissions: Only Space Developers should be able to change count, terminate or ssh to instances [\#4330](https://github.com/cloudfoundry/stratos/issues/4330) +- App: Gitlab Tab: Fix console errors [\#4325](https://github.com/cloudfoundry/stratos/issues/4325) +- Permissions: Only space developers should be able to create/unbind/delete routes in app routes list [\#4324](https://github.com/cloudfoundry/stratos/issues/4324) +- Permissions: Only Space Developers should be able to create/edit/delete an Autoscaler policy [\#4323](https://github.com/cloudfoundry/stratos/issues/4323) +- Permissions: Only space developers should be able to see the app summary deployment card [\#4322](https://github.com/cloudfoundry/stratos/issues/4322) +- Duplicated documentation between deploy/kubernetes and Helm Chart README.md [\#4315](https://github.com/cloudfoundry/stratos/issues/4315) +- App Service Edit Binding: cancel of stepper results in leaked subscription [\#4295](https://github.com/cloudfoundry/stratos/issues/4295) +- Exceptions thrown when navigating back from marketplace [\#4287](https://github.com/cloudfoundry/stratos/issues/4287) +- Unbind services stepper fails to show bound services [\#4246](https://github.com/cloudfoundry/stratos/issues/4246) +- Progress icon appears too close to right-hand size of table [\#4234](https://github.com/cloudfoundry/stratos/issues/4234) +- Cancel/create in service instance stepper returns to incorrect locations [\#4052](https://github.com/cloudfoundry/stratos/issues/4052) +- Exception thrown in setup steppers [\#3897](https://github.com/cloudfoundry/stratos/issues/3897) +- Logout leaves the UI as is if the verify or logout call fails [\#2633](https://github.com/cloudfoundry/stratos/issues/2633) +- Cf Build Packs: file name should wrap to next line if too long [\#1803](https://github.com/cloudfoundry/stratos/issues/1803) + +**Breaking Changes:** + +- **Kubernetes: Upgrade only possible from version 3.0.0 or later** + + When deploying into Kubernetes using Helm and upgrading from an earlier version of Stratos using `helm upgrade`, upgrade is **only** supported from version 3.0.0 or later. If you are using an earlier version, first upgrade to version 3.x before then upgrading to the latest version. +- **Angular 9 requires extensions to be declared** + + Extension components must now be made known to the extensions system in the module that they are declared in, using `ExtensionService.declare`. Please check the documentation. This is required to ensure that the new Angular compiler for Ivy does not remove these components for being unreferenced in the application. + ## 3.2.1 [Full Changelog](https://github.com/SUSE/stratos/compare/3.2.0...3.2.1) diff --git a/package-lock.json b/package-lock.json index 8bbb6d91b2..1c002ad57b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "3.2.1", + "version": "4.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 32d6ab56b1..e2996467de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stratos", - "version": "3.2.1", + "version": "4.0.0", "description": "Stratos Console", "main": "index.js", "scripts": { diff --git a/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts b/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts index faca9700ab..206abb267a 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/cf-shared.module.ts @@ -136,6 +136,9 @@ import { import { TableCellServiceBindableComponent, } from './components/list/list-types/cf-services/table-cell-service-bindable/table-cell-service-bindable.component'; +import { + TableCellServiceBrokerComponent, +} from './components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component'; import { TableCellServiceCfBreadcrumbsComponent, } from './components/list/list-types/cf-services/table-cell-service-cf-breadcrumbs/table-cell-service-cf-breadcrumbs.component'; @@ -248,6 +251,7 @@ const cfListTableCells: Type>[] = [ TableCellServiceReferencesComponent, TableCellServiceInstanceTagsComponent, TableCellCommitAuthorComponent, + TableCellServiceBrokerComponent ]; const cfListCards: Type>[] = [ diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.html index 6bfbddb4da..854f94676f 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.html @@ -1,7 +1,7 @@
- {{ (serviceInstance$ | async)?.entity.entity.name}} + {{ (serviceInstance$ | async)?.entity.name}}
@@ -18,7 +18,25 @@ {{ data.label }} {{ data.data$ | async }} - + + Service Broker + + + + + + + Service Scope + + + + + + + Date Created On + {{ row.metadata.created_at | date:'medium'}} + + Environment Variables diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts index 23b66083dc..b3e39e94f2 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/app-sevice-bindings/app-service-binding-card/app-service-binding-card.component.ts @@ -1,7 +1,6 @@ -import { DatePipe } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { combineLatest as observableCombineLatest, Observable, of as observableOf, of } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of } from 'rxjs'; import { filter, first, map, switchMap } from 'rxjs/operators'; import { @@ -24,7 +23,6 @@ import { serviceBindingEntityType } from '../../../../../../cf-entity-types'; import { ApplicationService } from '../../../../../../features/applications/application.service'; import { isUserProvidedServiceInstance } from '../../../../../../features/cf/cf.helpers'; import { - getServiceBrokerName, getServiceName, getServicePlanName, getServiceSummaryUrl, @@ -34,6 +32,10 @@ import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf- import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; import { CSI_CANCEL_URL } from '../../../../add-service-instance/csi-mode.service'; import { EnvVarViewComponent } from '../../../../env-var-view/env-var-view.component'; +import { + TableCellServiceBrokerComponentConfig, + TableCellServiceBrokerComponentMode, +} from '../../cf-services/table-cell-service-broker/table-cell-service-broker.component'; interface EnvVarData { @@ -54,19 +56,25 @@ export class AppServiceBindingCardComponent extends CardCell> | null>; + service$: Observable | null>; serviceInstance$: Observable>>; tags$: Observable[]>; entityConfig: ComponentEntityMonitorConfig; private envVarServicesSection$: Observable; - private isUserProvidedServiceInstance: boolean; + isUserProvidedServiceInstance: boolean; serviceDescription$: Observable; serviceUrl$: Observable; serviceName$: Observable; + brokerNameConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.NAME + } + brokerScopeConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.SCOPE, + } + constructor( private dialog: MatDialog, - private datePipe: DatePipe, private appService: ApplicationService, private serviceActionHelperService: ServiceActionHelperService, private currentUserPermissionsService: CurrentUserPermissionsService, @@ -105,11 +113,6 @@ export class AppServiceBindingCardComponent extends CardCell !!o.entity.entity.tags), map(o => o.entity.entity.tags.map(t => ({ value: t }))) @@ -126,7 +129,8 @@ export class AppServiceBindingCardComponent extends CardCell cfEntityCatalog.service.store.getEntityService(o.entity.entity.service_guid, this.appService.cfGuid, {}) .waitForEntity$), - filter(service => !!service) + filter(service => !!service), + map(e => e.entity) ); this.listData = [{ label: 'Service Plan', @@ -139,39 +143,19 @@ export class AppServiceBindingCardComponent extends CardCell { - if (this.isUserProvidedServiceInstance) { - return null; - } - const serviceInstance: IServiceInstance = si.entity.entity as IServiceInstance; - return this.service$.pipe( - switchMap(service => { - return getServiceBrokerName( - service.entity.entity.service_broker_guid, - serviceInstance.cfGuid, - ); - }) - ); - }) - ) - }, - ]; - this.envVarServicesSection$ = this.service$.pipe(map(s => s.entity.entity.label)); + }]; + this.envVarServicesSection$ = this.service$.pipe(map(s => s.entity.label)); this.serviceDescription$ = this.service$.pipe( - map(service => service.entity.entity.description) + map(service => service.entity.description) ); this.serviceUrl$ = this.service$.pipe( - map(service => getServiceSummaryUrl(service.entity.entity.cfGuid, service.entity.metadata.guid)) + map(service => getServiceSummaryUrl(service.entity.cfGuid, service.metadata.guid)) ); this.serviceName$ = this.service$.pipe( - map(service => getServiceName(service.entity)) + map(service => getServiceName(service)) ); } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.html index 0268284b7c..838f06bdb0 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.html @@ -13,13 +13,19 @@ - Service Broker + Broker - {{ serviceBrokerName$ | async }} + - Service Plans + Scope + + + + + + Plans {{ serviceEntity.entity.service_plans.length }} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.ts index f3696b8c7a..cf73ba00b7 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-card/cf-service-card.component.ts @@ -1,6 +1,5 @@ import { Component, Input } from '@angular/core'; import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { AppChip } from '../../../../../../../../core/src/shared/components/chips/chips.component'; @@ -9,8 +8,12 @@ import { RouterNav } from '../../../../../../../../store/src/actions/router.acti import { EntityServiceFactory } from '../../../../../../../../store/src/entity-service-factory.service'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; import { IService, IServiceExtra } from '../../../../../../cf-api-svc.types'; -import { getServiceBrokerName, getServiceName } from '../../../../../../features/service-catalog/services-helper'; +import { getServiceName } from '../../../../../../features/service-catalog/services-helper'; import { CfOrgSpaceLabelService } from '../../../../../services/cf-org-space-label.service'; +import { + TableCellServiceBrokerComponentConfig, + TableCellServiceBrokerComponentMode, +} from '../table-cell-service-broker/table-cell-service-broker.component'; export interface ServiceTag { value: string; @@ -27,7 +30,12 @@ export class CfServiceCardComponent extends CardCell> { cfOrgSpace: CfOrgSpaceLabelService; extraInfo: IServiceExtra; tags: AppChip[] = []; - serviceBrokerName$: Observable; + brokerNameConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.NAME + } + brokerScopeConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.SCOPE + } @Input() disableCardClick = false; @@ -45,13 +53,6 @@ export class CfServiceCardComponent extends CardCell> { if (!this.cfOrgSpace) { this.cfOrgSpace = new CfOrgSpaceLabelService(this.store, this.serviceEntity.entity.cfGuid); } - - if (!this.serviceBrokerName$) { - this.serviceBrokerName$ = getServiceBrokerName( - this.serviceEntity.entity.service_broker_guid, - this.serviceEntity.entity.cfGuid, - ); - } } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts index 2ed4424106..4dcfd049b1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/cf-service-instances-list-config.base.ts @@ -78,7 +78,7 @@ export class CfServiceInstancesListConfigBase implements IListConfig 'Service', cellComponent: TableCellServiceComponent, - cellFlex: '2' + cellFlex: '3' }, { columnId: 'lastOp', @@ -119,7 +119,7 @@ export class CfServiceInstancesListConfigBase implements IListConfig { }, { columnId: 'broker', headerCell: () => 'Broker', - cellDefinition: { - valuePath: 'entity.label', + cellComponent: TableCellServiceBrokerComponent, + cellConfig: { + mode: TableCellServiceBrokerComponentMode.NAME }, - cellFlex: '1' + cellFlex: '2' + }, { + columnId: 'brokerScope', + headerCell: () => 'Scope', + cellComponent: TableCellServiceBrokerComponent, + cellConfig: { + mode: TableCellServiceBrokerComponentMode.SCOPE + }, + cellFlex: '2' }, { columnId: 'plans', headerCell: () => 'Plans', diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.html new file mode 100644 index 0000000000..1465604a38 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.html @@ -0,0 +1,16 @@ +
+ + {{(broker$ | async).entity.name}} + +
+ + Space + {{ spaceLink.name }} + + + All Spaces + +
+
+ +- \ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.spec.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.spec.ts new file mode 100644 index 0000000000..d7569ed286 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableCellServiceBrokerComponent } from './table-cell-service-broker.component'; + +describe('TableCellServiceBrokerComponent', () => { + let component: TableCellServiceBrokerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TableCellServiceBrokerComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TableCellServiceBrokerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts new file mode 100644 index 0000000000..eea29808c6 --- /dev/null +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-services/table-cell-service-broker/table-cell-service-broker.component.ts @@ -0,0 +1,73 @@ +import { Component, Input } from '@angular/core'; +import { Observable } from 'rxjs'; +import { filter, map, switchMap } from 'rxjs/operators'; + +import { TableCellCustom } from '../../../../../../../../core/src/shared/components/list/list.types'; +import { APIResource } from '../../../../../../../../store/src/types/api.types'; +import { IServiceBroker } from '../../../../../../cf-api-svc.types'; +import { cfEntityCatalog } from '../../../../../../cf-entity-catalog'; +import { IService } from '../../../../../../public_api'; + +export enum TableCellServiceBrokerComponentMode { + NAME = 'NAME', + SCOPE = 'SCOPE' +} + +export interface TableCellServiceBrokerComponentConfig { + mode: TableCellServiceBrokerComponentMode; + altScope?: boolean; +} + +@Component({ + selector: 'app-table-cell-service-broker', + templateUrl: './table-cell-service-broker.component.html', + styleUrls: ['./table-cell-service-broker.component.scss'] +}) +export class TableCellServiceBrokerComponent extends TableCellCustom> { + + @Input() + config: TableCellServiceBrokerComponentConfig; + + pRow: APIResource; + @Input() + set row(row: APIResource) { + this.pRow = row; + if (row && !this.spaceLink$) { + this.broker$ = cfEntityCatalog.serviceBroker.store.getEntityService(this.row.entity.service_broker_guid, this.row.entity.cfGuid, {}).waitForEntity$ + .pipe( + map(e => e.entity) + ) + this.spaceLink$ = this.broker$.pipe( + filter(broker => !!broker.entity.space_guid), + switchMap(broker => cfEntityCatalog.space.store.getWithOrganization.getEntityService(broker.entity.space_guid, broker.entity.cfGuid).waitForEntity$), + map(e => e.entity), + map(space => ({ + name: space.entity.name, + link: ['/cloud-foundry', + space.entity.cfGuid, + 'organizations', + space.entity.organization_guid, + 'spaces', + space.metadata.guid, + 'summary' + ] + }) + ) + ) + } + } + get row(): APIResource { + return this.pRow; + } + + public spaceLink$: Observable<{ + name: string, + link: string[] + }>; + public broker$: Observable> + + constructor() { + super() + } + +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.html index 292f48f586..fd2aa9ce5b 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.html @@ -4,5 +4,12 @@
Plan: {{ row.entity.service_plan?.entity.name }}
-
Broker: {{ serviceBrokerName$ | async }}
+
+ Broker: + +
+
+ Scope: + +
\ No newline at end of file diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.scss b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.scss index e3f51aafb5..d28afe7bc1 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.scss +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.scss @@ -1,3 +1,10 @@ div > div { padding-bottom: 5px; } + +.broker { + display: flex; + app-table-cell-service-broker { + padding-left: 5px; + } +} diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.ts index 80434fe3b8..b9cbab0fec 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/cf-spaces-service-instances/table-cell-service/table-cell-service.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { Observable, of } from 'rxjs'; -import { filter, first, map, switchMap } from 'rxjs/operators'; +import { filter, map } from 'rxjs/operators'; import { cfEntityCatalog } from '../../../../../../../../cloud-foundry/src/cf-entity-catalog'; import { userProvidedServiceInstanceEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; @@ -9,7 +9,11 @@ import { getServiceName } from '../../../../../../../../cloud-foundry/src/featur import { TableCellCustom } from '../../../../../../../../core/src/shared/components/list/list.types'; import { entityCatalog } from '../../../../../../../../store/src/entity-catalog/entity-catalog'; import { APIResource } from '../../../../../../../../store/src/types/api.types'; -import { IServiceInstance } from '../../../../../../cf-api-svc.types'; +import { IService, IServiceInstance } from '../../../../../../cf-api-svc.types'; +import { + TableCellServiceBrokerComponentConfig, + TableCellServiceBrokerComponentMode, +} from '../../cf-services/table-cell-service-broker/table-cell-service-broker.component'; @Component({ selector: 'app-table-cell-service', @@ -20,13 +24,21 @@ export class TableCellServiceComponent extends TableCellCustom; serviceUrl$: Observable; - serviceBrokerName$: Observable; + service$: Observable>; // tslint:disable-next-line:ban-types isUserProvidedServiceInstance: Boolean; @Input() row: APIResource; @Input() entityKey: string; + brokerNameConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.NAME + } + brokerScopeConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.SCOPE, + altScope: true + } + ngOnInit() { this.isUserProvidedServiceInstance = @@ -46,18 +58,9 @@ export class TableCellServiceComponent extends TableCellCustom `/marketplace/${service.entity.entity.cfGuid}/${service.entity.metadata.guid}/summary`) ); - this.serviceBrokerName$ = service$.pipe( - first(), - switchMap(service => { - const brokerGuid = service.entity.entity.service_broker_guid; - return cfEntityCatalog.serviceBroker.store.getEntityService(brokerGuid, service.entity.entity.cfGuid, {}) - .waitForEntity$.pipe( - map(a => a.entity), - filter(res => !!res), - map(a => a.entity.name), - first() - ); - }) + this.service$ = service$.pipe( + filter(res => !!res), + map(a => a.entity), ); } } diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.html b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.html index c9fa1a25fe..65a4475306 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.html +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.html @@ -22,7 +22,17 @@
Service Broker - {{ serviceBrokerName$ | async }} + + + + + + + Service Scope + + + + Applications Attached diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts index ffb0dd8ae5..8f8f8a5592 100644 --- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts +++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/services-wall/service-instance-card/service-instance-card.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core'; import { Store } from '@ngrx/store'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; -import { filter, map, switchMap } from 'rxjs/operators'; +import { filter, map } from 'rxjs/operators'; import { CFAppState } from '../../../../../../../../cloud-foundry/src/cf-app-state'; import { serviceInstancesEntityType } from '../../../../../../../../cloud-foundry/src/cf-entity-types'; @@ -17,7 +17,6 @@ import { IService, IServiceInstance } from '../../../../../../cf-api-svc.types'; import { cfEntityCatalog } from '../../../../../../cf-entity-catalog'; import { cfEntityFactory } from '../../../../../../cf-entity-factory'; import { - getServiceBrokerName, getServiceName, getServicePlanName, getServiceSummaryUrl, @@ -26,6 +25,10 @@ import { CfCurrentUserPermissions } from '../../../../../../user-permissions/cf- import { ServiceActionHelperService } from '../../../../../data-services/service-action-helper.service'; import { CfOrgSpaceLabelService } from '../../../../../services/cf-org-space-label.service'; import { CSI_CANCEL_URL } from '../../../../add-service-instance/csi-mode.service'; +import { + TableCellServiceBrokerComponentConfig, + TableCellServiceBrokerComponentMode, +} from '../../cf-services/table-cell-service-broker/table-cell-service-broker.component'; @Component({ selector: 'app-service-instance-card', @@ -97,15 +100,6 @@ export class ServiceInstanceCardComponent extends CardCell getServiceBrokerName(service.entity.service_broker_guid, service.entity.cfGuid)) - ) - } - if (!this.serviceName$) { // See note for this.serviceBrokerName$ this.serviceName$ = this.service$.pipe( @@ -142,12 +136,19 @@ export class ServiceInstanceCardComponent extends CardCell; serviceName$: Observable; servicePlanName: string; serviceUrl: string; - private service$: Observable>; + service$: Observable>; + + brokerNameConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.NAME + } + brokerScopeConfig: TableCellServiceBrokerComponentConfig = { + mode: TableCellServiceBrokerComponentMode.SCOPE, + altScope: true + } private detach = () => { this.serviceActionHelperService.detachServiceBinding( diff --git a/src/frontend/packages/store/src/actions/entity.delete.actions.ts b/src/frontend/packages/store/src/actions/entity.delete.actions.ts new file mode 100644 index 0000000000..2b69ee601b --- /dev/null +++ b/src/frontend/packages/store/src/actions/entity.delete.actions.ts @@ -0,0 +1,31 @@ +import { Action } from '@ngrx/store'; + +import { EntityRequestAction } from '../types/request.types'; +import { IFavoriteMetadata, UserFavorite } from '../types/user-favorites.types'; + +export class EntityDeleteCompleteAction implements Action { + + public static ACTION_TYPE = '[Entity] Entity delete complete'; + public type = EntityDeleteCompleteAction.ACTION_TYPE; + + constructor( + public entityGuid: string, + public entityType: string, + public endpointGuid: string, + public endpointType: string, + public action: EntityRequestAction, + ) {} + + // Create an entity delete action if we have all of the properties we need + public static parse(action: EntityRequestAction): EntityDeleteCompleteAction { + if (action.guid && action.entityType && action.endpointType && action.endpointGuid) { + return new EntityDeleteCompleteAction(action.guid, action.entityType, action.endpointGuid, action.endpointType, action); + } + return null; + } + + public asFavorite(): UserFavorite { + return new UserFavorite(this.endpointGuid, this.endpointType, this.entityType, this.entityGuid); + } + +} diff --git a/src/frontend/packages/store/src/effects/api.effects.ts b/src/frontend/packages/store/src/effects/api.effects.ts index 493fe31634..34ee572143 100644 --- a/src/frontend/packages/store/src/effects/api.effects.ts +++ b/src/frontend/packages/store/src/effects/api.effects.ts @@ -3,13 +3,14 @@ import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { mergeMap, withLatestFrom } from 'rxjs/operators'; +import { EntityDeleteCompleteAction } from '../actions/entity.delete.actions'; import { baseRequestPipelineFactory } from '../entity-request-pipeline/base-single-entity-request.pipeline'; import { basePaginatedRequestPipeline } from '../entity-request-pipeline/entity-pagination-request-pipeline'; import { apiRequestPipelineFactory } from '../entity-request-pipeline/entity-request-pipeline'; import { PipelineHttpClient } from '../entity-request-pipeline/pipline-http-client.service'; import { PaginatedAction } from '../types/pagination.types'; -import { ICFAction } from '../types/request.types'; -import { ApiActionTypes } from './../actions/request.actions'; +import { ICFAction, WrapperRequestActionSuccess } from '../types/request.types'; +import { ApiActionTypes, RequestTypes } from './../actions/request.actions'; import { InternalAppState } from './../app-state'; @Injectable() @@ -18,9 +19,7 @@ export class APIEffect { private actions$: Actions, private store: Store, private httpClient: PipelineHttpClient - ) { - - } + ) { } @Effect() apiRequest$ = this.actions$.pipe( @@ -44,4 +43,22 @@ export class APIEffect { }), ); + // Whenever we spot a delete success operation, look to see if the action + // fulfils the entity delete requirements and dispatch an entity delete action if it does + @Effect() + apiDeleteRequest$ = this.actions$.pipe( + ofType(RequestTypes.SUCCESS), + withLatestFrom(this.store), + mergeMap(([action, appState]) => { + if (action.requestType === 'delete') { + const deleteAction = EntityDeleteCompleteAction.parse(action.apiAction); + if (deleteAction) { + // Dispatch a delete action for the entity + this.store.dispatch(deleteAction); + } + } + return []; + }) + ); + } diff --git a/src/frontend/packages/store/src/effects/user-favorites-effect.ts b/src/frontend/packages/store/src/effects/user-favorites-effect.ts index 02240e52af..76e31662f1 100644 --- a/src/frontend/packages/store/src/effects/user-favorites-effect.ts +++ b/src/frontend/packages/store/src/effects/user-favorites-effect.ts @@ -2,8 +2,9 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import { catchError, first, mergeMap, switchMap } from 'rxjs/operators'; +import { catchError, first, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators'; +import { EntityDeleteCompleteAction } from '../actions/entity.delete.actions'; import { ClearPaginationOfEntity } from '../actions/pagination.actions'; import { GetUserFavoritesAction, @@ -17,24 +18,24 @@ import { UpdateUserFavoriteMetadataAction, UpdateUserFavoriteMetadataSuccessAction, } from '../actions/user-favourites.actions'; -import { DispatchOnlyAppState } from '../app-state'; +import { InternalAppState } from '../app-state'; import { entityCatalog } from '../entity-catalog/entity-catalog'; import { proxyAPIVersion } from '../jetstream'; import { NormalizedResponse } from '../types/api.types'; import { StartRequestAction, WrapperRequestActionFailed, WrapperRequestActionSuccess } from '../types/request.types'; import { IFavoriteMetadata, UserFavorite, userFavoritesPaginationKey } from '../types/user-favorites.types'; import { UserFavoriteManager } from '../user-favorite-manager'; +import { STRATOS_ENDPOINT_TYPE, userFavouritesEntityType } from './../helpers/stratos-entity-factory'; const favoriteUrlPath = `/pp/${proxyAPIVersion}/favorites`; - @Injectable() export class UserFavoritesEffect { constructor( private http: HttpClient, private actions$: Actions, - private store: Store, + private store: Store, private userFavoriteManager: UserFavoriteManager ) { } @@ -144,4 +145,20 @@ export class UserFavoritesEffect { ); }) ); + + @Effect() + entityDeleteRequest$ = this.actions$.pipe( + ofType(EntityDeleteCompleteAction.ACTION_TYPE), + withLatestFrom(this.store), + mergeMap(([action, appState]) => { + // If there is a favorite, delete it + const fav = action.asFavorite(); + const entityKey = entityCatalog.getEntityKey(STRATOS_ENDPOINT_TYPE, userFavouritesEntityType); + if (appState.requestData && appState.requestData[entityKey] && appState.requestData[entityKey][fav.guid]) { + this.store.dispatch(new RemoveUserFavoriteAction(fav)); + } + return []; + }) + ); + } diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers.ts index f1e84bafca..f63f5a5781 100644 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers.ts +++ b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.helpers.ts @@ -1,3 +1,4 @@ +import { EntityDeleteCompleteAction } from '../../actions/entity.delete.actions'; import { AddRecentlyVisitedEntityAction } from '../../actions/recently-visited.actions'; import { IRecentlyVisitedEntity, IRecentlyVisitedState } from '../../types/recently-visited.types'; @@ -62,3 +63,11 @@ export function cleanRecentsList(state: IRecentlyVisitedState, endpointGuids: st // Convert the array back into a map return filtered.reduce(recentArrayToMap, {}); } + +export function clearEntityFromRecentsList(state: IRecentlyVisitedState, action: EntityDeleteCompleteAction): IRecentlyVisitedState { + // Remove entity from the map if it exists + const fav = action.asFavorite(); + const newState = { ...state }; + delete newState[fav.guid]; + return newState; +} \ No newline at end of file diff --git a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.ts b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.ts index 79fd5eaee4..19e21f17d9 100644 --- a/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.ts +++ b/src/frontend/packages/store/src/reducers/current-user-roles-reducer/recently-visited.reducer.ts @@ -7,17 +7,25 @@ import { GetAllEndpointsSuccess, UNREGISTER_ENDPOINTS_SUCCESS, } from '../../actions/endpoint.actions'; +import { EntityDeleteCompleteAction } from '../../actions/entity.delete.actions'; import { AddRecentlyVisitedEntityAction, SetRecentlyVisitedEntityAction } from '../../actions/recently-visited.actions'; import { entityCatalog } from '../../entity-catalog/entity-catalog'; import { endpointEntityType, STRATOS_ENDPOINT_TYPE } from '../../helpers/stratos-entity-factory'; import { IRecentlyVisitedState } from '../../types/recently-visited.types'; -import { addRecentlyVisitedEntity, cleanRecentsList, getDefaultRecentState } from './recently-visited.reducer.helpers'; +import { + addRecentlyVisitedEntity, + cleanRecentsList, + clearEntityFromRecentsList, + getDefaultRecentState, +} from './recently-visited.reducer.helpers'; export function recentlyVisitedReducer( state: IRecentlyVisitedState = getDefaultRecentState(), action: Action ): IRecentlyVisitedState { switch (action.type) { + case EntityDeleteCompleteAction.ACTION_TYPE: + return clearEntityFromRecentsList(state, action as EntityDeleteCompleteAction); case AddRecentlyVisitedEntityAction.ACTION_TYPE: return addRecentlyVisitedEntity(state, action as AddRecentlyVisitedEntityAction); case SetRecentlyVisitedEntityAction.ACTION_TYPE: diff --git a/src/test-e2e/application/application-autoscaler-e2e.spec.ts b/src/test-e2e/application/application-autoscaler-e2e.spec.ts index 8eb872fd73..996487c8d4 100644 --- a/src/test-e2e/application/application-autoscaler-e2e.spec.ts +++ b/src/test-e2e/application/application-autoscaler-e2e.spec.ts @@ -534,9 +534,6 @@ describe('Autoscaler -', () => { } else { console.log(`${time}: Waiting for event row: manually refreshing list`); eventPageBase.list.header.refresh(); - if (retries === 0) { - sub.unsubscribe(); - } } } }); diff --git a/src/test-e2e/marketplace/create-service-instances-bind-app-e2e.spec.ts b/src/test-e2e/marketplace/create-service-instances-bind-app-e2e.spec.ts index 91eb24e945..0fe47fada5 100644 --- a/src/test-e2e/marketplace/create-service-instances-bind-app-e2e.spec.ts +++ b/src/test-e2e/marketplace/create-service-instances-bind-app-e2e.spec.ts @@ -50,7 +50,7 @@ describe('Create Service Instance with binding', () => { .then(metaCardRows => { expect(metaCardRows[1].value).toBe(servicesSecrets.publicService.name); expect(metaCardRows[2].value).toBe('shared'); - expect(metaCardRows[4].value).toBe('1'); + expect(metaCardRows[5].value).toBe('1'); }); });