-
Notifications
You must be signed in to change notification settings - Fork 136
Feature json schema #2664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature json schema #2664
Changes from 10 commits
f2bac91
d5caf5e
7d746b0
ebdfcf2
2603bdc
0879bd0
5e6f35a
5b592bb
e7c63bf
a448786
e4f032c
048bfd9
f2075b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ import { createEntityRelationPaginationKey } from '../../store/helpers/entity-re | |
| import { getPaginationObservables } from '../../store/reducers/pagination-reducer/pagination-reducer.helper'; | ||
| import { APIResource } from '../../store/types/api.types'; | ||
| import { getIdFromRoute } from '../cloud-foundry/cf.helpers'; | ||
| import { JsonPointer } from 'angular6-json-schema-form'; | ||
|
|
||
|
|
||
| export const getSvcAvailability = (servicePlan: APIResource<IServicePlan>, | ||
|
|
@@ -90,23 +91,40 @@ export const getServicePlans = ( | |
| cfGuid: string, | ||
| store: Store<AppState>, | ||
| paginationMonitorFactory: PaginationMonitorFactory | ||
| ): Observable<APIResource<IServicePlan>[]> => { | ||
| ): Observable<APIResource<IServicePlan>[]> => { | ||
| return service$.pipe( | ||
| filter(p => !!p), | ||
| switchMap(service => { | ||
| if (service.entity.service_plans && service.entity.service_plans.length > 0) { | ||
| return observableOf(service.entity.service_plans); | ||
| if (service.entity.service_plans && service.entity.service_plans.length > 0) { | ||
| return observableOf(service.entity.service_plans); | ||
| } else { | ||
| const guid = service.metadata.guid; | ||
| const paginationKey = createEntityRelationPaginationKey(servicePlanSchemaKey, guid); | ||
| const getServicePlansAction = new GetServicePlansForService(guid, cfGuid, paginationKey); | ||
| // Could be a space-scoped service, make a request to fetch the plan | ||
| return getPaginationObservables<APIResource<IServicePlan>>({ | ||
| store: store, | ||
| action: getServicePlansAction, | ||
| paginationMonitor: paginationMonitorFactory.create(getServicePlansAction.paginationKey, entityFactory(servicePlanSchemaKey)) | ||
| }, true) | ||
| .entities$.pipe(share(), first()); | ||
| } | ||
| })); | ||
| }; | ||
|
|
||
| export const prettyValidationErrors = (formValidationErrors) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. formValidationErrors needs a type, that would open up the easier |
||
| if (!formValidationErrors) { return null; } | ||
| const errorArray = []; | ||
| for (const error of formValidationErrors) { | ||
| const message = error.message; | ||
| const dataPathArray = JsonPointer.parse(error.dataPath); | ||
| if (dataPathArray.length) { | ||
| let field: any; | ||
| dataPathArray.forEach(elm => field += /^\d+$/.test(elm) ? `[${elm}]` : `.${elm}`); | ||
| errorArray.push(`${field}: ${message}`); | ||
| } else { | ||
| const guid = service.metadata.guid; | ||
| const paginationKey = createEntityRelationPaginationKey(servicePlanSchemaKey, guid); | ||
| const getServicePlansAction = new GetServicePlansForService(guid, cfGuid, paginationKey); | ||
| // Could be a space-scoped service, make a request to fetch the plan | ||
| return getPaginationObservables<APIResource<IServicePlan>>({ | ||
| store: store, | ||
| action: getServicePlansAction, | ||
| paginationMonitor: paginationMonitorFactory.create(getServicePlansAction.paginationKey, entityFactory(servicePlanSchemaKey)) | ||
| }, true) | ||
| .entities$.pipe(share(), first()); | ||
| errorArray.push(message); | ||
| } | ||
| })); | ||
| } | ||
| return errorArray.join('<br>'); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,4 +14,25 @@ | |
| </mat-error> | ||
| </mat-form-field> | ||
| </form> | ||
| <div *ngIf="!!schema" class="json-schema"> | ||
| <mat-card class="json-schema-form"> | ||
| <mat-card-header> | ||
| <mat-card-title><b>Generated Form</b><button mat-button color="accent" (click)="showJsonSchema=!showJsonSchema">Json schema</button> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The button needs some padding-left, to ensure the 'on hover' button background has space between itself and text. Should also consider whether this button is something we should hide in production. @KlapTrap thoughts? |
||
| </mat-card-title> | ||
| </mat-card-header> | ||
| <mat-card-content> | ||
| <json-schema-form loadExternalAssets="true" [options]="jsonFormOptions" [schema]="schema" [framework]="selectedFramework" (validationErrors)="validationErrors($event)" (onChanges)="onFormChange($event)"> | ||
| </json-schema-form> | ||
| <div *ngIf="!!prettyValidationErrors" class="data-bad" [innerHTML]="prettyValidationErrors"></div> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For class names we use BEM notation (for an intro see http://getbem.com/introduction/). At the moment we have BEM checking turned off in our pipeline, but at some point in the future we'd like to turn it back on again. |
||
| </mat-card-content> | ||
| </mat-card> | ||
| <mat-card *ngIf="showJsonSchema" class="json-schema-data"> | ||
| <mat-card-header> | ||
| <mat-card-title><b>Json schema</b></mat-card-title> | ||
| </mat-card-header> | ||
| <mat-card-content> | ||
| <pre class="display-json">{{schema | json}} </pre> | ||
| </mat-card-content> | ||
| </mat-card> | ||
| </div> | ||
| </div> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,31 @@ | ||
| @import '../../../../../sass/mixins'; | ||
| :host { | ||
| flex: 1; | ||
| } | ||
|
|
||
| @mixin json-schema-component() { | ||
| .data-bad { background-color: #fcc; } | ||
| .display-json { | ||
| white-space: -moz-pre-wrap; | ||
| white-space: -pre-wrap; | ||
| white-space: -o-pre-wrap; | ||
| white-space: pre-wrap; | ||
| word-wrap: break-word; | ||
| } | ||
| .json-schema { | ||
| display: flex; | ||
| flex-direction: column; | ||
| @include breakpoint(laptop) { | ||
| flex-direction: row; | ||
| } | ||
| } | ||
| .json-schema-data, | ||
| .json-schema-form { | ||
| width: 100%; | ||
| @include breakpoint(laptop) { | ||
| width: 50%; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @include json-schema-component(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ import { PaginationMonitorFactory } from '../../../monitors/pagination-monitor.f | |
| import { StepOnNextResult } from '../../stepper/step/step.component'; | ||
| import { CsiGuidsService } from '../csi-guids.service'; | ||
| import { SpecifyDetailsStepComponent } from '../specify-details-step/specify-details-step.component'; | ||
| import { safeUnsubscribe, prettyValidationErrors } from '../../../../features/service-catalog/services-helper'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-bind-apps-step', | ||
|
|
@@ -35,6 +36,14 @@ export class BindAppsStepComponent implements OnDestroy, AfterContentInit { | |
| stepperForm: FormGroup; | ||
| apps$: Observable<APIResource<IApp>[]>; | ||
| guideText = 'Specify the application to bind (Optional)'; | ||
|
|
||
| selectedFramework = 'material-design'; | ||
| schema: any; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If possible it would be nice to type the fields and function params in this class. |
||
| showJsonSchema: boolean; | ||
| jsonFormOptions: any = { addSubmit: false }; | ||
| selectedServiceSubscription: Subscription; | ||
| formValidationErrors: any; | ||
| selectedService$: any; | ||
| constructor( | ||
| private store: Store<AppState>, | ||
| private paginationMonitorFactory: PaginationMonitorFactory | ||
|
|
@@ -53,6 +62,21 @@ export class BindAppsStepComponent implements OnDestroy, AfterContentInit { | |
| } | ||
| } | ||
|
|
||
| onFormChange(jsonData) { | ||
| if (!!jsonData) { | ||
| const stringData = JSON.stringify(jsonData); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For safety, in both places where |
||
| this.stepperForm.get('params').setValue(stringData); | ||
| } | ||
| } | ||
|
|
||
| validationErrors(data: any): void { | ||
| this.formValidationErrors = data; | ||
| } | ||
|
|
||
| get prettyValidationErrors() { | ||
| return prettyValidationErrors(this.formValidationErrors); | ||
| } | ||
|
|
||
| ngAfterContentInit() { | ||
| this.validateSubscription = this.stepperForm.statusChanges.pipe( | ||
| map(() => { | ||
|
|
@@ -80,9 +104,26 @@ export class BindAppsStepComponent implements OnDestroy, AfterContentInit { | |
| this.setBoundApp(); | ||
| } | ||
|
|
||
| onEnter = (selectedService$?) => { | ||
| this.selectedService$ = selectedService$; | ||
| if (selectedService$ instanceof Observable) { | ||
| this.selectedServiceSubscription = selectedService$ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you don't expect selectedService$ to change, or if this.schema never needs to be updated (which I don't think it does in this component), you can use a Alternatively, |
||
| .subscribe(selectedService => { | ||
| this.schema = this.filterSchema(selectedService.entity.entity.schemas.service_binding.create.parameters); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the two places this is used need to check whether if
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Full exception: |
||
| }); | ||
| } | ||
| } | ||
|
|
||
| private filterSchema = (schema: any): any => { | ||
| return Object.keys(schema).reduce((obj, key) => { | ||
| if (key !== '$schema') { obj[key] = schema[key]; } | ||
| return obj; | ||
| }, {}); | ||
| } | ||
|
|
||
| submit = (): Observable<StepOnNextResult> => { | ||
| this.setApp(); | ||
| return observableOf({ success: true }); | ||
| return observableOf({ success: true, data: this.selectedService$ }); | ||
| } | ||
|
|
||
| setApp = () => this.store.dispatch( | ||
|
|
@@ -91,6 +132,7 @@ export class BindAppsStepComponent implements OnDestroy, AfterContentInit { | |
|
|
||
| ngOnDestroy(): void { | ||
| this.validateSubscription.unsubscribe(); | ||
| safeUnsubscribe(this.selectedServiceSubscription); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,26 @@ | |
| <mat-option *ngFor="let sI of bindableServiceInstances$ | async" [disabled]="!sI.metadata.guid" [value]="sI.metadata.guid">{{ sI.entity.name }}</mat-option> | ||
| </mat-select> | ||
| </mat-form-field> | ||
|
|
||
| </form> | ||
| <div *ngIf="!!schema" class="json-schema"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a fair bit of duplication between this step and bind app (html, input, error handling, etc). Could the common parts be pulled out into a new component |
||
| <mat-card class="json-schema-form"> | ||
| <mat-card-header> | ||
| <mat-card-title><b>Generated Form</b><button mat-button color="accent" (click)="showJsonSchema=!showJsonSchema">Json schema</button> | ||
| </mat-card-title> | ||
| </mat-card-header> | ||
| <mat-card-content> | ||
| <json-schema-form loadExternalAssets="true" [options]="jsonFormOptions" [schema]="schema" [framework]="selectedFramework" (validationErrors)="validationErrors($event)" (onChanges)="onFormChange($event)"> | ||
| </json-schema-form> | ||
| <div *ngIf="!!prettyValidationErrors" class="data-bad" [innerHTML]="prettyValidationErrors"></div> | ||
| </mat-card-content> | ||
| </mat-card> | ||
| <mat-card *ngIf="showJsonSchema" class="json-schema-data"> | ||
| <mat-card-header> | ||
| <mat-card-title><b>Json schema</b></mat-card-title> | ||
| </mat-card-header> | ||
| <mat-card-content> | ||
| <pre class="display-json">{{schema | json}} </pre> | ||
| </mat-card-content> | ||
| </mat-card> | ||
| </div> | ||
| </div> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like this is used within Stratos, however removing results in the following error
Should this be a dependency of
angular6-json-schema-form?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes the material design module uses @angular/flex-layout. I'm open to better approachs