diff --git a/CHANGELOG.md b/CHANGELOG.md index efb44b4..6a8ac73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Hook useSaveWithGlobalRewrite() ([#161](https://github.com/i-VRESSE/workflow-builder/pull/161)) +* Support for if/then/else in JSON schema ([#160](https://github.com/i-VRESSE/workflow-builder/pull/160)) ## @i-vresse/wb-core 3.0.1 - 2024-05-23 diff --git a/docs/schema.md b/docs/schema.md index 4e96fc8..55d9adf 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -131,3 +131,27 @@ and `abcd.pdb` file has chain A and B with residues 1, 2, 3 and 4. Then the form will have restricted the `chain` prop to only allow `A` and `B` and will have restricted the sta and end prop to only allow 1, 2, 3 and 4. + +## If then else + +The `if`, `then` and `else` keywords can be used to [conditionally apply a schema](https://json-schema.org/understanding-json-schema/reference/conditionals#ifthenelse). + +For example to have the `foo` property only if the `bar` property is false use: + +```yaml + type: object + properties: + bar: + type: boolean + if: + properties: + bar: + const: true + then: {} + else: + properties: + foo: + type: string +``` + +Only supports simple const condition with one or more properties and not complex conditions like patterns. Also only a single if/tnen/else block per object is supported. It can be combined with [groups](uiSchema.md#uigroup). diff --git a/packages/core/src/grouper.test.ts b/packages/core/src/grouper.test.ts index 145e475..1d27e23 100644 --- a/packages/core/src/grouper.test.ts +++ b/packages/core/src/grouper.test.ts @@ -1,7 +1,13 @@ -import { expect, describe, it, beforeEach } from 'vitest' +import { expect, describe, it, beforeEach, assert } from 'vitest' import { JSONSchema7 } from 'json-schema' import { UiSchema } from '@rjsf/core' -import { groupCatalog, groupParameters, groupSchema, groupUiSchema, unGroupParameters } from './grouper' +import { + groupCatalog, + groupParameters, + groupSchema, + groupUiSchema, + unGroupParameters +} from './grouper' import { ICatalog, IParameters } from './types' function deepCopy (value: T): T { @@ -288,18 +294,22 @@ describe('given a schema with a 2 props with ui:group in uiSchema and 1 without' schema, uiSchema }, - categories: [{ - name: 'category1', - description: 'Category 1' - }], - nodes: [{ - schema, - uiSchema, - id: 'node1', - label: 'Node 1', - description: 'Description 1', - category: 'category1' - }], + categories: [ + { + name: 'category1', + description: 'Category 1' + } + ], + nodes: [ + { + schema, + uiSchema, + id: 'node1', + label: 'Node 1', + description: 'Description 1', + category: 'category1' + } + ], examples: {} } @@ -342,20 +352,24 @@ describe('given a schema with a 2 props with ui:group in uiSchema and 1 without' formSchema: expectedSchema, formUiSchema: expecteduiSchema }, - categories: [{ - name: 'category1', - description: 'Category 1' - }], - nodes: [{ - schema, - uiSchema, - formSchema: expectedSchema, - formUiSchema: expecteduiSchema, - id: 'node1', - label: 'Node 1', - description: 'Description 1', - category: 'category1' - }], + categories: [ + { + name: 'category1', + description: 'Category 1' + } + ], + nodes: [ + { + schema, + uiSchema, + formSchema: expectedSchema, + formUiSchema: expecteduiSchema, + id: 'node1', + label: 'Node 1', + description: 'Description 1', + category: 'category1' + } + ], examples: {} } expect(actual).toEqual(expected) @@ -499,7 +513,9 @@ describe('given a un-grouped prop with same name as group', () => { } } - expect(() => groupSchema(schema, uiSchema)).toThrow('Can not have group and un-grouped parameter with same name prop2') + expect(() => groupSchema(schema, uiSchema)).toThrow( + 'Can not have group and un-grouped parameter with same name prop2' + ) }) }) }) @@ -633,3 +649,277 @@ describe('given a prop with same name as group and another prop in same group', }) }) }) + +describe('given a schema with if/then/else', () => { + let schema: JSONSchema7 + let uiSchema: UiSchema + + describe('else in same group', () => { + beforeEach(() => { + schema = { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + additionalProperties: false, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: {}, + else: { + properties: { + prop2: { + type: 'string' + } + } + } + } + uiSchema = { + prop1: { + 'ui:group': 'group1' + }, + prop2: { + 'ui:group': 'group1' + } + } + }) + + describe('groupSchema()', () => { + it('should move properties inside an object with group name as key', () => { + const groupedSchema = groupSchema(schema, uiSchema) + + const expectedSchema: JSONSchema7 = { + type: 'object', + properties: { + group1: { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + additionalProperties: false, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: {}, + else: { + properties: { + prop2: { + type: 'string' + } + } + } + } + }, + additionalProperties: false + } + expect(groupedSchema).toEqual(expectedSchema) + }) + }) + }) + + describe('then in same group', () => { + beforeEach(() => { + schema = { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + additionalProperties: false, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + properties: { + prop2: { + type: 'string' + } + } + }, + else: {} + } + uiSchema = { + prop1: { + 'ui:group': 'group1' + }, + prop2: { + 'ui:group': 'group1' + } + } + }) + + describe('groupSchema()', () => { + it('should move properties inside an object with group name as key', () => { + const groupedSchema = groupSchema(schema, uiSchema) + + const expectedSchema: JSONSchema7 = { + type: 'object', + properties: { + group1: { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + additionalProperties: false, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + properties: { + prop2: { + type: 'string' + } + } + }, + else: {} + } + }, + additionalProperties: false + } + expect(groupedSchema).toEqual(expectedSchema) + }) + }) + }) + + describe.each<{ label: string, schema: JSONSchema7, uiSchema: UiSchema }>([ + { + label: 'if and else in different group', + schema: { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + additionalProperties: false, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + }, + else: { + properties: { + prop2: { + type: 'string' + } + } + } + }, + uiSchema: { + prop1: { + 'ui:group': 'group1' + }, + prop2: { + 'ui:group': 'group2' + } + } + }, + { + label: 'if and then in different group', + schema: { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + additionalProperties: false, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + properties: { + prop2: { + type: 'string' + } + } + }, + else: {} + }, + uiSchema: { + prop1: { + 'ui:group': 'group1' + }, + prop2: { + 'ui:group': 'group2' + } + } + }, + { + label: 'if not in group', + schema: { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + additionalProperties: false, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + properties: { + prop2: { + type: 'string' + } + } + }, + else: {} + }, + uiSchema: { + prop2: { + 'ui:group': 'group2' + } + } + } + ])('$label', ({ schema, uiSchema }) => { + it('groupSchema() should throw error', () => { + assert.throws(() => { + groupSchema(schema, uiSchema) + }, 'Cannot have an if in one group and a then/else in another group') + }) + }) +}) diff --git a/packages/core/src/grouper.ts b/packages/core/src/grouper.ts index 7c1e2e5..3a54a2b 100644 --- a/packages/core/src/grouper.ts +++ b/packages/core/src/grouper.ts @@ -18,6 +18,67 @@ import { ICatalog, IParameters } from './types' import { JSONSchema7 } from 'json-schema' import { isObject } from './utils/isObject' +function groupOfIfProp (schema: JSONSchema7, uiSchema: UiSchema): string | undefined { + if (!(schema.if !== undefined && + typeof schema.if !== 'boolean' && + (schema.if.properties != null))) { + return undefined + } + // TODO handle multiple props in schema.if + const ifPropName = Object.keys(schema.if.properties)[0] + if (ifPropName in uiSchema && 'ui:group' in uiSchema[ifPropName]) { + return uiSchema[ifPropName]['ui:group'] + } + return undefined +} + +function groupSchemaCondition ({ + k, + v, + group, + newSchema, + uiSchema, + newGroup +}: { + k: string + v: UiSchema + group: string + newSchema: JSONSchema7 + uiSchema: UiSchema + newGroup: JSONSchema7 +}): boolean { + // TODO handle when if is in group but then/else is not + // now treated as normal prop instead of conditional + const elseInGroup = + newSchema.else !== undefined && + typeof newSchema.else !== 'boolean' && + (newSchema.else.properties != null) && + k in newSchema.else.properties + const thenInGroup = + newSchema.then !== undefined && + typeof newSchema.then !== 'boolean' && + (newSchema.then.properties != null) && + k in newSchema.then.properties + if (thenInGroup || elseInGroup) { + const ifGroup = groupOfIfProp(newSchema, uiSchema) + if (ifGroup !== group) { + throw new Error( + 'Cannot have an if in one group and a then/else in another group' + ) + } + newGroup.if = newSchema.if + newGroup.then = newSchema.then + newGroup.else = newSchema.else + /* eslint-disable @typescript-eslint/no-dynamic-delete */ + delete newSchema.if + delete newSchema.then + delete newSchema.else + /* eslint-enable @typescript-eslint/no-dynamic-delete */ + return true + } + return false +} + /** * Create new JSON schema where properties marked with same `ui:group` are grouped together into an object. * @@ -41,7 +102,9 @@ export function groupSchema ( Object.keys(uiSchema).filter((k) => 'ui:group' in uiSchema[k]) ) const allProps = new Set(Object.keys(schema.properties ?? {})) - const grouplessProps = new Set([...allProps].filter((x) => !propsWithGroup.has(x))) + const grouplessProps = new Set( + [...allProps].filter((x) => !propsWithGroup.has(x)) + ) const grouplessPropsWithSameNameAsGroup = new Set( [...grouplessProps].filter((x) => definedGroups.has(x)) ) @@ -53,12 +116,18 @@ export function groupSchema ( ) } - if (!('properties' in newSchema && typeof newSchema.properties === 'object')) { + if ( + !('properties' in newSchema && typeof newSchema.properties === 'object') + ) { return newSchema } // prop with group and same name as any group should be nested first - const propsWithSameNameAsAnyGroup = new Set(Object.entries(uiSchema).filter(([k, v]) => 'ui:group' in v && definedGroups.has(k)).map((d) => d[0])) + const propsWithSameNameAsAnyGroup = new Set( + Object.entries(uiSchema) + .filter(([k, v]) => 'ui:group' in v && definedGroups.has(k)) + .map((d) => d[0]) + ) for (const k of propsWithSameNameAsAnyGroup) { const prop = newSchema.properties[k] /* eslint-disable @typescript-eslint/no-dynamic-delete */ @@ -78,7 +147,9 @@ export function groupSchema ( // TODO recursivly, now only loops over first direct props if ('ui:group' in v && !propsWithSameNameAsAnyGroup.has(k)) { const group = v['ui:group'] - if (!('properties' in newSchema && typeof newSchema.properties === 'object')) { + if ( + !('properties' in newSchema && typeof newSchema.properties === 'object') + ) { throw new Error('Schema must have properties') } if (!(group in newSchema.properties)) { @@ -92,6 +163,21 @@ export function groupSchema ( if (typeof newGroup === 'boolean' || newGroup.properties === undefined) { return } + if ( + groupSchemaCondition({ + k, + v, + group, + newSchema, + uiSchema, + newGroup + }) + ) { + // If k was a then or else prop then it is has been moved to the newGroup as if/then/else + // no need to move it as property + return + } + newGroup.properties[k] = newSchema.properties[k] // Remove k as it now is in the group /* eslint-disable @typescript-eslint/no-dynamic-delete */ diff --git a/packages/core/src/pruner.test.ts b/packages/core/src/pruner.test.ts index 1e346aa..eaf7e3d 100644 --- a/packages/core/src/pruner.test.ts +++ b/packages/core/src/pruner.test.ts @@ -640,4 +640,219 @@ describe('pruneDefaults()', () => { expect(result).toStrictEqual(expected) }) }) + + describe('if/then/else', () => { + describe('given if is true and has else parameter', () => { + it('should remove else parameter', () => { + const parameters = { + prop1: 'val1', + prop2: 'someval' + } + const schema: JSONSchema7 = { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + }, + else: { + properties: { + prop2: { + type: 'string' + } + } + }, + additionalProperties: false + } + + const result = pruneDefaults(parameters, schema, true) + const expected = { + prop1: 'val1' + } + expect(result).toStrictEqual(expected) + }) + }) + + describe('given if is false and has then parameter', () => { + it('should remove then parameter', () => { + const parameters = { + prop1: 'val2', + prop2: 'someval' + } + const schema: JSONSchema7 = { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + properties: { + prop2: { + type: 'string' + } + } + }, + else: { + }, + additionalProperties: false + } + + const result = pruneDefaults(parameters, schema, true) + const expected = { + prop1: 'val2' + } + expect(result).toStrictEqual(expected) + }) + }) + + describe('given if is true and has then and else parameter', () => { + it('should remove else parameter, but keep then', () => { + const parameters = { + prop1: 'val1', + prop2: 'someval', + prop3: 'someval3' + } + const schema: JSONSchema7 = { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + properties: { + prop3: { + type: 'string' + } + } + }, + else: { + properties: { + prop2: { + type: 'string' + } + } + }, + additionalProperties: false + } + + const result = pruneDefaults(parameters, schema, true) + const expected = { + prop1: 'val1', + prop3: 'someval3' + } + expect(result).toStrictEqual(expected) + }) + }) + + describe('given if is false and has else parameter which is same as default', () => { + it('should remove else parameter', () => { + const parameters = { + prop1: 'val2', + prop2: 'someval' + } + const schema: JSONSchema7 = { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + }, + else: { + properties: { + prop2: { + type: 'string', + default: 'someval' + } + } + }, + additionalProperties: false + } + + const result = pruneDefaults(parameters, schema, true) + const expected = { + prop1: 'val2' + } + expect(result).toStrictEqual(expected) + }) + }) + + describe('given if is true and has then parameter which is same as default', () => { + it('should remove then parameter', () => { + const parameters = { + prop1: 'val1', + prop2: 'someval' + } + const schema: JSONSchema7 = { + type: 'object', + properties: { + prop1: { + type: 'string', + enum: ['val1', 'val2'] + } + }, + if: { + properties: { + prop1: { + const: 'val1' + } + } + }, + then: { + properties: { + prop2: { + type: 'string', + default: 'someval' + } + } + }, + else: {}, + additionalProperties: false + } + + const result = pruneDefaults(parameters, schema, true) + const expected = { + prop1: 'val1' + } + expect(result).toStrictEqual(expected) + }) + }) + }) }) diff --git a/packages/core/src/pruner.ts b/packages/core/src/pruner.ts index 77c408c..1d62bc2 100644 --- a/packages/core/src/pruner.ts +++ b/packages/core/src/pruner.ts @@ -72,5 +72,63 @@ export function pruneDefaults (parameters: IParameters, schema: JSONSchema7, res newParameters[k] = v } }) - return newParameters + return pruneThenElses(newParameters, schema) +} + +function pruneThenElses (parameters: IParameters, schema: JSONSchema7): IParameters { + if (!(schema.if !== undefined && typeof schema.if !== 'boolean' && schema.if !== undefined && schema.if.properties !== undefined)) { + // no if then bail out + return parameters + } + + const condition = Object.entries(schema.if.properties).every( + ([k, sv]) => typeof sv === 'object' && sv.const !== undefined && sv.const === parameters[k] + ) + if (condition) { + // Remove else parameters + if (schema.else !== undefined && typeof schema.else !== 'boolean' && schema.else !== undefined && schema.else.properties !== undefined) { + const elsePropnames = Object.keys(schema.else.properties) + for (const k of elsePropnames) { + /* eslint-disable @typescript-eslint/no-dynamic-delete */ + delete parameters[k] + /* eslint-enable @typescript-eslint/no-dynamic-delete */ + } + } + // Remove then parameters that are same as default + if (schema.then !== undefined && typeof schema.then !== 'boolean' && schema.then !== undefined && schema.then.properties !== undefined) { + const thenPropnames = Object.keys(schema.then.properties) + for (const k of thenPropnames) { + const p = schema.then.properties[k] + if (typeof p === 'object' && p.default === parameters[k]) { + /* eslint-disable @typescript-eslint/no-dynamic-delete */ + delete parameters[k] + /* eslint-enable @typescript-eslint/no-dynamic-delete */ + } + } + } + } else { + // Remove then parameters + if (schema.then !== undefined && typeof schema.then !== 'boolean' && schema.then !== undefined && schema.then.properties !== undefined) { + const thenPropnames = Object.keys(schema.then.properties) + for (const k of thenPropnames) { + /* eslint-disable @typescript-eslint/no-dynamic-delete */ + delete parameters[k] + /* eslint-enable @typescript-eslint/no-dynamic-delete */ + } + } + // Remove else parameters that are same as default + if (schema.else !== undefined && typeof schema.else !== 'boolean' && schema.else !== undefined && schema.else.properties !== undefined) { + const elsePropnames = Object.keys(schema.else.properties) + for (const k of elsePropnames) { + const p = schema.else.properties[k] + if (typeof p === 'object' && p.default === parameters[k]) { + /* eslint-disable @typescript-eslint/no-dynamic-delete */ + delete parameters[k] + /* eslint-enable @typescript-eslint/no-dynamic-delete */ + } + } + } + } + + return parameters } diff --git a/packages/form/src/CollapsibleField.tsx b/packages/form/src/CollapsibleField.tsx index c8fdf45..f4cf7b9 100644 --- a/packages/form/src/CollapsibleField.tsx +++ b/packages/form/src/CollapsibleField.tsx @@ -97,7 +97,7 @@ export const CollapsibleField = (props: FieldProps): JSX.Element => { {title} {/* error icon */} - {hasError ? : null} + {hasError ?
: null} {/* show content when not collapsed */} {/* TitleField inside the ObjectField is not rendered when there is no title or name */} diff --git a/packages/haddock3_catalog/generate_haddock3_catalog.py b/packages/haddock3_catalog/generate_haddock3_catalog.py index 02cedcf..a4991b9 100755 --- a/packages/haddock3_catalog/generate_haddock3_catalog.py +++ b/packages/haddock3_catalog/generate_haddock3_catalog.py @@ -109,6 +109,7 @@ def config2schema(config): required = [] collapsed_config = collapse_expandable(config) + ifthenelses = {} for k, v in collapsed_config.items(): prop = {} prop_ui = {} @@ -124,6 +125,23 @@ def config2schema(config): prop['description'] = v['short'] if 'long' in v and v['long'] != 'No long description yet': prop['$comment'] = v['long'] + if 'incompatible' in v: + ifthenelse = { + "if": { + "properties": {} + }, + "then": {}, + "else": { + "properties": { + k: prop + } + } + } + for ik, iv in v['incompatible'].items(): + ifthenelse['if']['properties'][ik] = { + "const": iv + } + ifthenelses[k] = ifthenelse if 'type' not in v: # if not type field treat value as dict of dicts # TODO instead of removing group and explevel from mol1.prot_segid dict do proper filtering and support group recursivly @@ -336,7 +354,8 @@ def config2schema(config): raise ValueError(f"Don't know how to determine type of items of {v}") else: raise ValueError(f"Don't know what to do with {k}:{v}") - properties[k] = prop + if k not in ifthenelses: + properties[k] = prop if 'group' in v and v['group'] != '' and v['group'] is not None: prop_ui['ui:group'] = v['group'] if prop_ui: @@ -349,6 +368,10 @@ def config2schema(config): "required": required, "additionalProperties": False } + if ifthenelses: + if len(ifthenelses) > 1: + raise ValueError(f"Only one ifthenelse is supported, but got {len(ifthenelses)}") + schema.update(list(ifthenelses.values())[0]) return { "schema": schema, "uiSchema": uiSchema, diff --git a/packages/haddock3_catalog/public/catalog/haddock3.easy.yaml b/packages/haddock3_catalog/public/catalog/haddock3.easy.yaml index 92f0e37..fd9c5e5 100644 --- a/packages/haddock3_catalog/public/catalog/haddock3.easy.yaml +++ b/packages/haddock3_catalog/public/catalog/haddock3.easy.yaml @@ -163,17 +163,24 @@ global: this parameter to `true` will add the javascript library in generated files, therefore completely isolating haddock3 from any web call. type: boolean - less_io: - default: false - title: Reduce the amount of I/O operations. - description: Reduce the amount of I/O operations. - $comment: This option will reduce the amount of I/O operations by writing - less files to disk. This can be useful for example when running on a network - file system where I/O operations are slow. - type: boolean required: - run_dir additionalProperties: false + if: + properties: + mode: + const: batch + then: {} + else: + properties: + less_io: + default: false + title: Reduce the amount of I/O operations. + description: Reduce the amount of I/O operations. + $comment: This option will reduce the amount of I/O operations by writing + less files to disk. This can be useful for example when running on a network + file system where I/O operations are slow. + type: boolean uiSchema: molecules: items: diff --git a/packages/haddock3_catalog/public/catalog/haddock3.expert.yaml b/packages/haddock3_catalog/public/catalog/haddock3.expert.yaml index 3d4af04..dc28ad7 100644 --- a/packages/haddock3_catalog/public/catalog/haddock3.expert.yaml +++ b/packages/haddock3_catalog/public/catalog/haddock3.expert.yaml @@ -173,17 +173,24 @@ global: this parameter to `true` will add the javascript library in generated files, therefore completely isolating haddock3 from any web call. type: boolean - less_io: - default: false - title: Reduce the amount of I/O operations. - description: Reduce the amount of I/O operations. - $comment: This option will reduce the amount of I/O operations by writing - less files to disk. This can be useful for example when running on a network - file system where I/O operations are slow. - type: boolean required: - run_dir additionalProperties: false + if: + properties: + mode: + const: batch + then: {} + else: + properties: + less_io: + default: false + title: Reduce the amount of I/O operations. + description: Reduce the amount of I/O operations. + $comment: This option will reduce the amount of I/O operations by writing + less files to disk. This can be useful for example when running on a network + file system where I/O operations are slow. + type: boolean uiSchema: molecules: items: diff --git a/packages/haddock3_catalog/public/catalog/haddock3.guru.yaml b/packages/haddock3_catalog/public/catalog/haddock3.guru.yaml index 7676cf1..8d94561 100644 --- a/packages/haddock3_catalog/public/catalog/haddock3.guru.yaml +++ b/packages/haddock3_catalog/public/catalog/haddock3.guru.yaml @@ -183,17 +183,24 @@ global: this parameter to `true` will add the javascript library in generated files, therefore completely isolating haddock3 from any web call. type: boolean - less_io: - default: false - title: Reduce the amount of I/O operations. - description: Reduce the amount of I/O operations. - $comment: This option will reduce the amount of I/O operations by writing - less files to disk. This can be useful for example when running on a network - file system where I/O operations are slow. - type: boolean required: - run_dir additionalProperties: false + if: + properties: + mode: + const: batch + then: {} + else: + properties: + less_io: + default: false + title: Reduce the amount of I/O operations. + description: Reduce the amount of I/O operations. + $comment: This option will reduce the amount of I/O operations by writing + less files to disk. This can be useful for example when running on a network + file system where I/O operations are slow. + type: boolean uiSchema: molecules: items: