diff --git a/meta_configurator/src/components/dialogs/csvimport/importCsvUtils.ts b/meta_configurator/src/components/dialogs/csvimport/importCsvUtils.ts index c4a916f0..9d2cf233 100644 --- a/meta_configurator/src/components/dialogs/csvimport/importCsvUtils.ts +++ b/meta_configurator/src/components/dialogs/csvimport/importCsvUtils.ts @@ -15,6 +15,7 @@ import { } from '@/components/dialogs/csvimport/delimiterSeparatorUtils'; import {type CsvError, parse} from 'csv-parse/browser/esm'; import type {JsonSchemaType} from '@/schema/jsonSchemaType'; +import {identifyArraysInJson} from "@/utility/arrayPathUtils"; export function requestUploadFileToRef(resultString: Ref, resultTableName: Ref) { const {open, onChange} = useFileDialog(); @@ -152,18 +153,7 @@ export function userStringToIdentifier(input: string, cutExtension: boolean = fa // note that this function does not look for a table within a table export function detectPossibleTablesInJson(json: any, path: Path = []): Path[] { - const tables: Path[] = []; - for (const key in json) { - if (json.hasOwnProperty(key)) { - const newPath = path ? [...path, key] : [key]; - if (Array.isArray(json[key])) { - tables.push(newPath); - } else if (typeof json[key] === 'object' && json[key] !== null) { - tables.push(...detectPossibleTablesInJson(json[key], newPath)); - } - } - } - return tables; + return identifyArraysInJson(json, path, false, true); } export function detectPropertiesOfTableInJson(json: any, tablePath: Path): string[] { diff --git a/meta_configurator/src/components/panels/defaultPanelTypes.ts b/meta_configurator/src/components/panels/defaultPanelTypes.ts index aa502277..67b1c1bb 100644 --- a/meta_configurator/src/components/panels/defaultPanelTypes.ts +++ b/meta_configurator/src/components/panels/defaultPanelTypes.ts @@ -6,6 +6,7 @@ import GuiEditorPanel from '@/components/panels/gui-editor/GuiEditorPanel.vue'; import SchemaDiagramPanel from '@/components/panels/schema-diagram/SchemaDiagramPanel.vue'; import DebugPanel from '@/components/panels/debug-panel/DebugPanel.vue'; import AiPromptsPanel from '@/components/panels/ai-prompts/AiPromptsPanel.vue'; +import ListAnalysisPanel from './list-analysis/ListAnalysisPanel.vue'; export const panelTypeTextEditor: PanelTypeDefinition = { getComponent: () => CodeEditorPanel, @@ -39,6 +40,14 @@ export const panelTypeAiPrompts: PanelTypeDefinition = { name: 'aiPrompts', }; +export const panelTypeListAnalysis: PanelTypeDefinition = { + getComponent: () => ListAnalysisPanel, + supportedModes: [SessionMode.DataEditor], + label: 'List Analysis', + icon: 'fa-solid fa-table', + name: 'listAnalysis', +}; + export const panelTypeDebug: PanelTypeDefinition = { getComponent: () => DebugPanel, supportedModes: [SessionMode.DataEditor, SessionMode.SchemaEditor, SessionMode.Settings], @@ -55,5 +64,6 @@ export function registerDefaultPanelTypes() { panelTypeRegistry.registerPanelType('guiEditor', panelTypeGuiEditor); panelTypeRegistry.registerPanelType('schemaDiagram', panelTypeSchemaDiagram); panelTypeRegistry.registerPanelType('aiPrompts', panelTypeAiPrompts); + panelTypeRegistry.registerPanelType('listAnalysis', panelTypeListAnalysis); panelTypeRegistry.registerPanelType('debug', panelTypeDebug); } diff --git a/meta_configurator/src/components/panels/gui-editor/GuiEditorPanelJsonSchema.vue b/meta_configurator/src/components/panels/gui-editor/GuiEditorPanelJsonSchema.vue index aae9ade3..d03bcbc1 100644 --- a/meta_configurator/src/components/panels/gui-editor/GuiEditorPanelJsonSchema.vue +++ b/meta_configurator/src/components/panels/gui-editor/GuiEditorPanelJsonSchema.vue @@ -50,7 +50,7 @@ function updateData(path: Path, newValue: any) { sortedProperties[key] = parentData[key]; } }); - // after adding properties from the schema in proper order, add the rest of the properties + // after adding properties from the schema in proper order, add the rest of the properties f dataKeys.forEach(key => { if (!schemaKeys.includes(key)) { sortedProperties[key] = parentData[key]; diff --git a/meta_configurator/src/components/panels/list-analysis/ListAnalysisPanel.vue b/meta_configurator/src/components/panels/list-analysis/ListAnalysisPanel.vue new file mode 100644 index 00000000..01018adf --- /dev/null +++ b/meta_configurator/src/components/panels/list-analysis/ListAnalysisPanel.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/meta_configurator/src/components/panels/list-analysis/listAnalysisUtils.ts b/meta_configurator/src/components/panels/list-analysis/listAnalysisUtils.ts new file mode 100644 index 00000000..dfbcda44 --- /dev/null +++ b/meta_configurator/src/components/panels/list-analysis/listAnalysisUtils.ts @@ -0,0 +1,74 @@ +import _ from 'lodash'; +import {dataAt} from "@/utility/resolveDataAtPath"; +import {jsonPointerToPathTyped} from "@/utility/pathUtils"; + +export function createItemsRowsFromJson(itemsJson: any): { rows: any[], columnNames: string[] } { + const columnNames = collectItemColumnNames(itemsJson); + + const rows = itemsJson.map((itemJson: any) => { + return createItemRow(itemJson, columnNames); + }); + + const columnNamesFormatted: string[] = Array.from(columnNames).map((columnName: string) => { + return formatJsonPointerForUser(columnName); + }); + + return { rows: rows, columnNames: columnNamesFormatted }; +} + +export function createItemRow(itemJson: any, columnNames: Set): any { + const row: any = {}; + + for (const columnName of columnNames) { + + const columnData = dataAt(jsonPointerToPathTyped(columnName), itemJson); + + const formattedColumnName = formatJsonPointerAsPropertyName(columnName); + if (columnData !== undefined) { + row[formattedColumnName] = columnData; + } else { + row[formattedColumnName] = null; + } + } + + return row; +} + + +// collect all properties of the items, including nested properties +function collectItemColumnNames(itemsJson: any): Set { + const columnNames: Set = new Set(); + + for (const itemJson of itemsJson) { + for (const itemProperty in itemJson) { + if (itemJson.hasOwnProperty(itemProperty)) { + + // if it is an object: recursively collect all nested properties + if (_.isObject(itemJson[itemProperty])) { + const nestedColumnNames = collectItemColumnNames([itemJson[itemProperty]]); + nestedColumnNames.forEach((nestedColumnName: string) => { + columnNames.add(`/${itemProperty}${nestedColumnName}`); + }); + } else { + // if it is a simple property: add it to the list + if (itemProperty !== undefined) { + columnNames.add("/" + itemProperty); + } + } + } + } + } + + return columnNames; +} + + +export function formatJsonPointerAsPropertyName(pointer: string): string { + // remove first slash and replace others by dot + return pointer.replaceAll("/", ""); +} + +export function formatJsonPointerForUser(pointer: string): string { + // remove first slash and replace others by dot + return pointer.substring(1).replaceAll("/", "."); +} \ No newline at end of file diff --git a/meta_configurator/src/utility/arrayPathUtils.ts b/meta_configurator/src/utility/arrayPathUtils.ts new file mode 100644 index 00000000..0ca19415 --- /dev/null +++ b/meta_configurator/src/utility/arrayPathUtils.ts @@ -0,0 +1,29 @@ + +// note that this function does not look for a table within a table +import type {Path} from "@/utility/path"; + +export function identifyArraysInJson(json: any, path: Path = [], allowNestedArrays: boolean, onlyObjectArray: boolean): Path[] { + const arrayPaths: Path[] = []; + + + if (Array.isArray(json) && (!onlyObjectArray || json.length > 0 && typeof json[0] === 'object')) { + arrayPaths.push(path); + if (allowNestedArrays) { + // for each array element, recursively search for nested arrays + for (let i = 0; i < json.length; i++) { + arrayPaths.push(...identifyArraysInJson(json[i], [...path, i], allowNestedArrays, onlyObjectArray)); + } + } + + } else if (typeof json === 'object' && json !== null) { + // for each key, recursively search for nested arrays + for (const key in json) { + if (json.hasOwnProperty(key)) { + arrayPaths.push(...identifyArraysInJson(json[key], [...path, key], allowNestedArrays, onlyObjectArray)); + } + } + } + + + return arrayPaths; +} \ No newline at end of file