diff --git a/src/libs/actions/data-lake.ts b/src/libs/actions/data-lake.ts index 57b2c09d3..62f60f644 100644 --- a/src/libs/actions/data-lake.ts +++ b/src/libs/actions/data-lake.ts @@ -1,5 +1,7 @@ import { v4 as uuid } from 'uuid' +import { flattenData } from '../data-lake/data-flattener' + /** * A variable to be used on a Cockpit action * @param { string } id - The id of the variable @@ -52,28 +54,47 @@ export const getDataLakeVariableData = (id: string): string | number | boolean | return dataLakeVariableData[id] } -export const setDataLakeVariableData = (id: string, data: object | string | number | boolean): void => { - const newData = data - if (data === null) { +export const setDataLakeVariableData = ( + id: string, + data: object | string | number | boolean | Array +): void => { + if (data === null) return + + // Handle already-flat primitive types first + if (typeof data === 'string' || typeof data === 'number') { + if (dataLakeVariableData[id] === undefined) { + createDataLakeVariable(new DataLakeVariable(id, id, typeof data)) + } + dataLakeVariableData[id] = data + notifyDataLakeVariableListeners(id) + return + } + + // Try to flatten complex data + const flattenedData = flattenData(data, (value, index) => { + setDataLakeVariableData(`${id}/${index}`, value) + }) + + if (!flattenedData) return + + const { type, value } = flattenedData + + // Only proceed with string or number types + if (type !== 'string' && type !== 'number') { + console.debug(`attempting to create a variable with type ${type}. Skipping`) return } + + // Create variable if it doesn't exist if (dataLakeVariableData[id] === undefined) { - console.trace(`Cockpit action variable with id '${id}' does not exist. Creating it.`) - const type_of_variable = typeof data - if (type_of_variable === 'object') { - // TODO: support strings - } - if (type_of_variable !== 'string' && type_of_variable !== 'number') { - console.debug(`attempting to create a variable with type ${type_of_variable}. Skipping`) - return - } - createDataLakeVariable(new DataLakeVariable(id, id, typeof data)) + createDataLakeVariable(new DataLakeVariable(id, id, type)) } - if (newData === undefined || typeof newData === 'object') { - return + + // Update the value and notify listeners + if (typeof value === 'string' || typeof value === 'number') { + dataLakeVariableData[id] = value + notifyDataLakeVariableListeners(id) } - dataLakeVariableData[id] = newData - notifyDataLakeVariableListeners(id) } export const deleteDataLakeVariable = (id: string): void => { diff --git a/src/libs/data-lake/data-flattener.ts b/src/libs/data-lake/data-flattener.ts new file mode 100644 index 000000000..69e79d9b7 --- /dev/null +++ b/src/libs/data-lake/data-flattener.ts @@ -0,0 +1,90 @@ +/** + * The result of flattening complex data structures into simple types + */ +interface FlattenedData { + /** + * The determined type of the flattened data + */ + type: 'string' | 'number' | 'boolean' | 'object' + /** + * The resulting flattened value + */ + value: string | number | boolean | object | Array +} + +/** + * Type guard to check if a value is an array of numbers + * @param {unknown[]} data The data to check + * @returns {data is number[]} True if the array contains numbers + */ +function isNumberArray(data: unknown[]): data is number[] { + return typeof data[0] === 'number' +} + +/** + * Type guard to check if a value is an array of strings + * @param {unknown[]} data The data to check + * @returns {data is string[]} True if the array contains strings + */ +function isStringArray(data: unknown[]): data is string[] { + return typeof data[0] === 'string' +} + +/** + * Flattens complex data structures into simple types that can be stored in the data lake + * @param {object | string | number | boolean | Array} data The data to flatten + * @param {(value: number, index: string) => void} [onArrayElement] Callback for handling individual array elements + * @returns {FlattenedData | null} The flattened data structure or null if unable to flatten + */ +export function flattenData( + data: object | string | number | boolean | Array, + onArrayElement?: (value: number, index: string) => void +): FlattenedData | null { + const typeOfData = typeof data + + if (typeOfData !== 'object') { + return { + type: typeOfData as 'string' | 'number' | 'boolean', + value: data, + } + } + + // Handle arrays + if (Array.isArray(data)) { + if (data.length === 0) return null + + if (isStringArray(data)) { + return { + type: 'string', + value: data.join(''), + } + } + + if (isNumberArray(data) && onArrayElement) { + // Handle array of numbers by calling the callback for each element + data.forEach((value, index) => { + onArrayElement(value, index.toString()) + }) + return null + } + } + + // Handle objects with special properties + const objData = data as Record + + if ('type' in objData && 'value' in objData) { + return { + type: typeof objData.type as 'string' | 'number' | 'boolean', + value: objData.value as string | number | boolean, + } + } + + if ('bits' in objData) { + return { + type: typeof objData.bits as 'string' | 'number' | 'boolean', + value: objData.bits as string | number | boolean, + } + } + + return null +}