CSV import: upload support in the file explorer#24
Conversation
|
Hello Dear Steffen and Niklas, Progress update on this one. What's implemented:
Discoverability note: right now this only lives in the right-click context menu, the same place "Create New CSV" and the other file-creation actions live, so there's no visible upload button in the UI. Happy to add a visible button/icon in the Files panel if that's preferred over a context-menu-only action. Verified in the running app: upload at project root, upload into a nested folder, drag-and-drop onto the tree, rejecting a non-csv file, and the duplicate-name error. This is stacked on #23 since it needs .csv registered as a file type, will rebase onto main once that merges. Will wait for any advises and suggestions. |
|
@amirrza777 one build is failing. Could you check what's going on there, please? |
There was a problem hiding this comment.
Pull request overview
This PR adds first-class CSV upload support in the workbench file explorer (context-menu action + OS drag-and-drop) and wires up the required CSV/model-CSV plugin services and model-language contribution plumbing so uploaded CSVs can participate in the existing language/plugin pipeline.
Changes:
- Add “Upload CSV…” and OS drag-and-drop CSV upload paths in the file explorer, backed by a shared upload helper that creates files via Monaco’s file service and opens the created file.
- Add CSV and model-CSV services (dockerfiles, compose wiring, nginx routing) and register them as default plugins in dev compose.
- Extend the model language to support plugin-contributed
import <FORMAT> { ... }blocks (including CSV), and update model data/diagram paths to account for CSV-backed instances.
Reviewed changes
Copilot reviewed 49 out of 55 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| platform/common/src/main/kotlin/com/mdeo/common/model/ApiResult.kt | Formatting-only change (blank line normalization). |
| platform/backend/build.gradle.kts | Formatting-only change (blank line normalization). |
| infra/docker/workbench/nginx.conf | Proxy routing for /plugin/csv/ and /plugin/model-csv/. |
| infra/docker/workbench/Dockerfile | Increases Node memory for the workbench build step. |
| infra/docker/service-model-csv/Dockerfile | New container build for the model-csv plugin service. |
| infra/docker/service-csv/Dockerfile | New container build for the csv plugin service. |
| infra/docker-compose-dev.yaml | Registers csv/model-csv plugins + adds new services in dev compose. |
| app/tsconfig.build.json | Adds TS project references for new CSV/model-CSV packages/services. |
| app/packages/workbench/src/data/filesystem/uploadFiles.ts | New shared helper to upload CSV files into the project FS and open the result. |
| app/packages/workbench/src/components/tree/util.ts | Extends DnD callbacks to support OS file drops. |
| app/packages/workbench/src/components/tree/TreeItem.vue | Handles OS file drops on folder items and forwards to callback. |
| app/packages/workbench/src/components/tree/Tree.vue | Handles OS file drops on the tree background and forwards to callback. |
| app/packages/workbench/src/components/files/FileSystemItem.vue | Adds “Upload CSV…” action + hidden file input for folder context menus. |
| app/packages/workbench/src/components/files/Files.vue | Adds “Upload CSV…” at root context menu + wires OS drop handling to upload helper. |
| app/packages/service-model/tsconfig.json | Adds reference to language-model-csv. |
| app/packages/service-model/src/index.ts | Updates model service plugin definition (notably dropping .m_gen registration). |
| app/packages/service-model/src/handler/modelDataHandler.ts | Adds CSV-import path for producing ModelData from imported CSV content. |
| app/packages/service-model/package.json | Adds dependency on @mdeo/language-model-csv. |
| app/packages/service-model-csv/vite.config.ts | Builds the served ESM module for model-csv language provider. |
| app/packages/service-model-csv/tsconfig.json | New TS config for the model-csv service package. |
| app/packages/service-model-csv/src/served/language.ts | Serves model-csv plugin provider to the language server. |
| app/packages/service-model-csv/src/index.ts | New model-csv service: registers contribution plugin for import CSV { ... }. |
| app/packages/service-model-csv/package.json | New package definition for model-csv service. |
| app/packages/service-csv/vite.config.ts | Builds the served ESM module for csv language provider. |
| app/packages/service-csv/tsconfig.json | New TS config for the csv service package. |
| app/packages/service-csv/src/served/language.ts | Serves csv plugin provider to the language server. |
| app/packages/service-csv/src/index.ts | New csv service: registers .csv language plugin. |
| app/packages/service-csv/package.json | New package definition for csv service. |
| app/packages/language-model/src/plugin/resolvePlugins.ts | New resolver for model contribution plugins (imports + wrapper rules). |
| app/packages/language-model/src/plugin/modelContributionPlugin.ts | New contract for model-language contribution plugins. |
| app/packages/language-model/src/modelPlugin.ts | Builds model grammar dynamically from resolved contribution plugins. |
| app/packages/language-model/src/index.ts | Exports new model contribution plugin APIs. |
| app/packages/language-model/src/grammar/modelTypes.ts | Adds BaseModelImport + Model.imports to support contributed imports. |
| app/packages/language-model/src/grammar/modelRules.ts | Adds createModelRule(...) to include contributed import wrapper rules. |
| app/packages/language-model/src/features/modelScopeProvider.ts | Broadens class reference scoping to work under contributed import blocks. |
| app/packages/language-model/src/features/diagram-server/modelMetadataManager.ts | Preserves metadata for synthetic CSV nodes across renders when CSV import present. |
| app/packages/language-model/src/features/diagram-server/modelGModelFactory.ts | Renders synthetic diagram nodes based on CSV import rows. |
| app/packages/language-model-csv/tsconfig.json | New TS config for the model CSV contribution package. |
| app/packages/language-model-csv/src/plugin/modelCsvContributionPlugin.ts | Declares the CSV contribution plugin (serialized grammar + import wrapper info). |
| app/packages/language-model-csv/src/index.ts | Barrel exports for model-csv language/contribution features. |
| app/packages/language-model-csv/src/grammar/csvImportTypes.ts | AST shapes for import CSV { Class from "file.csv" }. |
| app/packages/language-model-csv/src/grammar/csvImportRules.ts | Parser rules for the CSV import block content. |
| app/packages/language-model-csv/src/features/csvImport.ts | Implements CSV->instances/links mapping logic (with warnings). |
| app/packages/language-model-csv/src/csvPlugin.ts | Standalone model-csv language plugin provider. |
| app/packages/language-model-csv/package.json | New package definition for @mdeo/language-model-csv. |
| app/packages/language-csv/tsconfig.json | New TS config for the csv language package. |
| app/packages/language-csv/src/index.ts | Barrel exports for csv language package. |
| app/packages/language-csv/src/grammar/csvTypes.ts | Minimal AST type for CSV file content. |
| app/packages/language-csv/src/grammar/csvRules.ts | Minimal grammar for treating a CSV as “any text”. |
| app/packages/language-csv/src/csvPlugin.ts | Standalone csv language plugin provider. |
| app/packages/language-csv/package.json | New package definition for @mdeo/language-csv. |
| app/package-lock.json | Adds workspace links and dependency updates for new packages. |
| .gitignore | Adds .DS_Store. |
Files not reviewed (1)
- app/package-lock.json: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let lastCreatedUri: Uri | undefined; | ||
|
|
||
| for (const file of csvFiles) { | ||
| const uri = Uri.joinPath(targetFolderUri, file.name); | ||
| try { | ||
| const text = await file.text(); | ||
| await fileService.createFile(uri, VSBuffer.fromString(text)); | ||
| lastCreatedUri = uri; | ||
| } catch (error) { | ||
| showError(`Failed to upload ${file.name}`, { | ||
| description: error instanceof Error ? error.message : undefined | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| if (lastCreatedUri == undefined) { | ||
| return; | ||
| } | ||
|
|
||
| showSuccess(csvFiles.length === 1 ? `Uploaded ${csvFiles[0]?.name}` : `Uploaded ${csvFiles.length} files`); | ||
|
|
| @@ -125,6 +127,8 @@ services: | |||
| condition: service_healthy | |||
| service-config-mdeo: | |||
| condition: service_healthy | |||
| service-model-csv: | |||
| condition: service_healthy | |||
| restart: unless-stopped | |||
| location ^~ /plugin/csv/ { | ||
| proxy_pass ${PLUGIN_CSV_SERVICE}/; | ||
| proxy_set_header Host $host; | ||
| proxy_set_header X-Real-IP $remote_addr; | ||
| } |
| const result = importCsvEntries(csvEntries, metamodelClasses); | ||
|
|
||
| const modelData: ModelData = { | ||
| metamodelPath, | ||
| instances: result.instances, | ||
| links: result.links | ||
| }; |
| const csvContent = await this.modelState.languageServices.shared.workspace.FileSystemProvider.readFile(uri); | ||
| const rows = csvContent.split(/\r?\n/).filter((line: string) => line.trim() !== "").map((line: string) => line.split(",")); | ||
| if (rows.length < 2) continue; | ||
| const classHierarchy = resolveClassChain(classRef, this.reflection).map((c) => c.name); | ||
| rows.slice(1).forEach((_row: string[], rowIndex: number) => { | ||
| const instanceName = `${classRef.name}_${rowIndex}`; |
| const config: ServiceConfig<ModelServices> = { | ||
| ...envConfig, | ||
| plugin: modelServicePlugin, | ||
| languages: [modelLanguageConfig, generatedModelLanguageConfig] | ||
| languages: [modelLanguageConfig] | ||
| }; |
…n plugin with import CSV syntax
…teCsvNodes instead
The POST /api/projects/{id}/csv-import endpoint materialized a CSV into
a standalone .metamodel/.model file pair using metamodel inference.
That workflow is superseded by the import CSV {} syntax, which reads
CSV data directly at render/execution time against an existing,
manually authored metamodel. Removes CsvImportService, CsvImportRoutes,
the now-unused CsvModelInference engine and its tests, the backend's
:metamodel dependency (no longer needed), and the unused
CSV_IMPORT_FAILED error code.
The import CSV {} syntax was hardcoded directly into the base model
grammar. Model now exposes a generic extension point (imports:
[BaseModelImport], mirroring Config's sections: [BaseConfigSection]),
and CSV is implemented as a real contribution plugin using the same
SerializedGrammar-based merging approach as config/config-mdeo:
- ModelContributionPlugin now carries a serialized grammar and import
metadata instead of raw parser rule objects, so it can cross service
boundaries as plain JSON
- resolveModelPlugins deserializes and wraps each contributed import in
a BaseModelImport-extending rule, and createModelRule builds the root
Model rule from whatever imports are actually resolved
- language-model-csv now builds a real contribution plugin (external
references for Class/ID/STRING/NEWLINE so its serialized grammar
doesn't duplicate definitions already provided by the host)
- New service-model-csv microservice mirrors service-config-mdeo:
registers the CSV contribution with the model language and hosts its
standalone generated language service
- Deleted the dead mutable-registry scaffolding (registerModelContributionPlugin)
that caused the original ESM dual-instance bug, and the orphaned files
left over from the previous abandoned attempt
- modelScopeProvider's class-reference resolution is now plugin-agnostic
(dispatches on the "class" property generically instead of hardcoding
CsvClassImport by type)
- Fixed a pre-existing bug in modelDataHandler where the CSV file path
was passed as a full URI to an API expecting a plain relative path
Verified end to end: grammar parses with no errors, live diagram
rendering from CSV still works, and the model-data execution pipeline
correctly resolves CSV-backed instances.
npm ci in CI failed because these two new packages, added as part of the contribution-plugin refactor, were never registered in the committed lock file.
Users could previously only get a CSV into a project by creating an empty file through the workbench text editor. Adds an "Upload CSV..." action to the file explorer's context menu (both at the project root and on individual folders) and OS drag-and-drop support onto the file tree, both going through the same upload path: read the file's text, create it via the existing file service, and open it in a new tab. Non-.csv files are rejected with a toast; a name collision surfaces a clear error instead of silently overwriting.
28eefe1 to
bc84f66
Compare
- uploadFiles.ts: the success toast used the attempted file count/first
file name instead of the actually-successful ones, so a partial
failure (one file fails, another succeeds) could report the wrong
count or the failed file's name as if it had uploaded.
- docker-compose-dev.yaml: workbench's depends_on was missing
service-csv (only had service-model-csv), even though nginx and the
backend's default plugin list both reference /plugin/csv, risking a
502 on startup before service-csv is healthy.
- nginx.conf: /plugin/csv/ was missing the proxy_http_version, forwarded
headers, and cross-origin headers every other plugin location has.
- service-model: register the generated model language (.m_gen), which
the platform still produces (e.g. optimizer solutions) and
language-model still supports, but this service no longer wired up.
- modelDataHandler: a CSV import present in a .m file was replacing all
hand-authored objects/links instead of merging with them.
- Moved the quote-aware CSV parser out of language-model-csv into
language-shared so language-model's live diagram rendering
(modelGModelFactory) uses the same correct parser instead of a naive
split(",")/split("\n") that miscounts rows on a quoted comma or
newline.
Verified live: CSV upload, mixed manual + CSV data, and a quoted CSV
value all render correctly.
|
Addressed the new round of Copilot comments:
Verified live: CSV upload, mixed manual + CSV data in the same file, and the plugin registrations all work correctly. |
Closes #21
Depends on #23 (branched from amir/csv-import, since this needs .csv registered as a first-class file type). Only the last commit (a81b3fc) is new to this PR; the rest is #23's history. Marking as draft until #23 merges, at which point I'll rebase this onto main.
What this adds
Previously the only way to get a CSV into a project was creating an empty file through the workbench text editor and typing/pasting into it. This adds:
Both paths go through the same upload helper: read the file's text, create it through the existing file service (the same one file creation already uses), and open it in a new tab. Non-.csv files are rejected with a toast instead of silently uploaded; a name collision shows a clear error instead of overwriting.
Verified
Tested both upload paths (root and nested folder), drag-and-drop onto the tree, rejection of a non-.csv file, and the duplicate-name error, all in the running app.