From 1e732d68391082bd0d51c84debdac9b07680eafe Mon Sep 17 00:00:00 2001 From: Patrick Kalita Date: Wed, 2 Oct 2024 16:12:54 -0700 Subject: [PATCH] Check `any_of` on slot definition when determining input type --- .../SampleSlotEditModal.test.ts | 90 +++++++++++++++++++ .../SampleSlotEditModal.tsx | 47 +++++++--- 2 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 src/components/SampleSlotEditModal/SampleSlotEditModal.test.ts diff --git a/src/components/SampleSlotEditModal/SampleSlotEditModal.test.ts b/src/components/SampleSlotEditModal/SampleSlotEditModal.test.ts new file mode 100644 index 0000000..14dbb55 --- /dev/null +++ b/src/components/SampleSlotEditModal/SampleSlotEditModal.test.ts @@ -0,0 +1,90 @@ +import { getSelectState } from "./SampleSlotEditModal"; +import { SchemaDefinition } from "../../linkml-metamodel"; +import { GoldEcosystemTreeNode, SampleDataValue } from "../../api"; + +const MOCK_SCHEMA: SchemaDefinition = { + id: "http://example.org/test", + name: "test", + slots: { + a_string: { + name: "a_string", + range: "string", + }, + a_choice: { + name: "a_choice", + range: "Choice", + }, + a_choice_or_string: { + name: "a_choice_or_string", + any_of: [{ range: "string" }, { range: "Choice" }], + }, + }, + enums: { + Choice: { + name: "Choice", + permissible_values: { + a: { text: "a" }, + b: { text: "b" }, + }, + }, + }, +}; + +const MOCK_GOLD_TREE: GoldEcosystemTreeNode = { + name: "test", + children: [], +}; + +const MOCK_SAMPLE_DATA: Record = {}; + +const MOCK_SAMPLE_DATA_GETTER = (name: string) => MOCK_SAMPLE_DATA[name]; + +describe("getSelectState", () => { + it("should return isSelectable = false when slot is null", () => { + const state = getSelectState( + MOCK_SCHEMA, + null, + MOCK_SAMPLE_DATA_GETTER, + MOCK_GOLD_TREE, + ); + expect(state.isSelectable).toBe(false); + }); + + it("should return isSelectable = false when the slot range is string", () => { + const state = getSelectState( + MOCK_SCHEMA, + MOCK_SCHEMA.slots!["a_string"], + MOCK_SAMPLE_DATA_GETTER, + MOCK_GOLD_TREE, + ); + expect(state.isSelectable).toBe(false); + }); + + it("should return isSelectable = true when the slot range is string", () => { + const state = getSelectState( + MOCK_SCHEMA, + MOCK_SCHEMA.slots!["a_choice"], + MOCK_SAMPLE_DATA_GETTER, + MOCK_GOLD_TREE, + ); + expect(state.isSelectable).toBe(true); + expect(state.permissibleValues).toEqual({ + a: { text: "a" }, + b: { text: "b" }, + }); + }); + + it("should return isSelectable = true when the slot range is any_of", () => { + const state = getSelectState( + MOCK_SCHEMA, + MOCK_SCHEMA.slots!["a_choice_or_string"], + MOCK_SAMPLE_DATA_GETTER, + MOCK_GOLD_TREE, + ); + expect(state.isSelectable).toBe(true); + expect(state.permissibleValues).toEqual({ + a: { text: "a" }, + b: { text: "b" }, + }); + }); +}); diff --git a/src/components/SampleSlotEditModal/SampleSlotEditModal.tsx b/src/components/SampleSlotEditModal/SampleSlotEditModal.tsx index b6a39c8..7864e8d 100644 --- a/src/components/SampleSlotEditModal/SampleSlotEditModal.tsx +++ b/src/components/SampleSlotEditModal/SampleSlotEditModal.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useRef, useState } from "react"; import { + PermissibleValue, SchemaDefinition, SlotDefinition, SlotDefinitionName, @@ -60,18 +61,40 @@ function getSelectState( getSlotValue: (slot: SlotDefinitionName) => SampleDataValue, goldEcosystemTree: GoldEcosystemTreeNode, ) { - if ( - !schema || - !slot || - !(schema.enums && slot.range && slot.range in schema.enums) - ) { - return { - isSelectable: false, - permissibleValues: {}, - warning: "", - }; + const nonSelectableState = { + isSelectable: false, + permissibleValues: {} as Record, + warning: "", + }; + // If the slot is null or the schema doesn't have any enums, don't render a select control. + if (!slot || !schema.enums) { + return nonSelectableState; + } + + // If the slot has a range that is an enum, use that. Otherwise, if the slot has an `any_of` + // iterate over those expressions and collect any with enum ranges. + const slotRangeEnumNames: string[] = []; + if (slot.range && slot.range in schema.enums) { + slotRangeEnumNames.push(slot.range); + } else if (slot.any_of !== undefined) { + for (const anyOfExpression of slot.any_of) { + if (anyOfExpression.range && anyOfExpression.range in schema.enums) { + slotRangeEnumNames.push(anyOfExpression.range); + } + } + } + + // If we didn't find any enum ranges, don't render a select control. + if (slotRangeEnumNames.length === 0) { + return nonSelectableState; + } + const schemaPermissibleValues: Record = {}; + for (const enumName of slotRangeEnumNames) { + Object.assign( + schemaPermissibleValues, + schema.enums[enumName].permissible_values, + ); } - const schemaPermissibleValues = schema.enums[slot.range].permissible_values; let permissibleValues = schemaPermissibleValues || {}; let warning = ""; const goldIndex = GOLD_ECOSYSTEM_SLOTS.indexOf(slot.name); @@ -337,3 +360,5 @@ const SampleSlotEditModal: React.FC = ({ }; export default SampleSlotEditModal; + +export { getSelectState };