Skip to content

Commit

Permalink
Merge pull request #3050 from cloudfoundry-incubator/feature-json-schema
Browse files Browse the repository at this point in the history
Feature json schema
  • Loading branch information
nwmac authored Jan 7, 2019
2 parents 24c509e + d7cdbee commit 795a2bc
Show file tree
Hide file tree
Showing 26 changed files with 511 additions and 119 deletions.
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@angular/compiler": "^6.1.1",
"@angular/core": "^6.1.1",
"@angular/forms": "^6.1.1",
"@angular/flex-layout": "^6.0.0-beta.16",
"@angular/http": "^6.1.1",
"@angular/material": "^6.1.0",
"@angular/material-moment-adapter": "^6.4.7",
Expand All @@ -59,6 +60,7 @@
"@ngrx/store-devtools": "^6.0.1",
"@swimlane/ngx-charts": "^9.0.0",
"angular2-virtual-scroll": "^0.3.1",
"stratos-angular6-json-schema-form": "1.0.3",
"core-js": "^2.5.7",
"hammerjs": "^2.0.8",
"js-yaml": "^3.11.0",
Expand Down
16 changes: 16 additions & 0 deletions src/frontend/app/core/cf-api-svc.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ export interface IServicePlan {
service?: APIResource<IService>;
guid?: string;
cfGuid?: string;
schemas?: ServicePlanSchemas;
}

export interface ServicePlanSchemas {
service_instance: ServicePlanSchema;
service_binding: ServicePlanSchema;
}

export interface ServicePlanSchema {
create?: {
parameters: object
};
update?: {
parameters: object
};
}

export interface IServicePlanExtra {
Expand All @@ -81,6 +96,7 @@ export interface IServicePlanCost {
};
unit: string;
}

export interface IService {
label: string;
description: string;
Expand Down
16 changes: 16 additions & 0 deletions src/frontend/app/core/utils.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,22 @@ export function parseHttpPipeError(res): {} {
return {};
}

export function safeStringToObj(value: string): object {
try {
if (value) {
const jsonObj = JSON.parse(value);
// Check if jsonObj is actually an obj
if (jsonObj.constructor !== {}.constructor) {
throw new Error('not an object');
}
return jsonObj;
}
} catch (e) {
return null;
}
return null;
}

export const safeUnsubscribe = (...subs: Subscription[]) => {
subs.forEach(sub => {
if (sub) {
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/app/features/service-catalog/services-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { getPaginationObservables } from '../../store/reducers/pagination-reduce
import { APIResource } from '../../store/types/api.types';
import { getIdFromRoute } from '../cloud-foundry/cf.helpers';


export const getSvcAvailability = (servicePlan: APIResource<IServicePlan>,
serviceBroker: APIResource<IServiceBroker>,
allServicePlanVisibilities: APIResource<IServicePlanVisibility>[]) => {
Expand Down Expand Up @@ -51,7 +50,6 @@ export const getServiceJsonParams = (params: any): {} => {
return prms;
};


export const isMarketplaceMode = (activatedRoute: ActivatedRoute) => {
const serviceId = getIdFromRoute(activatedRoute, 'serviceId');
const cfId = getIdFromRoute(activatedRoute, 'endpointId');
Expand Down Expand Up @@ -108,3 +106,5 @@ export const getServicePlans = (
}
}));
};


Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
<app-step title="Select Plan" [onNext]="selectPlan.onNext" [blocked]="initialisedService$ | async" [onEnter]="selectPlan.onEnter" [valid]="selectPlan.validate | async" cancelButtonText="Cancel">
<app-select-plan-step #selectPlan></app-select-plan-step>
</app-step>
<app-step [title]="bindAppStepperText" *ngIf="modeService.viewDetail.showBindApp" [skip]="skipApps$ | async" [onNext]="bindApp.submit " [valid]="bindApp.validate | async" cancelButtonText="Cancel" finishButtonText="Bind">
<app-step [title]="bindAppStepperText" *ngIf="modeService.viewDetail.showBindApp" [skip]="skipApps$ | async" [onEnter]="bindApp.onEnter" [onNext]="bindApp.submit " [valid]="bindApp.validate | async" cancelButtonText="Cancel">
<app-bind-apps-step #bindApp [boundAppId]="appId"></app-bind-apps-step>
</app-step>
<app-step title="Service Instance" [onNext]="specifyDetails.onNext" [onEnter]="specifyDetails.onEnter" [blocked]="!!(specifyDetails.serviceInstancesInit$ | async)" [valid]=" specifyDetails.validate | async " cancelButtonText="Cancel " nextButtonText="Create ">
<app-step title="Service Instance" [onNext]="specifyDetails.onNext" [onEnter]="specifyDetails.onEnter" [blocked]="!!(specifyDetails.serviceInstancesInit$ | async)" [valid]="specifyDetails.validate | async " cancelButtonText="Cancel " nextButtonText="Create ">
<app-specify-details-step [appId]="appId" #specifyDetails [showModeSelection]="!!appId "></app-specify-details-step>
</app-step>
</app-steppers>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MaterialDesignFrameworkModule } from 'stratos-angular6-json-schema-form';

import { ServicesService } from '../../../../features/service-catalog/services.service';
import { ServicesServiceMock } from '../../../../features/service-catalog/services.service.mock';
Expand Down Expand Up @@ -28,6 +29,7 @@ import { CfServiceCardComponent } from '../../list/list-types/cf-services/cf-ser
import { MetadataItemComponent } from '../../metadata-item/metadata-item.component';
import { MultilineTitleComponent } from '../../multiline-title/multiline-title.component';
import { PageHeaderModule } from '../../page-header/page-header.module';
import { SchemaFormComponent } from '../../schema-form/schema-form.component';
import { ServiceIconComponent } from '../../service-icon/service-icon.component';
import { SteppersModule } from '../../stepper/steppers.module';
import { BindAppsStepComponent } from '../bind-apps-step/bind-apps-step.component';
Expand Down Expand Up @@ -63,12 +65,14 @@ describe('AddServiceInstanceComponent', () => {
AppChipsComponent,
ApplicationStateIconComponent,
ApplicationStateIconPipe,
SchemaFormComponent,
MultilineTitleComponent,
FocusDirective
],
imports: [
PageHeaderModule,
SteppersModule,
MaterialDesignFrameworkModule,
// CoreModule,
BaseTestModulesNoShared
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class AddServiceInstanceComponent implements OnDestroy, AfterContentInit
bindAppStepperText = 'Bind App (Optional)';
appId: string;
public inMarketplaceMode: boolean;

constructor(
private cSIHelperServiceFactory: CreateServiceInstanceHelperServiceFactory,
private activatedRoute: ActivatedRoute,
Expand Down Expand Up @@ -130,13 +131,6 @@ export class AddServiceInstanceComponent implements OnDestroy, AfterContentInit
}
}

private getIdsFromRoute() {
const serviceId = getIdFromRoute(this.activatedRoute, 'serviceId');
const cfId = getIdFromRoute(this.activatedRoute, 'endpointId');
const appId = getIdFromRoute(this.activatedRoute, 'id');
return { serviceId, cfId, appId };
}

private setupForAppServiceMode() {

const appId = getIdFromRoute(this.activatedRoute, 'id');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
<mat-option *ngFor="let app of apps$ | async" [value]="app.metadata.guid">{{ app.entity.name }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="JSON parameters" matAutosizeMinRows="2" matAutosizeMaxRows="5" formControlName="params"></textarea>
<mat-error *ngIf="stepperForm.controls.params?.hasError('notValidJson')">
Not valid JSON. Please specify a valid JSON Object
</mat-error>
</mat-form-field>
<div *ngIf="stepperForm.controls.apps.value" class="stepper-form__params">
<div>Binding Parameters</div>
<app-schema-form [config]="schemaFormConfig" (dataChange)="setBindingParams($event)" (validChange)="setParamValid($event)"></app-schema-form>
</div>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
:host {
flex: 1;
}

.stepper-form__params {
padding-top: 20px;
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MaterialDesignFrameworkModule } from 'stratos-angular6-json-schema-form';

import { BindAppsStepComponent } from './bind-apps-step.component';
import { BaseTestModules, BaseTestModulesNoShared } from '../../../../test-framework/cloud-foundry-endpoint-service.helper';
import { ServicesService } from '../../../../features/service-catalog/services.service';
import { ServicesServiceMock } from '../../../../features/service-catalog/services.service.mock';
import { CsiGuidsService } from '../csi-guids.service';
import { BaseTestModulesNoShared } from '../../../../test-framework/cloud-foundry-endpoint-service.helper';
import { PaginationMonitorFactory } from '../../../monitors/pagination-monitor.factory';
import { SchemaFormComponent } from '../../schema-form/schema-form.component';
import { CsiGuidsService } from '../csi-guids.service';
import { BindAppsStepComponent } from './bind-apps-step.component';

describe('BindAppsStepComponent', () => {
let component: BindAppsStepComponent;
let fixture: ComponentFixture<BindAppsStepComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [BindAppsStepComponent],
imports: [BaseTestModulesNoShared],
declarations: [
BindAppsStepComponent,
SchemaFormComponent
],
imports: [
BaseTestModulesNoShared,
MaterialDesignFrameworkModule
],
providers: [
{ provide: ServicesService, useClass: ServicesServiceMock },
CsiGuidsService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { AfterContentInit, Component, Input, OnDestroy } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
import { filter, first, map, switchMap, tap } from 'rxjs/operators';
import { filter, switchMap } from 'rxjs/operators';

import { IServicePlan } from '../../../../core/cf-api-svc.types';
import { IApp } from '../../../../core/cf-api.types';
import { appDataSort } from '../../../../features/cloud-foundry/services/cloud-foundry-endpoint.service';
import { pathGet, safeUnsubscribe } from '../../../../core/utils.service';
import { SetCreateServiceInstanceApp } from '../../../../store/actions/create-service-instance.actions';
import { GetAllAppsInSpace } from '../../../../store/actions/space.actions';
import { AppState } from '../../../../store/app-state';
Expand All @@ -15,9 +16,8 @@ import { getPaginationObservables } from '../../../../store/reducers/pagination-
import { selectCreateServiceInstance } from '../../../../store/selectors/create-service-instance.selectors';
import { APIResource } from '../../../../store/types/api.types';
import { PaginationMonitorFactory } from '../../../monitors/pagination-monitor.factory';
import { SchemaFormConfig } from '../../schema-form/schema-form.component';
import { StepOnNextResult } from '../../stepper/step/step.component';
import { CsiGuidsService } from '../csi-guids.service';
import { SpecifyDetailsStepComponent } from '../specify-details-step/specify-details-step.component';

@Component({
selector: 'app-bind-apps-step',
Expand All @@ -30,18 +30,21 @@ export class BindAppsStepComponent implements OnDestroy, AfterContentInit {
boundAppId: string;

validateSubscription: Subscription;
validate = new BehaviorSubject(true);
validate = new BehaviorSubject<boolean>(true);
serviceInstanceGuid: string;
stepperForm: FormGroup;
apps$: Observable<APIResource<IApp>[]>;
guideText = 'Specify the application to bind (Optional)';
selectedServicePlan: APIResource<IServicePlan>;
bindingParams: object = {};
schemaFormConfig: SchemaFormConfig;

constructor(
private store: Store<AppState>,
private paginationMonitorFactory: PaginationMonitorFactory
) {
this.stepperForm = new FormGroup({
apps: new FormControl(''),
params: new FormControl('', SpecifyDetailsStepComponent.isValidJsonValidatorFn()),
});
}

Expand All @@ -54,16 +57,6 @@ export class BindAppsStepComponent implements OnDestroy, AfterContentInit {
}

ngAfterContentInit() {
this.validateSubscription = this.stepperForm.statusChanges.pipe(
map(() => {
if (this.stepperForm.pristine) {
setTimeout(() => this.validate.next(true));
}
setTimeout(() => this.validate.next(this.stepperForm.valid));
})
).subscribe();


this.apps$ = this.store.select(selectCreateServiceInstance).pipe(
filter(p => !!p && !!p.spaceGuid && !!p.cfGuid),
switchMap(createServiceInstance => {
Expand All @@ -80,17 +73,53 @@ export class BindAppsStepComponent implements OnDestroy, AfterContentInit {
this.setBoundApp();
}

submit = (): Observable<StepOnNextResult> => {
this.setApp();
return observableOf({ success: true });
onEnter = (selectedServicePlan: APIResource<IServicePlan>) => {
if (selectedServicePlan) {
// Don't overwrite if it's null (we've returned to this step from the next)
this.selectedServicePlan = selectedServicePlan;
}

// Start
this.validateSubscription = this.stepperForm.controls['apps'].valueChanges.subscribe(app => {
if (!app) {
// If there's no app selected the step will always be valid
this.validate.next(true);
}
});

if (!this.schemaFormConfig) {
// Create new config
this.schemaFormConfig = {
schema: pathGet('entity.schemas.service_binding.create.parameters', this.selectedServicePlan),
};
} else {
// Update existing config (retaining any existing config)
this.schemaFormConfig = {
...this.schemaFormConfig,
schema: pathGet('entity.schemas.service_binding.create.parameters', this.selectedServicePlan)
};
}

}

setBindingParams(data) {
this.bindingParams = data;
}

setParamValid(valid: boolean) {
this.validate.next(valid);
}

setApp = () => this.store.dispatch(
new SetCreateServiceInstanceApp(this.stepperForm.controls.apps.value, this.stepperForm.controls.params.value)
)
submit = (): Observable<StepOnNextResult> => {
this.store.dispatch(new SetCreateServiceInstanceApp(this.stepperForm.controls.apps.value, this.bindingParams));
return observableOf({
success: true,
data: this.selectedServicePlan
});
}

ngOnDestroy(): void {
this.validateSubscription.unsubscribe();
safeUnsubscribe(this.validateSubscription);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ import {
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import {
BehaviorSubject,
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
Subscription,
} from 'rxjs';
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
import {
distinctUntilChanged,
filter,
Expand Down Expand Up @@ -167,7 +161,12 @@ export class SelectPlanStepComponent implements OnDestroy {

onNext = (): Observable<StepOnNextResult> => {
this.store.dispatch(new SetCreateServiceInstanceServicePlan(this.stepperForm.controls.servicePlans.value));
return observableOf({ success: true });
return this.selectedPlan$.pipe(
map(selectedServicePlan => ({
success: true,
data: selectedServicePlan.entity
}))
);
}

ngOnDestroy(): void {
Expand Down
Loading

0 comments on commit 795a2bc

Please sign in to comment.