Skip to content

feat: flyway style migration - #3500

Open
ppatel9703 wants to merge 6 commits into
devfrom
migration-flyway-poc
Open

feat: flyway style migration#3500
ppatel9703 wants to merge 6 commits into
devfrom
migration-flyway-poc

Conversation

@ppatel9703

@ppatel9703 ppatel9703 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a Flyway-style Migrations tab to the engine UI so admins can view migration history/status for a database engine and author + run new versioned SQL migrations without leaving SEMOSS. This is the UI counterpart to the migration engine added in SEMOSS/Semoss#2822 — see How It Ties Together below for the full request lifecycle across both PRs.

Changes Made

Tab gating (engine-layout.tsx) — Adds a second usePixel<boolean> call alongside the existing getDatabaseCategory one, scoped to DATABASE engines only:

const getMigrationsEnabled = usePixel<boolean>(
  engineId && route.type === "DATABASE"
    ? `GetEngineMigrationsEnabled(engine=["${engineId}"]);`
    : "",
  { data: false },
);

The empty-string query when not a DATABASE engine short-circuits the pixel call and defaults to false. The existing tab-filter logic (already scoped to route.type === "DATABASE") gains a third branch alongside the Query/SPARQL checks: if (t.path === "migrations") return migrationsEnabled === true;. The page's loading gate was extended to block on both pixel calls (getDatabaseCategory.status !== "SUCCESS" || getMigrationsEnabled.status !== "SUCCESS") before rendering, so the tab list doesn't flicker in before the flag resolves.

engine.constants.ts — New ENGINE_ROUTES entry for Migrations (path: "migrations"), inserted after sparql-query, with restrict: ["READ_ONLY", "EDIT", "OWNER"]. Read-only users can see the tab (to view history); write actions are gated separately in the page component.

Migrations page (engine-migrations.page.tsx, refactored from an initial engine-migrations-page.tsx) — Calls ListEngineMigrations(engine=["${active.id}"]); via usePixel. Handles loading (spinner), error (surfaces the pixel error message), and empty states ("No migrations found for this engine"). Table columns: Version, Description, File, State (badge), Applied By, Applied On, Execution Time (formatted as Xms under 1s, else X.XXs), and Notes (truncated error message). No optimistic updates — a manual Refresh button and the "New Migration" dialog's close callback both trigger a full listMigrations.refresh(). Adds a canEdit = active.role === "EDIT" || active.role === "OWNER" check (mirroring the backend's userCanEditEngine) that gates both the "New Migration" button and whether the dialog is even mounted, so the UI never offers an action that would fail server-side.

engine-migration-state-badge.tsx — Maps each of the five states to a badge: SUCCESS and FAILED use semantic Badge variants (outline/destructive); OUTDATED and MISSING fall back to raw Tailwind color classes (amber/orange) since no semantic "warning" token exists yet in the design system — called out in-code as a known gap; default/PENDING renders an outline "Pending" badge.

engine-migrations-summary.tsx — Computes and renders a 6-stat summary row above the table: Current Version (highest version among SUCCESS/OUTDATED rows, using a compareVersions helper that numerically compares dotted segments — mirrors the Java-side version comparison exactly) plus counts of Applied, Pending, Failed, Outdated, and Missing rows. Only rendered when there's at least one migration.

engine-new-migration-dialog.tsx — Form with Description and SQL (monospace textarea) fields. Client-side validation requires both non-empty before any network call, with an inline error toast otherwise. On submit, calls:

SaveEngineMigration(engine=["${active.id}"], sql=[${JSON.stringify(sqlContent)}], description=[${JSON.stringify(description)}]);

and checks the pixel errors array first, then result.success === false, throwing the returned errorMessage in either failure case. On success: toast confirming Migration V${version} applied successfully, form reset, and onClose(true) (parent refreshes the table). On failure: error toast, form state preserved so the user can fix and retry without retyping the SQL. A submitting flag disables both buttons and shows an inline spinner. Note: there is no confirmation step before running — "Save" writes the file and executes the SQL immediately, matching the backend's own immediate-execution behavior.

index.ts — Barrel-exports the three new components (engine-migration-state-badge, engine-migrations-summary, engine-new-migration-dialog).

How It Ties Together

  1. Engine page loads → GetEngineMigrationsEnabled is called → the Migrations tab only renders if the engine's smss has ENABLE_MIGRATIONS=true (set server-side, see the paired backend PR).
  2. Tab opens → ListEngineMigrations is called → the backend's MigrationStatusUtils.getStatus() reconciles the engine's assets/.migrations folder against its SEMOSS_SCHEMA_HISTORY table and returns one row per migration version with a computed state.
  3. User opens "New Migration", enters a description + SQL, and saves → SaveEngineMigration writes the versioned file server-side, runs it immediately, and returns success/failure → this dialog surfaces that result via toast and the parent page re-fetches the list.
  4. The frontend's local MigrationState type (PENDING | SUCCESS | FAILED | MISSING | OUTDATED) is a literal mirror of the backend's MigrationStatus states — both sides comment the contract explicitly so the enums can't silently drift apart.
  5. Migrations can also be applied outside the UI entirely (e.g. deployed alongside a release and picked up on the engine's next open()); this UI is a read/author surface on top of that, not the only way migrations run.

How to Test

  1. Enable ENABLE_MIGRATIONS=true on a database engine's smss (requires the backend PR).
  2. Open that engine and confirm the "Migrations" tab appears only for DATABASE engines with the flag set — not for other engine types or engines without it.
  3. Confirm a read-only user can see the tab and history, but the "New Migration" button/dialog is hidden or disabled (only EDIT/OWNER roles can author).
  4. Open the tab and confirm the summary stats and table render correctly against real migration history (versions, states, applied by/on, execution time).
  5. Use "New Migration" to submit a valid SQL statement and confirm it runs immediately, shows a success toast, and appears in the refreshed table as SUCCESS.
  6. Submit an invalid SQL statement and confirm a FAILED state/error toast, and that the form retains your input so you can correct and resubmit.
  7. Force an OUTDATED or MISSING state on the backend and confirm the badge/summary counts reflect it correctly.

Notes

  • Depends on / pairs with SEMOSS/Semoss#2822, which adds the GetEngineMigrationsEnabled, ListEngineMigrations, and SaveEngineMigration reactors this UI calls.
  • Known gap: no semantic "warning" badge token exists yet for OUTDATED/MISSING states, so those fall back to raw Tailwind colors instead of design-system tokens.

@snyk-io

snyk-io Bot commented Jul 29, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@ppatel9703
ppatel9703 marked this pull request as ready for review August 3, 2026 12:46
@ppatel9703
ppatel9703 requested a review from a team as a code owner August 3, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants