mirror of
https://github.com/jwetzell/showbridge-webui.git
synced 2026-09-10 23:59:27 +00:00
rework params-form logic out of schema service
This commit is contained in:
@@ -5,6 +5,7 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
import { ParamInfo } from '../../models/form.model';
|
import { ParamInfo } from '../../models/form.model';
|
||||||
import { ListsService } from '../../services/lists.service';
|
import { ListsService } from '../../services/lists.service';
|
||||||
import { SchemaService } from '../../services/schema.service';
|
import { SchemaService } from '../../services/schema.service';
|
||||||
|
import { parseStringToArray } from '../../utils/params.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-array-form',
|
selector: 'app-array-form',
|
||||||
@@ -30,7 +31,7 @@ export class ArrayFormComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.paramFormControl && this.paramInfo?.schema) {
|
if (this.paramFormControl && this.paramInfo?.schema) {
|
||||||
if (!Array.isArray(this.paramFormControl.value)) {
|
if (!Array.isArray(this.paramFormControl.value)) {
|
||||||
this.arrayValue = this.schemaService.parseStringToArray(
|
this.arrayValue = parseStringToArray(
|
||||||
this.paramFormControl.value,
|
this.paramFormControl.value,
|
||||||
this.paramInfo.schema,
|
this.paramInfo.schema,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,4 @@
|
|||||||
@if (paramsSchema && paramsFormInfo) {
|
@if (paramsSchema && paramsFormInfo) {
|
||||||
@if (paramsOptions.length > 1) {
|
|
||||||
<mat-tab-group
|
|
||||||
dynamicHeight
|
|
||||||
[selectedIndex]="paramsOptionsSelectedIndex"
|
|
||||||
(selectedTabChange)="paramsOptionsTabSelected($event)"
|
|
||||||
>
|
|
||||||
@for (paramsOption of paramsOptions; track paramsOption) {
|
|
||||||
<mat-tab [label]="paramsOption.display"></mat-tab>
|
|
||||||
}
|
|
||||||
</mat-tab-group>
|
|
||||||
}
|
|
||||||
<form [formGroup]="paramsFormInfo.formGroup">
|
<form [formGroup]="paramsFormInfo.formGroup">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@for (key of paramKeys(); track key) {
|
@for (key of paramKeys(); track key) {
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
|
import { ParamsFormComponent } from './params-form.component';
|
||||||
|
import { SchemaService } from '../../services/schema.service';
|
||||||
|
import { SomeJSONSchema } from 'ajv/dist/types/json-schema';
|
||||||
|
|
||||||
|
describe('ParamsForm', ()=>{
|
||||||
|
let component: ParamsFormComponent;
|
||||||
|
let fixture: ComponentFixture<ParamsFormComponent>;
|
||||||
|
|
||||||
|
beforeEach(async ()=>{
|
||||||
|
fixture = TestBed.createComponent(ParamsFormComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
await fixture.whenStable();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create', ()=>{
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create form for schema', async ()=>{
|
||||||
|
const paramsSchema: SomeJSONSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {},
|
||||||
|
required: []
|
||||||
|
}
|
||||||
|
fixture.componentRef.setInput('paramsSchema', paramsSchema)
|
||||||
|
await fixture.whenStable()
|
||||||
|
expect(fixture.nativeElement.querySelector('form')).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('form should have input for string property', async ()=>{
|
||||||
|
const paramsSchema: SomeJSONSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
"test": {
|
||||||
|
type: "string",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: []
|
||||||
|
}
|
||||||
|
fixture.componentRef.setInput('paramsSchema', paramsSchema)
|
||||||
|
await fixture.whenStable()
|
||||||
|
const formEl = fixture.nativeElement.querySelector('form')
|
||||||
|
expect(formEl).toBeDefined()
|
||||||
|
const inputEl = formEl.querySelector('input')
|
||||||
|
expect(inputEl).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('form should have select for string enum property', async ()=>{
|
||||||
|
const paramsSchema: SomeJSONSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
"test": {
|
||||||
|
type: "string",
|
||||||
|
enum: ['one','two','three']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: []
|
||||||
|
}
|
||||||
|
fixture.componentRef.setInput('paramsSchema', paramsSchema)
|
||||||
|
await fixture.whenStable()
|
||||||
|
const formEl = fixture.nativeElement.querySelector('form')
|
||||||
|
expect(formEl).toBeDefined()
|
||||||
|
const selectEl = formEl.querySelector('select')
|
||||||
|
expect(selectEl).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('form input should reflect data', async ()=>{
|
||||||
|
const paramsSchema: SomeJSONSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
"test": {
|
||||||
|
type: "string",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: []
|
||||||
|
}
|
||||||
|
|
||||||
|
fixture.componentRef.setInput('paramsSchema', paramsSchema)
|
||||||
|
await fixture.whenStable()
|
||||||
|
fixture.componentRef.setInput('data', {
|
||||||
|
test: 'hello'
|
||||||
|
})
|
||||||
|
await fixture.whenStable()
|
||||||
|
const inputEl = fixture.nativeElement.querySelector('input')
|
||||||
|
expect(inputEl).toBeDefined()
|
||||||
|
expect(inputEl.value).toBe('hello')
|
||||||
|
|
||||||
|
fixture.componentRef.setInput('data', {
|
||||||
|
test: 'changed'
|
||||||
|
})
|
||||||
|
await fixture.whenStable()
|
||||||
|
expect(inputEl.value).toBe('changed')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import { JsonPipe } from '@angular/common';
|
import { JsonPipe } from '@angular/common';
|
||||||
import { Component, inject, Input, OnInit, output } from '@angular/core';
|
import { Component, inject, Input, OnChanges, OnDestroy, OnInit, output, SimpleChange, SimpleChanges } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatTabChangeEvent, MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { SomeJSONSchema } from 'ajv/dist/types/json-schema';
|
import { SomeJSONSchema } from 'ajv/dist/types/json-schema';
|
||||||
import { cloneDeep, has } from 'lodash-es';
|
import { cloneDeep, has, isEqual } from 'lodash-es';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { ParamInfo, ParamsFormInfo } from '../../models/form.model';
|
import { ParamInfo, ParamsFormInfo } from '../../models/form.model';
|
||||||
import { SchemaService } from '../../services/schema.service';
|
import { SchemaService } from '../../services/schema.service';
|
||||||
|
import { cleanParams, schemaToParamsFormInfo } from '../../utils/params.utils';
|
||||||
import { ArrayFormComponent } from '../array-form/array-form.component';
|
import { ArrayFormComponent } from '../array-form/array-form.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-params-form',
|
selector: 'app-params-form',
|
||||||
templateUrl: './params-form.component.html',
|
templateUrl: './params-form.component.html',
|
||||||
@@ -29,7 +29,7 @@ import { ArrayFormComponent } from '../array-form/array-form.component';
|
|||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class ParamsFormComponent implements OnInit {
|
export class ParamsFormComponent implements OnChanges, OnDestroy {
|
||||||
@Input() paramsSchema?: SomeJSONSchema;
|
@Input() paramsSchema?: SomeJSONSchema;
|
||||||
@Input() data?: any;
|
@Input() data?: any;
|
||||||
updated = output<any>();
|
updated = output<any>();
|
||||||
@@ -37,136 +37,75 @@ export class ParamsFormComponent implements OnInit {
|
|||||||
paramsFormInfo?: ParamsFormInfo;
|
paramsFormInfo?: ParamsFormInfo;
|
||||||
|
|
||||||
formGroupSubscription?: Subscription;
|
formGroupSubscription?: Subscription;
|
||||||
paramsOptions: {
|
|
||||||
display: string;
|
|
||||||
paramsFormInfo: ParamsFormInfo;
|
|
||||||
keys: string[];
|
|
||||||
schema: SomeJSONSchema;
|
|
||||||
}[] = [];
|
|
||||||
paramsOptionsSelectedIndex: number = 0;
|
|
||||||
|
|
||||||
private schemaService = inject(SchemaService);
|
ngOnDestroy(): void {
|
||||||
|
console.log('params-form destroyed')
|
||||||
constructor() {}
|
if (this.formGroupSubscription) {
|
||||||
|
this.formGroupSubscription.unsubscribe()
|
||||||
ngOnInit(): void {
|
|
||||||
if (this.paramsSchema) {
|
|
||||||
if (this.paramsSchema.properties) {
|
|
||||||
this.paramsFormInfo = this.schemaService.getFormInfoFromParamsSchema(this.paramsSchema);
|
|
||||||
} else if (this.paramsSchema.oneOf) {
|
|
||||||
this.paramsOptions = this.paramsSchema.oneOf.map((oneOf: any, index: number) => {
|
|
||||||
const paramsOption = {
|
|
||||||
display: oneOf.title ? oneOf.title : `Option ${index + 1}`,
|
|
||||||
schema: oneOf,
|
|
||||||
paramsFormInfo: this.schemaService.getFormInfoFromParamsSchema(oneOf),
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...paramsOption,
|
|
||||||
keys: Object.keys(paramsOption.paramsFormInfo.formGroup.controls),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const matchingSchemaIndex = this.schemaService.matchParamsDataToSchema(
|
|
||||||
this.data,
|
|
||||||
this.paramsOptions.map((paramsOption) => paramsOption.schema),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.paramsOptionsSelectedIndex = matchingSchemaIndex;
|
|
||||||
this.paramsFormInfo = this.paramsOptions[matchingSchemaIndex].paramsFormInfo;
|
|
||||||
this.paramsSchema = this.paramsOptions[matchingSchemaIndex].schema;
|
|
||||||
} else {
|
|
||||||
console.error('params is not a singular or oneOf');
|
|
||||||
console.error(this.paramsSchema);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.data && this.paramsFormInfo?.formGroup) {
|
|
||||||
// NOTE(jwetzell): prepare data for form patching
|
|
||||||
const dataToPatch = cloneDeep(this.data);
|
|
||||||
Object.entries(this.paramsFormInfo.paramsInfo).forEach(([paramKey, paramInfo]) => {
|
|
||||||
if (has(dataToPatch, paramKey)) {
|
|
||||||
switch (paramInfo.type) {
|
|
||||||
case 'object':
|
|
||||||
dataToPatch[paramKey] = JSON.stringify(dataToPatch[paramKey]);
|
|
||||||
break;
|
|
||||||
case 'array':
|
|
||||||
dataToPatch[paramKey] = dataToPatch[paramKey]
|
|
||||||
.map((item: any) => {
|
|
||||||
switch (typeof item) {
|
|
||||||
case 'object':
|
|
||||||
return JSON.stringify(item);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.join(',');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.paramsFormInfo.formGroup.patchValue(dataToPatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.formGroupSubscription = this.paramsFormInfo?.formGroup.valueChanges.subscribe((value) => {
|
|
||||||
this.formUpdated();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
paramsOptionsTabSelected(event: MatTabChangeEvent) {
|
ngOnChanges(changes: SimpleChanges<{
|
||||||
// NOTE(jwetzell): no longer interested in the old formGroup valueChanges
|
paramsSchema: SomeJSONSchema
|
||||||
if (this.formGroupSubscription) {
|
data: any
|
||||||
this.formGroupSubscription.unsubscribe();
|
}>): void {
|
||||||
}
|
console.log(changes)
|
||||||
|
if (changes.paramsSchema) {
|
||||||
const paramsOption = this.paramsOptions[event.index];
|
if (changes.paramsSchema.previousValue === undefined && changes.paramsSchema.currentValue !== undefined) {
|
||||||
|
if (this.paramsSchema) {
|
||||||
this.paramsSchema = paramsOption.schema;
|
if (this.paramsSchema.properties) {
|
||||||
this.paramsFormInfo = paramsOption.paramsFormInfo;
|
this.paramsFormInfo = schemaToParamsFormInfo(this.paramsSchema);
|
||||||
|
if (this.formGroupSubscription === undefined) {
|
||||||
// NOTE(jwetzell): modify params that should change from the data when switch paramOptions
|
this.formGroupSubscription = this.paramsFormInfo?.formGroup.valueChanges.subscribe((value) => {
|
||||||
Object.entries(this.paramsFormInfo.paramsInfo).forEach(([paramKey, paramInfo]) => {
|
this.formUpdated();
|
||||||
if (this.data !== undefined) {
|
});
|
||||||
if (paramInfo.isConst) {
|
}
|
||||||
if (this.data[paramKey]) {
|
} else {
|
||||||
delete this.data[paramKey];
|
console.error('params is not a singular object');
|
||||||
}
|
console.error(this.paramsSchema);
|
||||||
}
|
|
||||||
|
|
||||||
if (paramInfo.default) {
|
|
||||||
if (this.data[paramKey] !== undefined && this.data[paramKey] !== paramInfo.default) {
|
|
||||||
this.data[paramKey] = paramInfo.default;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
const allowedParamKeys = Object.keys(this.paramsSchema?.properties);
|
if (changes.data) {
|
||||||
if (this.data) {
|
if (!isEqual(changes.data.currentValue, changes.data.previousValue)) {
|
||||||
// NOTE(jwetzell): remove keys that aren't allowed in the new params variation
|
if (this.paramsFormInfo?.formGroup !== undefined) {
|
||||||
Object.keys(this.data).forEach((paramKey) => {
|
// NOTE(jwetzell): prepare data for form patching
|
||||||
if (allowedParamKeys && !allowedParamKeys.includes(paramKey)) {
|
const dataToPatch = cloneDeep(this.data);
|
||||||
delete this.data[paramKey];
|
Object.entries(this.paramsFormInfo.paramsInfo).forEach(([paramKey, paramInfo]) => {
|
||||||
|
if (has(dataToPatch, paramKey)) {
|
||||||
|
switch (paramInfo.type) {
|
||||||
|
case 'object':
|
||||||
|
dataToPatch[paramKey] = JSON.stringify(dataToPatch[paramKey]);
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
dataToPatch[paramKey] = dataToPatch[paramKey]
|
||||||
|
.map((item: any) => {
|
||||||
|
switch (typeof item) {
|
||||||
|
case 'object':
|
||||||
|
return JSON.stringify(item);
|
||||||
|
default:
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(',');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.paramsFormInfo.formGroup.patchValue(dataToPatch);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.paramsFormInfo.formGroup) {
|
|
||||||
this.formGroupSubscription = this.paramsFormInfo.formGroup.valueChanges.subscribe((value) => {
|
|
||||||
this.formUpdated();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.data && this.paramsFormInfo.formGroup) {
|
|
||||||
this.paramsFormInfo.formGroup.patchValue(this.data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
formUpdated() {
|
formUpdated() {
|
||||||
if (this.paramsSchema) {
|
if (this.paramsSchema) {
|
||||||
const params = this.schemaService.cleanParams(
|
const params = cleanParams(
|
||||||
this.paramsSchema,
|
this.paramsSchema,
|
||||||
this.paramsFormInfo?.formGroup.value,
|
this.paramsFormInfo?.formGroup.value,
|
||||||
);
|
);
|
||||||
@@ -189,11 +128,13 @@ export class ParamsFormComponent implements OnInit {
|
|||||||
|
|
||||||
getParamValue(key: string) {
|
getParamValue(key: string) {
|
||||||
if (this.paramsSchema) {
|
if (this.paramsSchema) {
|
||||||
const params = this.schemaService.cleanParams(
|
const params = cleanParams(
|
||||||
this.paramsSchema,
|
this.paramsSchema,
|
||||||
this.paramsFormInfo?.formGroup.value,
|
this.paramsFormInfo?.formGroup.value,
|
||||||
);
|
);
|
||||||
return params[key];
|
return params[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -286,97 +286,7 @@ export class SchemaService {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormInfoFromParamsSchema(schema: SomeJSONSchema): ParamsFormInfo {
|
|
||||||
const paramsFormInfo: ParamsFormInfo = {
|
|
||||||
formGroup: new FormGroup({}),
|
|
||||||
paramsInfo: {},
|
|
||||||
};
|
|
||||||
if (schema?.properties) {
|
|
||||||
const paramKeys = Object.keys(schema.properties);
|
|
||||||
Object.entries(schema.properties).forEach(([paramKey, paramSchema]: [string, any]) => {
|
|
||||||
if (paramSchema.type) {
|
|
||||||
switch (paramSchema.type) {
|
|
||||||
case 'string':
|
|
||||||
case 'number':
|
|
||||||
case 'integer':
|
|
||||||
case 'boolean':
|
|
||||||
case 'array': // TODO(jwetzell): actually handle arrays
|
|
||||||
case 'object': // TODO(jwetzell): actually handle objects
|
|
||||||
let formDefault = '';
|
|
||||||
const validators: ValidatorFn[] = [];
|
|
||||||
|
|
||||||
// NOTE(jwetzell): check for a default value to set
|
|
||||||
if (paramSchema.const) {
|
|
||||||
formDefault = paramSchema.const;
|
|
||||||
} else if (paramSchema.default) {
|
|
||||||
formDefault = paramSchema.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(jwetzell): add as many validators as we can
|
|
||||||
if (paramSchema.minimum) {
|
|
||||||
validators.push(Validators.min(paramSchema.minimum));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paramSchema.maximum) {
|
|
||||||
validators.push(Validators.max(paramSchema.maximum));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paramSchema.pattern) {
|
|
||||||
validators.push(Validators.pattern(new RegExp(paramSchema.pattern)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schema.required) {
|
|
||||||
if (schema.required.includes(paramKey)) {
|
|
||||||
validators.push(Validators.required);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsFormInfo.paramsInfo[paramKey] = {
|
|
||||||
key: paramKey,
|
|
||||||
display: paramSchema.title ? paramSchema.title : paramKey,
|
|
||||||
type: paramSchema.type,
|
|
||||||
hint: paramSchema.description,
|
|
||||||
isConst: !!paramSchema.const,
|
|
||||||
schema: paramSchema,
|
|
||||||
placeholder: '',
|
|
||||||
default: paramSchema.default ? paramSchema.default : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (paramSchema.examples && paramSchema.examples.length > 0) {
|
|
||||||
paramsFormInfo.paramsInfo[paramKey].placeholder = paramSchema.examples[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paramSchema.type === 'object') {
|
|
||||||
validators.push(this.objectValidator);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO(jwetzell): figure out how to disable a control but not have to deal with undefined values on disabled controls
|
|
||||||
paramsFormInfo.formGroup.addControl(
|
|
||||||
paramKey,
|
|
||||||
new FormControl(formDefault, validators),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (paramSchema.enum) {
|
|
||||||
paramsFormInfo.paramsInfo[paramKey].options = paramSchema.enum;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error(
|
|
||||||
`schema-service: unhandled param schema type for form group = ${paramSchema.type}`,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('schema-service: param property without type');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error('trigger-form: params schema without properties');
|
|
||||||
console.error(schema);
|
|
||||||
}
|
|
||||||
return paramsFormInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanArray(values: any[], itemSchema: SomeJSONSchema) {
|
cleanArray(values: any[], itemSchema: SomeJSONSchema) {
|
||||||
if (Array.isArray(values)) {
|
if (Array.isArray(values)) {
|
||||||
@@ -397,117 +307,6 @@ export class SchemaService {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanParams(paramsSchema: SomeJSONSchema, params: any): any {
|
|
||||||
Object.keys(params).forEach((paramKey) => {
|
|
||||||
if (paramsSchema.properties[paramKey]) {
|
|
||||||
const paramSchema = paramsSchema.properties[paramKey];
|
|
||||||
|
|
||||||
// delete null/undefined/empty params that aren't required
|
|
||||||
if (
|
|
||||||
params[paramKey] === undefined ||
|
|
||||||
params[paramKey] === null ||
|
|
||||||
params[paramKey] === ''
|
|
||||||
) {
|
|
||||||
if (paramSchema.required) {
|
|
||||||
if (!paramSchema.includes(paramKey)) {
|
|
||||||
delete params[paramKey];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delete params[paramKey];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paramSchema.type) {
|
|
||||||
switch (paramSchema.type) {
|
|
||||||
case 'integer':
|
|
||||||
var paramValue = parseInt(params[paramKey]);
|
|
||||||
// NOTE(jwetzell): delete non-numbers
|
|
||||||
if (Number.isNaN(paramValue)) {
|
|
||||||
delete params[paramKey];
|
|
||||||
} else {
|
|
||||||
params[paramKey] = paramValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
var paramValue = parseFloat(params[paramKey]);
|
|
||||||
// NOTE(jwetzell): delete non-numbers
|
|
||||||
if (Number.isNaN(paramValue)) {
|
|
||||||
delete params[paramKey];
|
|
||||||
} else {
|
|
||||||
params[paramKey] = paramValue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'array':
|
|
||||||
if (!Array.isArray(params[paramKey])) {
|
|
||||||
const paramValue = params[paramKey];
|
|
||||||
params[paramKey] = this.parseStringToArray(paramValue, paramSchema);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'object':
|
|
||||||
try {
|
|
||||||
params[paramKey] = JSON.parse(params[paramKey]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('object param is not JSON');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'string':
|
|
||||||
// string is default so nothing needs to happen to clean it's value
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(`schema-service: unhandled param schema type: ${paramSchema.type}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
parseStringToArray(value: any, schema: SomeJSONSchema): any[] | undefined {
|
|
||||||
if (!Array.isArray(value)) {
|
|
||||||
const paramValue = value;
|
|
||||||
if (paramValue === undefined || paramValue.trim().length === 0) {
|
|
||||||
value = [];
|
|
||||||
} else {
|
|
||||||
if (schema.items?.type) {
|
|
||||||
if (schema.items?.type === 'integer') {
|
|
||||||
return paramValue
|
|
||||||
.split(',')
|
|
||||||
.map((part: string) => part.trim())
|
|
||||||
.map((item: any) => parseInt(item));
|
|
||||||
} else if (schema.items?.type === 'number') {
|
|
||||||
return paramValue
|
|
||||||
.split(',')
|
|
||||||
.map((part: string) => part.trim())
|
|
||||||
.map((item: any) => parseFloat(item));
|
|
||||||
} else if (schema.items?.type === 'string') {
|
|
||||||
return paramValue.split(',').map((part: string) => part.trim());
|
|
||||||
} else if (schema.items?.type === 'object') {
|
|
||||||
// TODO(jwetzell): this seems gross, not sure if this covers everything
|
|
||||||
return JSON.parse(`[${paramValue}]`);
|
|
||||||
} else {
|
|
||||||
console.error(`schema-service: unhandled array schema type: ${schema.items?.type}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// NOTE(jwetzell): default to comma-separated strings
|
|
||||||
return paramValue.split(',').map((part: string) => part.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
objectValidator(control: AbstractControl): ValidationErrors | null {
|
|
||||||
try {
|
|
||||||
JSON.parse(control.value);
|
|
||||||
return null;
|
|
||||||
} catch (error) {
|
|
||||||
return { json: true };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonValidator(validateSchema: SomeJSONSchema) {
|
jsonValidator(validateSchema: SomeJSONSchema) {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
return (control: AbstractControl): ValidationErrors | null => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
import { FormGroup, ValidatorFn, Validators, FormControl, AbstractControl, ValidationErrors } from "@angular/forms";
|
||||||
|
import { SomeJSONSchema } from "ajv/dist/types/json-schema";
|
||||||
|
import { ParamsFormInfo } from "../models/form.model";
|
||||||
|
|
||||||
|
export function schemaToParamsFormInfo(schema: SomeJSONSchema): ParamsFormInfo {
|
||||||
|
const paramsFormInfo: ParamsFormInfo = {
|
||||||
|
formGroup: new FormGroup({}),
|
||||||
|
paramsInfo: {},
|
||||||
|
};
|
||||||
|
if (schema?.properties) {
|
||||||
|
const paramKeys = Object.keys(schema.properties);
|
||||||
|
Object.entries(schema.properties).forEach(([paramKey, paramSchema]: [string, any]) => {
|
||||||
|
if (paramSchema.type) {
|
||||||
|
switch (paramSchema.type) {
|
||||||
|
case 'string':
|
||||||
|
case 'number':
|
||||||
|
case 'integer':
|
||||||
|
case 'boolean':
|
||||||
|
case 'array': // TODO(jwetzell): actually handle arrays
|
||||||
|
case 'object': // TODO(jwetzell): actually handle objects
|
||||||
|
let formDefault = '';
|
||||||
|
const validators: ValidatorFn[] = [];
|
||||||
|
|
||||||
|
// NOTE(jwetzell): check for a default value to set
|
||||||
|
if (paramSchema.const) {
|
||||||
|
formDefault = paramSchema.const;
|
||||||
|
} else if (paramSchema.default) {
|
||||||
|
formDefault = paramSchema.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(jwetzell): add as many validators as we can
|
||||||
|
if (paramSchema.minimum) {
|
||||||
|
validators.push(Validators.min(paramSchema.minimum));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramSchema.maximum) {
|
||||||
|
validators.push(Validators.max(paramSchema.maximum));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramSchema.pattern) {
|
||||||
|
validators.push(Validators.pattern(new RegExp(paramSchema.pattern)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.required) {
|
||||||
|
if (schema.required.includes(paramKey)) {
|
||||||
|
validators.push(Validators.required);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paramsFormInfo.paramsInfo[paramKey] = {
|
||||||
|
key: paramKey,
|
||||||
|
display: paramSchema.title ? paramSchema.title : paramKey,
|
||||||
|
type: paramSchema.type,
|
||||||
|
hint: paramSchema.description,
|
||||||
|
isConst: !!paramSchema.const,
|
||||||
|
schema: paramSchema,
|
||||||
|
placeholder: '',
|
||||||
|
default: paramSchema.default ? paramSchema.default : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (paramSchema.examples && paramSchema.examples.length > 0) {
|
||||||
|
paramsFormInfo.paramsInfo[paramKey].placeholder = paramSchema.examples[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramSchema.type === 'object') {
|
||||||
|
validators.push(objectValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO(jwetzell): figure out how to disable a control but not have to deal with undefined values on disabled controls
|
||||||
|
paramsFormInfo.formGroup.addControl(
|
||||||
|
paramKey,
|
||||||
|
new FormControl(formDefault, validators),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (paramSchema.enum) {
|
||||||
|
paramsFormInfo.paramsInfo[paramKey].options = paramSchema.enum;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(
|
||||||
|
`schema-service: unhandled param schema type for form group = ${paramSchema.type}`,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('schema-service: param property without type');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('trigger-form: params schema without properties');
|
||||||
|
console.error(schema);
|
||||||
|
}
|
||||||
|
return paramsFormInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanParams(paramsSchema: SomeJSONSchema, params: any): any {
|
||||||
|
Object.keys(params).forEach((paramKey) => {
|
||||||
|
if (paramsSchema.properties[paramKey]) {
|
||||||
|
const paramSchema = paramsSchema.properties[paramKey];
|
||||||
|
|
||||||
|
// delete null/undefined/empty params that aren't required
|
||||||
|
if (
|
||||||
|
params[paramKey] === undefined ||
|
||||||
|
params[paramKey] === null ||
|
||||||
|
params[paramKey] === ''
|
||||||
|
) {
|
||||||
|
if (paramSchema.required) {
|
||||||
|
if (!paramSchema.includes(paramKey)) {
|
||||||
|
delete params[paramKey];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete params[paramKey];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramSchema.type) {
|
||||||
|
switch (paramSchema.type) {
|
||||||
|
case 'integer':
|
||||||
|
var paramValue = parseInt(params[paramKey]);
|
||||||
|
// NOTE(jwetzell): delete non-numbers
|
||||||
|
if (Number.isNaN(paramValue)) {
|
||||||
|
delete params[paramKey];
|
||||||
|
} else {
|
||||||
|
params[paramKey] = paramValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
var paramValue = parseFloat(params[paramKey]);
|
||||||
|
// NOTE(jwetzell): delete non-numbers
|
||||||
|
if (Number.isNaN(paramValue)) {
|
||||||
|
delete params[paramKey];
|
||||||
|
} else {
|
||||||
|
params[paramKey] = paramValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
if (!Array.isArray(params[paramKey])) {
|
||||||
|
const paramValue = params[paramKey];
|
||||||
|
params[paramKey] = parseStringToArray(paramValue, paramSchema);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'object':
|
||||||
|
try {
|
||||||
|
params[paramKey] = JSON.parse(params[paramKey]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('object param is not JSON');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
// string is default so nothing needs to happen to clean it's value
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(`schema-service: unhandled param schema type: ${paramSchema.type}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectValidator(control: AbstractControl): ValidationErrors | null {
|
||||||
|
try {
|
||||||
|
JSON.parse(control.value);
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
return { json: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseStringToArray(value: any, schema: SomeJSONSchema): any[] | undefined {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
const paramValue = value;
|
||||||
|
if (paramValue === undefined || paramValue.trim().length === 0) {
|
||||||
|
value = [];
|
||||||
|
} else {
|
||||||
|
if (schema.items?.type) {
|
||||||
|
if (schema.items?.type === 'integer') {
|
||||||
|
return paramValue
|
||||||
|
.split(',')
|
||||||
|
.map((part: string) => part.trim())
|
||||||
|
.map((item: any) => parseInt(item));
|
||||||
|
} else if (schema.items?.type === 'number') {
|
||||||
|
return paramValue
|
||||||
|
.split(',')
|
||||||
|
.map((part: string) => part.trim())
|
||||||
|
.map((item: any) => parseFloat(item));
|
||||||
|
} else if (schema.items?.type === 'string') {
|
||||||
|
return paramValue.split(',').map((part: string) => part.trim());
|
||||||
|
} else if (schema.items?.type === 'object') {
|
||||||
|
// TODO(jwetzell): this seems gross, not sure if this covers everything
|
||||||
|
return JSON.parse(`[${paramValue}]`);
|
||||||
|
} else {
|
||||||
|
console.error(`schema-service: unhandled array schema type: ${schema.items?.type}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// NOTE(jwetzell): default to comma-separated strings
|
||||||
|
return paramValue.split(',').map((part: string) => part.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user