Create all type: model files in models/ instead of sources/#8811
Create all type: model files in models/ instead of sources/#8811lovincyrus merged 11 commits intomainfrom
type: model files in models/ instead of sources/#8811Conversation
royendo
left a comment
There was a problem hiding this comment.
barring fail e2e, looks good to me
60a5975 to
abe9669
Compare
ericpgreen2
left a comment
There was a problem hiding this comment.
isSourceModel heuristic can false-positive on YAML-only models
In file-and-resource-watcher.ts:368-369, the source detection changed from filePaths[0]?.startsWith("/sources/") to:
const isSourceModel =
filePath?.startsWith("/models/") && filePath?.endsWith(".yaml");This matches any YAML file in /models/, but not all of those are sources. A model defined entirely in YAML (like the incremental model in the test suite) would also match. Combined with the other conditions in isNewSource — specVersion === "1", stateVersion === "2", and isLeafResource — a YAML-only leaf model would incorrectly trigger the "Source imported successfully" modal on first reconciliation.
The runtime already tags sources with defined_as_source: true in parse_source.go:35. If that flag is available on the model spec in the frontend, checking it here would be more precise than relying on the file extension.
Developed in collaboration with Claude Code
When submitting the explorer step in multi-step connectors, model files are now created in models/ instead of sources/, ensuring consistency with the file type designation (type: model in YAML).
Consolidate model file creation across all code paths: - submitAddSourceForm (multi-step connector source step) - createSource (local file uploads and drag-and-drop) - Local file picker and drag-and-drop navigation This ensures consistency: all YAML files with type: model are created in models/ instead of sources/.
Centralize EntityType.Table mapping to models/ in getFilePathFromNameAndType and getFileAPIPathFromNameAndType. Restore all call sites to use the mapper instead of hardcoded paths. Single source of truth for file paths.
Update all test file paths from /sources/ to /models/ for Table/Source entity types. Fix file-and-resource-watcher.ts to check /models/ instead of /sources/ when detecting newly imported sources, which enables the "View this source" modal to appear after file upload. Also fix getFilePathFromPagePath() in entity-mappers.ts for consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Check that filePath ends with .yaml to avoid matching directory paths when detecting imported sources in the models/ directory.
Source files now live in /models/ instead of /sources/, but should retain manual save behavior for safety (they contain credentials and connections). Add isFileWithoutAutosave() function to handle the new location while preserving auto-save for SQL model files.
The incremental model test now requires a manual save since YAML files in /models/ have auto-save disabled. This ensures the model is persisted before waitForProfiling checks for API responses.
Replace file-path-based isSourceModel check with the runtime's definedAsSource flag, which precisely identifies sources without false-positiving on YAML-only models in /models/.
2de0a57 to
ebade47
Compare
Question: How should the watcher detect newly-imported sources?The e2e test BackgroundThe const isSourceModel = res.resource.meta?.filePaths?.[0]?.startsWith("/sources/");Since this branch moves source files to res.resource.model?.spec?.definedAsSource === trueThe problemThe YAML generated by So Options
Thoughts on which approach is best? @ericpgreen2 Update: resolved offline. |
Replace the definedAsSource check with a pendingSourceImports set that the UI registers before writing source files. This avoids false positives on YAML-only models and eliminates the need for fragile heuristics. The UI now explicitly tells the watcher which files are source imports via three entry points: submitAddSourceForm, createSource (used by LocalSourceUpload and FileDrop), and the watcher clears the path from the set when the modal is triggered.
ericpgreen2
left a comment
There was a problem hiding this comment.
The path migration from /sources/ to /models/ looks complete — no stale references remain. The pendingSourceImports approach is a better signal than the old startsWith("/sources/") heuristic. Two things worth addressing:
1. Missing cleanup in createSource.ts
createSource adds to pendingSourceImports before calling runtimeServicePutFile but never cleans up on failure. If the PUT throws, the entry leaks in the Set. Compare with submitAddDataForm.ts which correctly cleans up in both error paths.
The callers (FileDrop.svelte, LocalSourceUpload.svelte) catch errors but don't clean up either.
2. pendingSourceImports and sourceImportedPath are implicitly coupled
This PR introduces pendingSourceImports as new state that's tightly coupled to the existing sourceImportedPath — the watcher has to remember to call delete on one and set on the other in lockstep, and error paths have to delete without setting. That coupling is invisible and easy to get wrong (see issue 1).
Since you're introducing new state here anyway, consider synthesizing both into a single class that owns the full ingestion lifecycle:
class SourceIngestionTracker {
private pending = new Set<string>();
public ingestedPath = writable<string | null>(null);
trackPending(filePath: string) {
this.pending.add(filePath);
}
isPending(filePath: string) {
return this.pending.has(filePath);
}
/** Ingestion finished — remove from pending and notify the UI */
trackIngested(filePath: string) {
this.pending.delete(filePath);
this.ingestedPath.set(filePath);
}
/** Source creation was rolled back */
trackCancelled(filePath: string) {
this.pending.delete(filePath);
}
/** User dismissed the success modal */
dismiss() {
this.ingestedPath.set(null);
}
}This makes the transitions explicit — trackIngested vs trackCancelled — so it's impossible to forget half the pair. The watcher calls tracker.trackIngested(filePath) instead of two separate operations, and error paths call tracker.trackCancelled(filePath) with clear intent.
The naming also aligns with the domain language the UI already uses ("has been ingested") and replaces the more ambiguous "imported" terminology.
…estionTracker Replace the implicitly coupled `pendingSourceImports` Set and `sourceImportedPath` writable with a single `SourceIngestionTracker` class that owns the full ingestion lifecycle. This makes state transitions explicit (trackPending → trackIngested vs trackCancelled) and fixes a leak where `createSource` never cleaned up pending entries on failure.
This PR creates all type: model files in
models/instead ofsources/Checklist: