From 4009c2d45d9f778da7cae4aa0799ae939e7c8aac Mon Sep 17 00:00:00 2001 From: Faye Tan Date: Tue, 7 Jan 2025 12:15:28 -0500 Subject: [PATCH 1/3] support structs --- src/example/ExampleConfig.ts | 133 ++++++++----------- src/internal/variableTypeWithDefaultValue.ts | 3 +- 2 files changed, 60 insertions(+), 76 deletions(-) diff --git a/src/example/ExampleConfig.ts b/src/example/ExampleConfig.ts index dfed31c..09ea95f 100644 --- a/src/example/ExampleConfig.ts +++ b/src/example/ExampleConfig.ts @@ -172,44 +172,35 @@ export const COMPREHENSIVE_EXAMPLE_CONFIG = [ }, }, }, - // TODO: struct not yet fully supported - // { - // fieldId: "input-struct-field", - // field: { - // type: "single", - - // label: "Input struct", - // fieldValue: { - // type: "inputOutput", - // variableType: { - // type: "struct", - // structFieldTypes: [ - // { - // fieldId: "struct-field-1", - // fieldType: { - // type: "string", - // }, - // }, - // { - // fieldId: "struct-field-2", - // fieldType: { - // type: "boolean", - // }, - // }, - // ], - // }, - // defaultValue: { - // status: "LOADED", - // defaultValue: { - // structFields: { - // "struct-field-1": "yay", - // "struct-field-2": false, - // } - // } - // } - // }, - // } - // }, + { + fieldId: "input-struct-field", + field: { + type: "single", + label: "Input struct", + fieldValue: { + type: "inputOutput", + variableType: { + type: "struct", + structFieldTypes: [ + { + fieldId: "struct-field-1", + fieldType: { + type: "string", + defaultValue: "yay", + }, + }, + { + fieldId: "struct-field-2", + fieldType: { + type: "boolean", + defaultValue: false, + }, + }, + ], + }, + }, + }, + }, { fieldId: "event", field: { @@ -251,43 +242,35 @@ export const COMPREHENSIVE_EXAMPLE_CONFIG = [ }, }, }, - // TODO: struct not supported yet - // { - // fieldId: "structInListOf", - // field: { - // type: "single", - // label: "Struct in list of", - // fieldValue: { - // type: "inputOutput", - // variableType: { - // type: "struct", - // structFieldTypes: [ - // { - // fieldId: "struct-field-1", - // fieldType: { - // type: "number", - // }, - // }, - // { - // fieldId: "struct-field-2", - // fieldType: { - // type: "date", - // }, - // }, - // ], - // }, - // defaultValue: { - // status: "LOADED", - // defaultValue: { - // structFields: { - // "struct-field-1": 321, - // "struct-field-2": new Date("2024-01-01"), - // } - // } - // } - // } - // } - // }, + { + fieldId: "structInListOf", + field: { + type: "single", + label: "Struct in list of", + fieldValue: { + type: "inputOutput", + variableType: { + type: "struct", + structFieldTypes: [ + { + fieldId: "struct-field-1", + fieldType: { + type: "number", + defaultValue: 321, + }, + }, + { + fieldId: "struct-field-2", + fieldType: { + type: "date", + defaultValue: new Date("2024-01-01"), + }, + }, + ], + }, + }, + }, + }, { fieldId: "nestedListOfField", field: { diff --git a/src/internal/variableTypeWithDefaultValue.ts b/src/internal/variableTypeWithDefaultValue.ts index c9dc969..d5520b5 100644 --- a/src/internal/variableTypeWithDefaultValue.ts +++ b/src/internal/variableTypeWithDefaultValue.ts @@ -12,6 +12,7 @@ See the License for the specific language governing permissions and limitations under the License. */ import { ObjectSetLocators } from "../types"; +import { StructValue } from "./variableValues"; /** * Available variable types in the context and Workshop. @@ -27,7 +28,7 @@ export type IVariableType_WithDefaultValue = | IVariableType_NumberList_WithDefaultValue | IVariableType_DateList_WithDefaultValue | IVariableType_TimestampList_WithDefaultValue - // | IVariableType_Struct // TODO: Struct support is coming, but is not fully supported yet + | IVariableType_Struct_WithDefaultValue | IVariableType_ObjectSet_WithDefaultValue; // Workshop only supports struct fields containing the following types. Nested structs are not yet supported From dd6daa082bb76d9e2aa0269ef97bfa9cd7fae53e Mon Sep 17 00:00:00 2001 From: Faye Tan Date: Tue, 7 Jan 2025 14:47:31 -0500 Subject: [PATCH 2/3] Better example --- README.md | 98 ++++++++++----- src/example/Example.tsx | 236 +++++++++++++++++++++++++++++------ src/example/ExampleConfig.ts | 12 +- 3 files changed, 273 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 52ac6ea..7586e33 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ See [Examples.tsx](./src/example/Example.tsx) for a complete example, and see [E A basic config definition: ```typescript -export const BASIC_CONFIG_DEFINITION = [ +const BASIC_CONFIG_DEFINITION = [ { fieldId: "stringField", field: { @@ -96,45 +96,81 @@ Here is an example React component that shows how to call `useWorkshopContext` w const ExampleComponent = () => { const workshopContext = useWorkshopContext(BASIC_CONFIG_DEFINITION); - if (isAsyncValue_Loading(workshopContext)) { - // Render a loading state - } else if (isAsyncValue_Loaded(workshopContext)) { - // Must explicitly declare type for the loaded context value - const loadedWorkshopContext: IWorkshopContext = workshopContext.value; - - const { stringField, workshopEvent, listOfField } = loadedWorkshopContext; + return visitLoadingState(workshopContext, { + loading: () => <>...Render a loading state, + succeeded: ( + // Must use to explicitly delare type for loaded context value + loadedWorkshopContext: IWorkshopContext + ) => ( + + ), + reloading: (_reloadingContext) => <>...Render a reloading state, + failed: (_error) => <>...Render an error state, + }); +}; - // Examples of retrieving single field values. - const stringValue: IAsyncValue = stringField.fieldValue; +const LoadedExampleComponent: React.FC<{ + // Must use to explicitly delare type for loaded context value + loadedWorkshopContext: IWorkshopContext; +}> = (props) => { + const { stringField, workshopEvent, listOfField } = + props.loadedWorkshopContext; - // Examples of retrieving listOf field values. - listOfField.forEach(listItem => { - const booleanListValue: IAsyncValue = listItem.booleanListField.fieldValue; - }); + // Example of retrieving single field values. + const stringValue: IAsyncValue = stringField.fieldValue; - // Examples of setting single field values. + // Examples of setting a single field value + const changeStringFieldValue = React.useCallback(() => { stringField.setLoading(); stringField.setLoadedValue("Hello world!"); stringField.setReloadingValue("Hello world is reloading."); stringField.setFailedWithError("Hello world failed to load."); + }, [stringField]); - // Examples of setting listOf field values. - listOfField.forEach((listItem, index) => { - listItem.booleanListField.setLoading(); - listItem.booleanListField.setLoadedValue([true, false]); - listItem.booleanListField.setReloadingValue([false, true]); - listItem.booleanListField.setFailedWithError(`Failed to load on listOf layer ${index}`); - }); - - - // Example of executing event. Takes a React MouseEvent, or undefined if not applicable + // Example of executing event. + const executeWorkshopEvent = React.useCallback(() => { + // Takes a React MouseEvent, or undefined if not applicable workshopEvent.executeEvent(undefined); - - - return
Render something here.
; - } else if (isAsyncValue_FailedLoading(workshopContext)) { - // Render a failure state - } + }, [workshopEvent]); + + // Examples of setting a single field value inside listOf field values + const executeListOfFieldChange = React.useCallback( + (index: number) => () => { + if (index < listOfField.length) { + listOfField[index]?.booleanListField.setLoading(); + listOfField[index]?.booleanListField.setLoadedValue([true, false]); + listOfField[index]?.booleanListField.setReloadingValue([false, true]); + listOfField[index]?.booleanListField.setFailedWithError( + `Failed to load on listOf layer ${index}` + ); + } + }, + [listOfField] + ); + + return ( +
+ {JSON.stringify(stringValue)} + +
+ +
+ {listOfField.map((listItem, index) => { + return ( + <> + {JSON.stringify(listItem.booleanListField.fieldValue)} + + + ); + })} +
+ ); }; ``` diff --git a/src/example/Example.tsx b/src/example/Example.tsx index 646fea5..05ced30 100644 --- a/src/example/Example.tsx +++ b/src/example/Example.tsx @@ -13,13 +13,10 @@ limitations under the License. */ import React from "react"; import { COMPREHENSIVE_EXAMPLE_CONFIG } from "./ExampleConfig"; -import { - IAsyncValue, - visitLoadingState, -} from "../types/loadingState"; +import { IAsyncValue, visitLoadingState } from "../types/loadingState"; import { IWorkshopContext } from "../types/workshopContext"; import { useWorkshopContext } from "../"; -import { ObjectSetLocators } from "../types"; +import { IConfigDefinition, ObjectSetLocators } from "../types"; /** * This is an example of how to use `useWorkshopContext`, and then ensure that the context object returned is loaded before @@ -31,9 +28,13 @@ export const Example = () => { // Use a visitor function to render based on the async status of the workshop context object return visitLoadingState(workshopContext, { loading: () => <>Loading..., - succeeded: loadedContext => , - reloading: _reloadingContext => <>Reloading..., - failed: _error => <>Error..., + succeeded: ( + loadedContext: IWorkshopContext + ) => , + reloading: ( + _reloadingContext: IWorkshopContext + ) => <>Reloading..., + failed: (_error) => <>Error..., }); }; @@ -49,6 +50,7 @@ const LoadedComprehensiveExample: React.FC<{ numberField, dateField, timestampField, + structField, stringListField, objectSetField, event, @@ -72,6 +74,15 @@ const LoadedComprehensiveExample: React.FC<{ const dateFieldValue: IAsyncValue = dateField.fieldValue; const timestampFieldValue: IAsyncValue = timestampField.fieldValue; + const structFieldValue: IAsyncValue< + | { + structFields: { + structField1: string | undefined; + structField2: boolean | undefined; + }; + } + | undefined + > = structField.fieldValue; const objectSetFieldValue: IAsyncValue = objectSetField.fieldValue; @@ -79,13 +90,13 @@ const LoadedComprehensiveExample: React.FC<{ // const primaryKeys: string[] = isAsyncValueLoaded(objectSetFieldValue) ? objectSetFieldValue.value.primaryKeys : []; // const housesfilteredByPrimaryKey: ObjectSet = client.ontology.objects.RottenTomatoesMovies.where(query => query.rottenTomatoesLink.containsAnyTerm(primaryKeys.join(" "))); - const stringListFieldValue: IAsyncValue = + const stringListFieldValue: IAsyncValue = stringListField.fieldValue; const booleanListFieldValue: IAsyncValue = booleanListField.fieldValue; const numberListFieldValue: IAsyncValue = numberListField.fieldValue; - // date arrays are stored as a string array with every entry in the format "yyyy-mm-dd" + // date arrays are stored as a string array with every entry in the format "yyyy-mm-dd" const dateListFieldValue: IAsyncValue = dateListField.fieldValue; const timestampListFieldValue: IAsyncValue = @@ -97,17 +108,36 @@ const LoadedComprehensiveExample: React.FC<{ stringField.setLoading(); stringField.setLoadedValue("Hello world!!!"); // The value takes the config field type, in this case, string stringField.setReloadingValue("I am reloading..."); // The value takes the config field type, in this case, string - stringField.setFailedWithError("Oh no, an error occurred!"); // Takes string for error message + stringField.setFailedWithError("Oh no, an error occurred with stringField!"); // Takes string for error message booleanField.setLoading(); booleanField.setLoadedValue(false); // The value takes the config field type, in this case, boolean booleanField.setReloadingValue(true); // The value takes the config field type, in this case, boolean - booleanField.setFailedWithError("Oh no, an error occurred!"); // Takes string for error message + booleanField.setFailedWithError( + "Oh no, an error occurred with booleanField!" + ); // Takes string for error message dateField.setLoading(); dateField.setLoadedValue(new Date("2024-01-01")); // The value takes the config field type, in this case, Date. Note that the value saved is a string in format "yyyy-mm-dd" dateField.setReloadingValue(new Date("2024-12-31")); // The value takes the config field type, in this case, Date. Note that the value saved is a string in format "yyyy-mm-dd" - dateField.setFailedWithError("Oh no, an error occurred!"); // Takes string for error message + dateField.setFailedWithError("Oh no, an error occurred with dateField!"); // Takes string for error message + + structField.setLoading(); + structField.setLoadedValue({ + // The value takes the config field type, in this case the struct defined in the config + structFields: { + structField1: "Hello world!", + structField2: true, + }, + }); + structField.setReloadingValue({ + // The value takes the config field type, in this case the struct defined in the config + structFields: { + structField1: "I am reloading...", + structField2: false, + }, + }); + structField.setFailedWithError("Oh no, an error occurred with structField!"); // Takes string for error message /** * Examples of executing an event @@ -151,27 +181,161 @@ const LoadedComprehensiveExample: React.FC<{ }); }); - return <> - {stringFieldValue} -
- {booleanFieldValue} -
- {numberFieldValue} -
- {dateFieldValue} -
- {timestampFieldValue} -
- {objectSetFieldValue} -
- {stringListFieldValue} -
- {booleanListFieldValue} -
- {numberListFieldValue} -
- {dateListFieldValue} -
- {timestampListFieldValue} - ; + return ( + <> + {stringFieldValue} +
+ {booleanFieldValue} +
+ {numberFieldValue} +
+ {dateFieldValue} +
+ {timestampFieldValue} +
+ {structFieldValue} +
+ {objectSetFieldValue} +
+ {stringListFieldValue} +
+ {booleanListFieldValue} +
+ {numberListFieldValue} +
+ {dateListFieldValue} +
+ {timestampListFieldValue} + + ); +}; + +const BASIC_CONFIG_DEFINITION = [ + { + fieldId: "stringField", + field: { + type: "single", + fieldValue: { + type: "inputOutput", + variableType: { + type: "string", + defaultValue: "test", + }, + }, + label: "Input string (title)", + }, + }, + { + fieldId: "workshopEvent", + field: { + type: "single", + label: "Events", + fieldValue: { + type: "event", + }, + }, + }, + { + fieldId: "listOfField", + field: { + type: "listOf", + label: "A list of fields", + addButtonText: "Add another item to these listOf fields", + config: [ + { + fieldId: "booleanListField", + field: { + type: "single", + label: "Boolean list in a listOf", + fieldValue: { + type: "inputOutput", + variableType: { + type: "boolean-list", + defaultValue: [true, false, true, false], + }, + }, + }, + }, + ], + }, + }, +] as const satisfies IConfigDefinition; + +const ExampleComponent = () => { + const workshopContext = useWorkshopContext(BASIC_CONFIG_DEFINITION); + + return visitLoadingState(workshopContext, { + loading: () => <>...Render a loading state, + // Must use to explicitly delare type for loaded context value + succeeded: ( + loadedWorkshopContext: IWorkshopContext + ) => ( + + ), + reloading: (_reloadingContext) => <>...Render a reloading state, + failed: (_error) => <>...Render an error state, + }); +}; + +const LoadedExampleComponent: React.FC<{ + loadedWorkshopContext: IWorkshopContext; +}> = (props) => { + const { stringField, workshopEvent, listOfField } = + props.loadedWorkshopContext; + + // Example of retrieving single field values. + const stringValue: IAsyncValue = stringField.fieldValue; + + // Examples of setting a single field value + const changeStringFieldValue = React.useCallback(() => { + stringField.setLoading(); + stringField.setLoadedValue("Hello world!"); + stringField.setReloadingValue("Hello world is reloading."); + stringField.setFailedWithError("Hello world failed to load."); + }, [stringField]); + + // Example of executing event. + const executeWorkshopEvent = React.useCallback(() => { + // Takes a React MouseEvent, or undefined if not applicable + workshopEvent.executeEvent(undefined); + }, [workshopEvent]); + + // Examples of setting a single field value inside listOf field values + const executeListOfFieldChange = React.useCallback( + (index: number) => () => { + if (index < listOfField.length) { + listOfField[index]?.booleanListField.setLoading(); + listOfField[index]?.booleanListField.setLoadedValue([true, false]); + listOfField[index]?.booleanListField.setReloadingValue([false, true]); + listOfField[index]?.booleanListField.setFailedWithError( + `Failed to load on listOf layer ${index}` + ); + } + }, + [listOfField] + ); + + return ( +
+ {JSON.stringify(stringValue)} + +
+ +
+ {listOfField.map((listItem, index) => { + return ( + <> + {JSON.stringify(listItem.booleanListField.fieldValue)} + + + ); + })} +
+ ); }; diff --git a/src/example/ExampleConfig.ts b/src/example/ExampleConfig.ts index 09ea95f..9d981c7 100644 --- a/src/example/ExampleConfig.ts +++ b/src/example/ExampleConfig.ts @@ -173,7 +173,7 @@ export const COMPREHENSIVE_EXAMPLE_CONFIG = [ }, }, { - fieldId: "input-struct-field", + fieldId: "structField", field: { type: "single", label: "Input struct", @@ -183,14 +183,14 @@ export const COMPREHENSIVE_EXAMPLE_CONFIG = [ type: "struct", structFieldTypes: [ { - fieldId: "struct-field-1", + fieldId: "structField1", fieldType: { type: "string", defaultValue: "yay", }, }, { - fieldId: "struct-field-2", + fieldId: "structField2", fieldType: { type: "boolean", defaultValue: false, @@ -243,7 +243,7 @@ export const COMPREHENSIVE_EXAMPLE_CONFIG = [ }, }, { - fieldId: "structInListOf", + fieldId: "structInsideListOf", field: { type: "single", label: "Struct in list of", @@ -253,14 +253,14 @@ export const COMPREHENSIVE_EXAMPLE_CONFIG = [ type: "struct", structFieldTypes: [ { - fieldId: "struct-field-1", + fieldId: "structField1", fieldType: { type: "number", defaultValue: 321, }, }, { - fieldId: "struct-field-2", + fieldId: "structField2", fieldType: { type: "date", defaultValue: new Date("2024-01-01"), From bbb429996d190c205d119d32fad774304a42c449 Mon Sep 17 00:00:00 2001 From: Faye Tan Date: Tue, 7 Jan 2025 23:46:27 -0500 Subject: [PATCH 3/3] struct values transformed in map --- src/createDefaultConfigValueMap.ts | 39 ++++-- src/example/Example.tsx | 131 +------------------ src/internal/variableTypeWithDefaultValue.ts | 1 - src/transform-config/utils.ts | 39 +++++- src/types/workshopContext.ts | 17 ++- 5 files changed, 77 insertions(+), 150 deletions(-) diff --git a/src/createDefaultConfigValueMap.ts b/src/createDefaultConfigValueMap.ts index 39c15d9..01f95fb 100644 --- a/src/createDefaultConfigValueMap.ts +++ b/src/createDefaultConfigValueMap.ts @@ -12,10 +12,14 @@ * limitations under the License. */ - -import { asyncValueLoaded, IAsyncValue, IConfigDefinition } from "./types"; +import { asyncValueLoaded, IConfigDefinition } from "./types"; import { assertNever, formatDate } from "./utils"; -import { IConfigValueMap, IVariableType_WithDefaultValue, IVariableValue } from "./internal"; +import { + IConfigValueMap, + IVariableType_WithDefaultValue, + IVariableValue, + StructValue, +} from "./internal"; /** * Takes the configDefinition and pulls out the default values, creating a default config values map. @@ -32,8 +36,10 @@ export function createDefaultConfigValueMap( case "inputOutput": configValueMap[configField.fieldId] = { type: "single", - value: variableTypeWithDefaultValueToValue( - configField.field.fieldValue.variableType + value: asyncValueLoaded( + variableTypeWithDefaultValueToValue( + configField.field.fieldValue.variableType + ) ), }; return; @@ -65,13 +71,24 @@ export function createDefaultConfigValueMap( function variableTypeWithDefaultValueToValue( variableType: IVariableType_WithDefaultValue -): IAsyncValue { +): IVariableValue | undefined { // For date and date-list variable types, need to convert from Date to string and Date[] to string[] // As we will save date values as strings in format "yyyy-mm-dd" if (variableType.type === "date" && variableType.defaultValue != null) { - return asyncValueLoaded(formatDate(variableType.defaultValue)); - } else if (variableType.type === "date-list" && variableType.defaultValue != null) { - return asyncValueLoaded(variableType.defaultValue.map(formatDate)); - } - return asyncValueLoaded(variableType.defaultValue); + return formatDate(variableType.defaultValue); + } else if ( + variableType.type === "date-list" && + variableType.defaultValue != null + ) { + return variableType.defaultValue.map(formatDate); + } else if (variableType.type === "struct") { + const structFields = variableType.structFieldTypes.reduce((acc, structFieldType) => { + acc[structFieldType.fieldId] = variableTypeWithDefaultValueToValue(structFieldType.fieldType); + return acc; + }, {} as StructValue["structFields"]); + return { + structFields, + }; + } + return variableType.defaultValue; } diff --git a/src/example/Example.tsx b/src/example/Example.tsx index 05ced30..bc771ac 100644 --- a/src/example/Example.tsx +++ b/src/example/Example.tsx @@ -16,7 +16,7 @@ import { COMPREHENSIVE_EXAMPLE_CONFIG } from "./ExampleConfig"; import { IAsyncValue, visitLoadingState } from "../types/loadingState"; import { IWorkshopContext } from "../types/workshopContext"; import { useWorkshopContext } from "../"; -import { IConfigDefinition, ObjectSetLocators } from "../types"; +import { ObjectSetLocators } from "../types"; /** * This is an example of how to use `useWorkshopContext`, and then ensure that the context object returned is loaded before @@ -210,132 +210,3 @@ const LoadedComprehensiveExample: React.FC<{ ); }; -const BASIC_CONFIG_DEFINITION = [ - { - fieldId: "stringField", - field: { - type: "single", - fieldValue: { - type: "inputOutput", - variableType: { - type: "string", - defaultValue: "test", - }, - }, - label: "Input string (title)", - }, - }, - { - fieldId: "workshopEvent", - field: { - type: "single", - label: "Events", - fieldValue: { - type: "event", - }, - }, - }, - { - fieldId: "listOfField", - field: { - type: "listOf", - label: "A list of fields", - addButtonText: "Add another item to these listOf fields", - config: [ - { - fieldId: "booleanListField", - field: { - type: "single", - label: "Boolean list in a listOf", - fieldValue: { - type: "inputOutput", - variableType: { - type: "boolean-list", - defaultValue: [true, false, true, false], - }, - }, - }, - }, - ], - }, - }, -] as const satisfies IConfigDefinition; - -const ExampleComponent = () => { - const workshopContext = useWorkshopContext(BASIC_CONFIG_DEFINITION); - - return visitLoadingState(workshopContext, { - loading: () => <>...Render a loading state, - // Must use to explicitly delare type for loaded context value - succeeded: ( - loadedWorkshopContext: IWorkshopContext - ) => ( - - ), - reloading: (_reloadingContext) => <>...Render a reloading state, - failed: (_error) => <>...Render an error state, - }); -}; - -const LoadedExampleComponent: React.FC<{ - loadedWorkshopContext: IWorkshopContext; -}> = (props) => { - const { stringField, workshopEvent, listOfField } = - props.loadedWorkshopContext; - - // Example of retrieving single field values. - const stringValue: IAsyncValue = stringField.fieldValue; - - // Examples of setting a single field value - const changeStringFieldValue = React.useCallback(() => { - stringField.setLoading(); - stringField.setLoadedValue("Hello world!"); - stringField.setReloadingValue("Hello world is reloading."); - stringField.setFailedWithError("Hello world failed to load."); - }, [stringField]); - - // Example of executing event. - const executeWorkshopEvent = React.useCallback(() => { - // Takes a React MouseEvent, or undefined if not applicable - workshopEvent.executeEvent(undefined); - }, [workshopEvent]); - - // Examples of setting a single field value inside listOf field values - const executeListOfFieldChange = React.useCallback( - (index: number) => () => { - if (index < listOfField.length) { - listOfField[index]?.booleanListField.setLoading(); - listOfField[index]?.booleanListField.setLoadedValue([true, false]); - listOfField[index]?.booleanListField.setReloadingValue([false, true]); - listOfField[index]?.booleanListField.setFailedWithError( - `Failed to load on listOf layer ${index}` - ); - } - }, - [listOfField] - ); - - return ( -
- {JSON.stringify(stringValue)} - -
- -
- {listOfField.map((listItem, index) => { - return ( - <> - {JSON.stringify(listItem.booleanListField.fieldValue)} - - - ); - })} -
- ); -}; diff --git a/src/internal/variableTypeWithDefaultValue.ts b/src/internal/variableTypeWithDefaultValue.ts index d5520b5..94ef877 100644 --- a/src/internal/variableTypeWithDefaultValue.ts +++ b/src/internal/variableTypeWithDefaultValue.ts @@ -12,7 +12,6 @@ See the License for the specific language governing permissions and limitations under the License. */ import { ObjectSetLocators } from "../types"; -import { StructValue } from "./variableValues"; /** * Available variable types in the context and Workshop. diff --git a/src/transform-config/utils.ts b/src/transform-config/utils.ts index 0d9b50c..caf7fc5 100644 --- a/src/transform-config/utils.ts +++ b/src/transform-config/utils.ts @@ -17,10 +17,13 @@ import { IVariableValue, IVariableType_WithDefaultValue, IVariableToSet, + IVariableType_Struct_WithDefaultValue, + StructValue, + StructValueWithObjectRids, } from "../internal"; import { IAsyncValue } from "../types"; import { isOntologyObject } from "../types/ontologyObject"; -import { VariableTypeToValueTypeToSet } from "../types/workshopContext"; +import { StructVariableValueTypeToSet, VariableTypeToValueTypeToSet } from "../types/workshopContext"; import { assertNever, formatDate } from "../utils"; // Helpers for transformConfigToWorkshopContext @@ -102,6 +105,7 @@ function isDate(val: unknown): val is Date { * - for objectSet variables, extract the primaryKeys, which is OSDK's preferred format to load objects with and cap to first 10,000 primaryKeys * - for date variables, convert from Date value to string value in format "yyyy-mm-dd" * - for date array variables, convert from Date[] value to string[] in format "yyyy-mm-dd" per entry + * - for struct variables, ensure every struct field has been transformed */ export function maybeTransformValueToSetToValueMapTypes< V extends IVariableType_WithDefaultValue @@ -142,10 +146,30 @@ export function maybeTransformValueToSetToValueMapTypes< value.every(isDate) ) { return value.map(formatDate); + } else if (isStruct(value)) { + if (variableType.type === "struct") { + const structFields = variableType.structFieldTypes.reduce((acc, structFieldType) => { + const structFieldValue = value.structFields[structFieldType.fieldId]; + acc[structFieldType.fieldId] = maybeTransformValueToSetToValueMapTypes(structFieldType.fieldType, structFieldValue); + return acc; + }, {} as StructValue["structFields"]); + return { + structFields + } + } else { + return undefined; + } } return value; } +/** + * Given an unknown param v, return true only if v can be attributed to be a struct variable value. + */ +function isStruct(v: unknown): v is StructVariableValueTypeToSet { + return typeof v === "object" && v != null && "structFields" in v && v.structFields != null && typeof v.structFields === "object"; +} + /** * Before sending a value to Workshop: * - for objectSet variables, extract the objectRids, which is Workshop's preferred format to load objects with and cap to first 10,000 objectRids @@ -172,6 +196,19 @@ export function maybeTransformValueToSetToWorkshopValue< value.every(isDate) ) { return value.map(formatDate); + } else if (isStruct(value)) { + if (variableType.type === "struct") { + const structFields = variableType.structFieldTypes.reduce((acc, structFieldType) => { + const structFieldValue = value.structFields[structFieldType.fieldId]; + acc[structFieldType.fieldId] = maybeTransformValueToSetToWorkshopValue(structFieldType.fieldType, structFieldValue); + return acc; + }, {} as StructValueWithObjectRids["structFields"]); + return { + structFields + } + } else { + return undefined; + } } return value; } diff --git a/src/types/workshopContext.ts b/src/types/workshopContext.ts index fd04af8..774d3ec 100644 --- a/src/types/workshopContext.ts +++ b/src/types/workshopContext.ts @@ -13,6 +13,7 @@ limitations under the License. */ import { IStructVariableFieldTypes_WithDefaultValue, + IVariableType_Struct_WithDefaultValue, IVariableType_WithDefaultValue, } from "../internal"; import { IConfigDefinition, IConfigDefinitionField } from "./configDefinition"; @@ -137,16 +138,18 @@ export type VariableTypeToValueTypeToSet< type: "struct"; structFieldTypes: readonly IStructVariableFieldTypes_WithDefaultValue[]; } - ? { - structFields: { - [K in T["structFieldTypes"][number]["fieldId"]]: - | VariableTypeToValueTypeToSet< + ? StructVariableValueTypeToSet + : never; + +export type StructVariableValueTypeToSet = { + structFields: { + [K in T["structFieldTypes"][number]["fieldId"]]: + | VariableTypeToValueTypeToSet< ExtractFieldType > | undefined; - }; - } - : never; + } +} /** * Mapped type to extract field types on structs.