feat(import): make SaaS→self-host import work + dup guard + regression test#31
Merged
Conversation
The documented two-step (`adapt-cli-export.ts` → `POST /api/import`) was broken in v1.7.1 and earlier: the adapter emitted snake_case (`url_monitors`, `status_code`, `timeout_ms`) but `/api/import` reads camelCase (`urlMonitors`, `statusCode`, `timeoutMs`), so it imported **zero rows, silently**. SaaS→self-host onboarding has never functioned. - adapt-cli-export.ts: rewritten as an importable `adaptSaaSExport()` emitting exactly what `/api/import` consumes; thin CLI kept. Reports SaaS resources with no self-host mapping (suites/heartbeats/alert channels/status pages/incidents) as skipped, not silently dropped. - scripts/import-from-saas.ts: one-shot wrapper — `obs export` (or `--from <file>`) → in-process adapt → authed POST. `--dry-run`. - Idempotency: there's no unique-name constraint, so re-import would silently DUPLICATE (not skip, as the old plan assumed). Added a pre-flight name-collision check that refuses (exit 1) unless `--allow-duplicates`. Non-atomic by design — catches the realistic re-run footgun, documented. - docs/import-from-saas.md + README pointer. Verified end-to-end against a live stack: clean import creates rows (was zero), re-run refuses with exit 1 (no duplicate), --allow-duplicates overrides, --dry-run needs no key. Patch → 1.7.2 (scripts ship in the image; the documented workflow was broken on :latest). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/import-from-saas-test.ts — pure, no DB/server. Asserts adaptSaaSExport() output is exactly the camelCase shape /api/import consumes, and that the snake_case keys which caused the silent zero-import regression never reappear. Wired into run-integration.sh (pre-push + CI) and `bun run test:import`. Anti-vacuous: regressing the adapter back to snake_case fails the `camelCase top-level keys` and `no snake_case keys leak` checks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
samir1498
added a commit
that referenced
this pull request
May 26, 2026
…onsistency (#88) Three small UX fixes batched together. 1. Settings → API keys: clicking "I've copied it" used to be a visual no-op. The reveal-panel host's innerHTML was populated when oneTimeKey was set but never cleared on the next render. Add an explicit `if (!oneTimeKey && host) host.innerHTML = '';` so the panel goes away when dismissed. 2. Import JSON dialog: drop the `www.` from the observeone.com/cli link. Aligns with README (which already uses bare apex) so every operator- facing URL in the repo points at the same canonical host. 3. Heartbeat detail page back-link reads "← back" instead of "← All monitors", matching the other 7 monitor types' detail pages. Test: tests/ui/api-key-dismiss.e2e.spec.ts creates a key, asserts the reveal panel is visible, clicks dismiss, asserts the cleartext element is hidden afterward. Closes friction #13, #31, #32. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The documented two-step
adapt-cli-export.ts→POST /api/importwas broken in v1.7.1 and prior — the adapter emitted snake_case (url_monitors,status_code,timeout_ms) while/api/importreads camelCase (urlMonitors,statusCode,timeoutMs), so SaaS→self-host onboarding imported zero rows, silently. Scripts ship in the Docker image and this is the documented workflow, so it's been broken on:latest.What
adapt-cli-export.tsrewritten as an importableadaptSaaSExport()emitting exactly what/api/importconsumes; thin CLI kept. SaaS-only resources (suites/heartbeats/alert channels/status pages/incidents) are reported as skipped, not silently dropped.scripts/import-from-saas.ts— the one-shot wrapper:obs export(or--from <file>) → in-process adapt → authedPOST.--dry-run(no key/DB).--allow-duplicates. Non-atomic by design — catches the realistic re-run footgun; documented.scripts/import-from-saas-test.ts— pure contract regression guard (no DB/server): asserts the adapter output is exactly/api/import's shape and the snake_case keys never reappear. Wired into the integration suite +bun run test:import. Anti-vacuous: regressing to snake_case fails it.docs/import-from-saas.md+ README pointer.Verified
End-to-end against a live stack: clean import creates rows (was zero), re-run refuses with exit 1 (no duplicate),
--allow-duplicatesoverrides,--dry-runneeds no key. Contract test passes; negative control proves it catches the exact regression. Pre-push suite green incl. the new stage.Scope: minimal working importer (HTTP + API checks). Full parity (QA suites / channels / status pages / db monitors → import) is a deliberate follow-up. Distinct from backup/restore (that's instance→instance DR, not SaaS migration). Patch → 1.7.2 (the shipped, documented workflow was broken on
:latest).🤖 Generated with Claude Code