forked from KaotoIO/kaoto
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(canvasform): Use local SchemaBridge
Currently, in case a schema contains a `oneOf` array, `uniforms` combines all `oneOf` definitions in a single schema definition. The issue with this approach is that combine potentially non-compatible schemas, like the `errorHandler` one, since we need to specify a single property. This commit extends the `getField` method from the uniforms `JSONSchemaBridge` to add the `oneOf` definitions into the field, this way, we could use this information in the form to render an UI control to select a given schema relates: KaotoIO#948 relates: KaotoIO#560
- Loading branch information
Showing
12 changed files
with
392 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { AutoField } from '@kaoto-next/uniforms-patternfly'; | ||
import { ComponentType, createElement } from 'react'; | ||
import { useForm } from 'uniforms'; | ||
import { KaotoSchemaDefinition } from '../../models'; | ||
|
||
export type AutoFieldsProps = { | ||
autoField?: ComponentType<{ name: string }>; | ||
element?: ComponentType | string; | ||
fields?: string[]; | ||
omitFields?: string[]; | ||
}; | ||
|
||
export function CustomAutoFields({ | ||
autoField = AutoField, | ||
element = 'div', | ||
fields, | ||
omitFields = [], | ||
...props | ||
}: AutoFieldsProps) { | ||
const { schema } = useForm(); | ||
const rootField = schema.getField(''); | ||
|
||
/** Special handling for oneOf schemas */ | ||
if (Array.isArray((rootField as KaotoSchemaDefinition['schema']).oneOf)) { | ||
return createElement(element, props, [createElement(autoField!, { key: '', name: '' })]); | ||
} | ||
|
||
return createElement( | ||
element, | ||
props, | ||
(fields ?? schema.getSubfields()) | ||
.filter((field) => !omitFields!.includes(field)) | ||
.map((field) => createElement(autoField!, { key: field, name: field })), | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { csvDataFormatSchema } from '../../stubs/csv.dataformat'; | ||
import { errorHandlerSchema } from '../../stubs/error-handler'; | ||
import { SchemaBridge } from './schema-bridge'; | ||
|
||
describe('SchemaBridge', () => { | ||
let schemaBridge: SchemaBridge; | ||
|
||
it('error handler - should return `refErrorHandler` field', () => { | ||
schemaBridge = new SchemaBridge({ validator: () => null, schema: errorHandlerSchema }); | ||
schemaBridge.getSubfields(); | ||
Object.keys(errorHandlerSchema.properties!).forEach((key) => { | ||
schemaBridge.getSubfields(key); | ||
}); | ||
|
||
const field = schemaBridge.getField('refErrorHandler'); | ||
expect(field).toEqual({ | ||
description: 'References to an existing or custom error handler.', | ||
oneOf: [ | ||
{ type: 'string' }, | ||
{ | ||
additionalProperties: false, | ||
properties: { | ||
id: { description: 'The id of this node', title: 'Id', type: 'string' }, | ||
ref: { description: 'References to an existing or custom error handler.', title: 'Ref', type: 'string' }, | ||
}, | ||
type: 'object', | ||
}, | ||
], | ||
required: ['ref'], | ||
title: 'Ref Error Handler', | ||
}); | ||
}); | ||
|
||
describe('dataformat', () => { | ||
it('should return `header` field', () => { | ||
schemaBridge = new SchemaBridge({ validator: () => null, schema: csvDataFormatSchema }); | ||
schemaBridge.getSubfields(); | ||
Object.keys(csvDataFormatSchema.properties!).forEach((key) => { | ||
schemaBridge.getSubfields(key); | ||
}); | ||
|
||
const field = schemaBridge.getField('header'); | ||
expect(field).toEqual({ | ||
description: 'To configure the CSV headers', | ||
items: { | ||
type: 'string', | ||
}, | ||
title: 'Header', | ||
type: 'array', | ||
}); | ||
}); | ||
|
||
it('should return `header` initial value', () => { | ||
schemaBridge = new SchemaBridge({ validator: () => null, schema: csvDataFormatSchema }); | ||
schemaBridge.getSubfields(); | ||
Object.keys(csvDataFormatSchema.properties!).forEach((key) => { | ||
schemaBridge.getSubfields(key); | ||
}); | ||
|
||
const field = schemaBridge.getInitialValue('header'); | ||
expect(field).toEqual([]); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { JSONSchema4 } from 'json-schema'; | ||
import { joinName } from 'uniforms'; | ||
import { JSONSchemaBridge } from 'uniforms-bridge-json-schema'; | ||
import { resolveRefIfNeeded } from '../../utils'; | ||
import memoize from 'lodash/memoize'; | ||
|
||
export class SchemaBridge extends JSONSchemaBridge { | ||
/** | ||
* Regular expression to match the dot number or dot dollar sign in the field name | ||
* for example: `$.` or `.0` or `.1` or `.2` etc. | ||
*/ | ||
static DOT_NUMBEROR_OR_DOLLAR_REGEXP = /\.\d+|\.\$/; | ||
|
||
constructor(options: ConstructorParameters<typeof JSONSchemaBridge>[0]) { | ||
super(options); | ||
this.getField = memoize(this.getField.bind(this)); | ||
} | ||
|
||
getField(name: string): Record<string, unknown> { | ||
/** Process schemas in the parent class */ | ||
const originalField = super.getField(name); | ||
SchemaBridge.DOT_NUMBEROR_OR_DOLLAR_REGEXP.lastIndex = 0; | ||
if (SchemaBridge.DOT_NUMBEROR_OR_DOLLAR_REGEXP.test(name)) { | ||
return originalField; | ||
} | ||
|
||
const fieldNames = joinName(null, name); | ||
const field = fieldNames.reduce((definition, next, index, array) => { | ||
const prevName = joinName(array.slice(0, index)); | ||
|
||
if (definition.type === 'object') { | ||
definition = definition.properties[joinName.unescape(next)]; | ||
|
||
/** Enhance schemas with the definitions if available in the oneOf property */ | ||
if (Object.keys(definition).length === 0 && Array.isArray(this._compiledSchema[prevName].oneOf)) { | ||
let oneOfDefinition = this._compiledSchema[prevName].oneOf.find((oneOf: JSONSchema4) => { | ||
return Array.isArray(oneOf.required) && oneOf.required[0] === next; | ||
}).properties[next]; | ||
|
||
oneOfDefinition = resolveRefIfNeeded(oneOfDefinition, this.schema); | ||
Object.assign(definition, oneOfDefinition); | ||
} else if (definition.$ref) { | ||
/** Resolve $ref if needed */ | ||
Object.assign(definition, resolveRefIfNeeded(definition, this.schema)); | ||
} | ||
} | ||
|
||
return definition; | ||
}, this.schema); | ||
|
||
/** At this point, the super._compiledSchemas is populated, so we can return the enhanced field */ | ||
return field; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { useMemo } from 'react'; | ||
import { useForm } from 'uniforms'; | ||
import { KaotoSchemaDefinition } from '../models'; | ||
import { ROOT_PATH, getValue } from '../utils'; | ||
import { getAppliedSchemaIndex } from '../utils/get-applied-schema-index'; | ||
import { OneOfSchemas } from '../utils/get-oneof-schema-list'; | ||
import { useSchemaBridgeContext } from './schema-bridge.hook'; | ||
|
||
interface AppliedSchema { | ||
index: number; | ||
name: string; | ||
description?: string; | ||
schema: KaotoSchemaDefinition['schema']; | ||
model: Record<string, unknown>; | ||
} | ||
|
||
export const useAppliedSchema = (fieldName: string, oneOfSchemas: OneOfSchemas[]): AppliedSchema | undefined => { | ||
const form = useForm(); | ||
const { schemaBridge } = useSchemaBridgeContext(); | ||
|
||
const result = useMemo(() => { | ||
const currentModel = getValue(form.model, fieldName === '' ? ROOT_PATH : fieldName); | ||
|
||
const oneOfList = oneOfSchemas.map((oneOf) => oneOf.schema); | ||
const index = getAppliedSchemaIndex( | ||
currentModel, | ||
oneOfList, | ||
schemaBridge?.schema as KaotoSchemaDefinition['schema'], | ||
); | ||
if (index === -1) { | ||
return undefined; | ||
} | ||
|
||
const foundSchema = oneOfSchemas[index]; | ||
|
||
return { | ||
index, | ||
name: foundSchema.name, | ||
description: foundSchema.description, | ||
schema: foundSchema.schema, | ||
model: currentModel, | ||
}; | ||
}, [fieldName, form.model, oneOfSchemas, schemaBridge?.schema]); | ||
|
||
return result; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './applied-schema.hook'; | ||
export * from './entities'; | ||
export * from './local-storage.hook'; | ||
export * from './schema-bridge.hook'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,25 @@ | ||
import { FunctionComponent, PropsWithChildren, createContext, useMemo } from 'react'; | ||
import { FunctionComponent, PropsWithChildren, RefObject, createContext, useMemo } from 'react'; | ||
import { JSONSchemaBridge as SchemaBridge } from 'uniforms-bridge-json-schema'; | ||
import { SchemaService } from '../components/Form/schema.service'; | ||
import { KaotoSchemaDefinition } from '../models/kaoto-schema'; | ||
|
||
interface SchemaBridgeProviderProps { | ||
schema: KaotoSchemaDefinition['schema'] | undefined; | ||
parentRef?: RefObject<HTMLElement>; | ||
} | ||
|
||
export const SchemaBridgeContext = createContext<SchemaBridge | undefined>(undefined); | ||
export const SchemaBridgeContext = createContext<{ | ||
schemaBridge: SchemaBridge | undefined; | ||
parentRef: RefObject<HTMLElement> | null; | ||
}>({ schemaBridge: undefined, parentRef: null }); | ||
|
||
export const SchemaBridgeProvider: FunctionComponent<PropsWithChildren<SchemaBridgeProviderProps>> = (props) => { | ||
const schemaBridge = useMemo(() => { | ||
const value = useMemo(() => { | ||
const schemaService = new SchemaService(); | ||
const schemaBridge = schemaService.getSchemaBridge(props.schema); | ||
|
||
return schemaBridge; | ||
}, [props.schema]); | ||
return { schemaBridge, parentRef: props.parentRef ?? null }; | ||
}, [props.parentRef, props.schema]); | ||
|
||
return <SchemaBridgeContext.Provider value={schemaBridge}>{props.children}</SchemaBridgeContext.Provider>; | ||
return <SchemaBridgeContext.Provider value={value}>{props.children}</SchemaBridgeContext.Provider>; | ||
}; |
Oops, something went wrong.