From b1be3e2371608f276c3fc1ac14cbd7c6551921be Mon Sep 17 00:00:00 2001 From: Jordan Sanz Date: Tue, 16 Jan 2024 20:18:19 -0500 Subject: [PATCH] fix(): update subsetSchema (#1386) --- .../src/core/schemas/internal/subsetSchema.ts | 46 ++++- .../internal/filterSchemaToUiSchema.test.ts | 186 +++++++++++++----- 2 files changed, 180 insertions(+), 52 deletions(-) diff --git a/packages/common/src/core/schemas/internal/subsetSchema.ts b/packages/common/src/core/schemas/internal/subsetSchema.ts index 5241d985d02..a69c99c4ea9 100644 --- a/packages/common/src/core/schemas/internal/subsetSchema.ts +++ b/packages/common/src/core/schemas/internal/subsetSchema.ts @@ -6,6 +6,7 @@ import { IConfigurationSchema } from "../types"; * @param schema * @param props * @returns + * */ export function subsetSchema( schema: IConfigurationSchema, @@ -14,15 +15,44 @@ export function subsetSchema( const subset: IConfigurationSchema = cloneObject(schema); // 1. remove un-specified properties from the "required" array - subset.required = [...subset.required].filter((required) => - props.includes(required) - ); + if (subset.required) { + subset.required = [...subset.required].filter((required) => + props.includes(required) + ); + } // 2. filter the rest of the schema down to the specified properties - Object.keys(subset.properties).forEach((key) => { - if (props.indexOf(key) === -1) { - delete subset.properties[key]; - } - }); + if (subset.properties) { + Object.keys(subset.properties).forEach((key) => { + if (props.indexOf(key) === -1) { + delete subset.properties[key]; + } + }); + } + + // 3. filter schema compositions (anyOf, allOf, oneOf) down + // to specified properties + + // TODO: enhance subset functionality to filter more complex + // compositional schemas (anyOf, oneOf) and non-if/then/else conditional structures. + // Also enhance to account for whole compositions versus individual clauses. + + if (subset.allOf) { + subset.allOf = subset.allOf.map((s) => + subsetSchema(s as IConfigurationSchema, props) + ); + } + + if (subset.if) { + subset.if = subsetSchema(subset.if as IConfigurationSchema, props); + } + + if (subset.then) { + subset.then = subsetSchema(subset.then as IConfigurationSchema, props); + } + + if (subset.else) { + subset.else = subsetSchema(subset.else as IConfigurationSchema, props); + } return subset; } diff --git a/packages/common/test/core/schemas/internal/filterSchemaToUiSchema.test.ts b/packages/common/test/core/schemas/internal/filterSchemaToUiSchema.test.ts index 3f3bda46cea..e5fa3981848 100644 --- a/packages/common/test/core/schemas/internal/filterSchemaToUiSchema.test.ts +++ b/packages/common/test/core/schemas/internal/filterSchemaToUiSchema.test.ts @@ -6,57 +6,155 @@ import { describe("filterSchemaToUiSchema util:", () => { it("returns schema limited to props in UiSchema", () => { + const schema: IConfigurationSchema = { + required: ["name"], + type: "object", + properties: { + name: { + type: "string", + minLength: 1, + maxLength: 250, + }, + summary: { + type: "string", + }, + description: { + type: "string", + }, + }, + }; + + const SteppedUiSchema: IUiSchema = { + type: "Layout", + elements: [ + { + type: "Section", + labelKey: "section1.label.key", + options: { + section: "stepper", + open: true, + }, + elements: [ + { + type: "Step", + labelKey: "step1.label.key", + elements: [ + { + labelKey: "property1.label.key", + scope: "/properties/name", + type: "Control", + }, + { + labelKey: "property2.label.key", + scope: "/properties/summary", + type: "Control", + }, + ], + }, + ], + }, + ], + }; const chk = filterSchemaToUiSchema(schema, SteppedUiSchema); expect(chk.properties?.description).not.toBeDefined(); }); -}); - -const schema: IConfigurationSchema = { - required: ["name"], - type: "object", - properties: { - name: { - type: "string", - minLength: 1, - maxLength: 250, - }, - summary: { - type: "string", - }, - description: { - type: "string", - }, - }, -}; -const SteppedUiSchema: IUiSchema = { - type: "Layout", - elements: [ - { - type: "Section", - labelKey: "section1.label.key", - options: { - section: "stepper", - open: true, + it("returns schema limited to props in UiSchema when there is a conditional that does not apply", () => { + const schema: IConfigurationSchema = { + required: ["title"], + properties: { + status: { + type: "string", + }, + groups: { + type: "array", + }, + title: { + type: "string", + }, + _metric: { + type: "object", + properties: { + type: { + type: "string", + enum: ["static", "dynamic"], + }, + value: { + type: "string", + }, + }, + }, }, - elements: [ + allOf: [ { - type: "Step", - labelKey: "step1.label.key", - elements: [ - { - labelKey: "property1.label.key", - scope: "/properties/name", - type: "Control", + if: { + properties: { + _metric: { + properties: { + type: { + const: "static", + }, + }, + }, }, - { - labelKey: "property2.label.key", - scope: "/properties/summary", - type: "Control", + }, + then: { + properties: { + _metric: { + required: ["value"], + }, }, - ], + }, + else: { + required: ["status"], + }, + }, + ], + }; + + const uiSchema: IUiSchema = { + type: "Layout", + elements: [ + { + type: "Control", + scope: "/properties/title", + labelKey: "title", + }, + { + type: "Control", + scope: "/properties/status", + labelKey: "status", + }, + { + type: "Control", + scope: "/properties/groups", + labelKey: "groups", }, ], - }, - ], -}; + }; + + const chk = filterSchemaToUiSchema(schema, uiSchema); + expect(chk).toEqual({ + required: ["title"], + properties: { + status: { + type: "string", + }, + groups: { + type: "array", + }, + title: { + type: "string", + }, + }, + allOf: [ + { + if: { properties: {} }, + then: { properties: {} }, + else: { required: ["status"] }, + }, + ], + }); + expect(chk.properties?._metric).not.toBeDefined(); + }); +});