@@ -12,6 +12,7 @@ defmodule RealtimeWeb.Dashboard.TenantMigrations do
1212 alias Realtime.Api
1313 alias Realtime.Api.Tenant
1414 alias Realtime.Database
15+ alias Realtime.Tenants.Migrations
1516
1617 @ pg_delta_filter ~s"""
1718 {
@@ -84,17 +85,9 @@ defmodule RealtimeWeb.Dashboard.TenantMigrations do
8485 end
8586
8687 @ impl true
87- def handle_event (
88- "apply_plan" ,
89- _params ,
90- % {
91- assigns: % {
92- tenant: % Tenant { } = tenant ,
93- external_id: ref ,
94- pg_delta: { :ok , % { status: :changes , sql: sql } }
95- }
96- } = socket
97- ) do
88+ def handle_event ( "apply_plan" , _params , socket ) do
89+ % { tenant: % Tenant { } = tenant , external_id: ref , pg_delta: { :ok , % { status: :changes , sql: sql } } } = socket . assigns
90+
9891 case apply_pg_delta ( tenant , sql ) do
9992 :ok ->
10093 { :noreply , push_patch ( socket , to: "/admin/dashboard/tenant_migrations?external_id=#{ URI . encode ( ref ) } " ) }
@@ -104,6 +97,19 @@ defmodule RealtimeWeb.Dashboard.TenantMigrations do
10497 end
10598 end
10699
100+ @ impl true
101+ def handle_event ( "backfill_migrations" , _params , socket ) do
102+ % { tenant: % Tenant { } = tenant , external_id: ref , pg_delta: { :ok , % { status: :no_changes } } } = socket . assigns
103+
104+ case backfill_schema_migrations ( tenant ) do
105+ :ok ->
106+ { :noreply , push_patch ( socket , to: "/admin/dashboard/tenant_migrations?external_id=#{ URI . encode ( ref ) } " ) }
107+
108+ { :error , msg } ->
109+ { :noreply , assign ( socket , error: msg ) }
110+ end
111+ end
112+
107113 @ impl true
108114 def render ( assigns ) do
109115 ~H"""
@@ -136,6 +142,8 @@ defmodule RealtimeWeb.Dashboard.TenantMigrations do
136142
137143 < h6 class = "mt-4 " > pg-delta plan vs baseline</ h6 >
138144 <%= pg_delta_plan ( @ pg_delta ) %>
145+
146+ <%= backfill_section ( @ pg_delta , @ schema_migrations ) %>
139147 <% end %>
140148 </ div >
141149 """
@@ -256,6 +264,76 @@ defmodule RealtimeWeb.Dashboard.TenantMigrations do
256264 """
257265 end
258266
267+ defp backfill_section ( pg_delta , schema_migrations ) do
268+ total = length ( Migrations . migrations ( ) )
269+
270+ { show , applied } =
271+ case { pg_delta , schema_migrations } do
272+ { { :ok , % { status: :no_changes } } , { :ok , rows } } ->
273+ applied = length ( rows )
274+ { applied < total , applied }
275+
276+ _ ->
277+ { false , nil }
278+ end
279+
280+ assigns = % { show: show , applied: applied , total: total }
281+
282+ ~H"""
283+ < div :if = { @ show } >
284+ < h6 class = "mt-4 " > Backfill schema_migrations</ h6 >
285+ < div class = "alert alert-warning " >
286+ realtime.schema_migrations has <%= @ applied %> of <%= @ total %> versions applied and pg-delta reports no drift.
287+ Backfill inserts the missing rows and sets tenants.migrations_ran to <%= @ total %> .
288+ </ div >
289+ < div class = "d-flex justify-content-end mt-3 " >
290+ < button
291+ type = "button "
292+ class = "btn btn-primary "
293+ phx-click = "backfill_migrations "
294+ phx-disable-with = "Backfilling... "
295+ data-confirm = { "Insert missing version(s) into realtime.schema_migrations and set tenants.migrations_ran to #{ @ total } ?" }
296+ >
297+ Backfill schema_migrations
298+ </ button >
299+ </ div >
300+ </ div >
301+ """
302+ end
303+
304+ @ doc false
305+ def backfill_schema_migrations ( % Tenant { external_id: external_id } = tenant ) do
306+ versions = Enum . map ( Migrations . migrations ( ) , fn { v , _mod } -> v end )
307+ total = length ( versions )
308+
309+ with { :ok , settings } <- Database . from_tenant ( tenant , @ application_name , :stop ) ,
310+ { :ok , db_conn } <- Database . connect_db ( settings ) ,
311+ :ok <- insert_versions ( db_conn , versions ) ,
312+ { :ok , _ } <- Api . update_migrations_ran ( external_id , total ) do
313+ :ok
314+ else
315+ { :error , % { postgres: % { message: message } } } ->
316+ log_warning ( "TenantMigrationsBackfillFailed" , message )
317+ { :error , "Backfill failed: #{ message } " }
318+
319+ { :error , reason } ->
320+ log_warning ( "TenantMigrationsBackfillFailed" , reason )
321+ { :error , "Backfill failed: #{ inspect ( reason ) } " }
322+ end
323+ end
324+
325+ defp insert_versions ( db_conn , versions ) do
326+ backfill_insert =
327+ "INSERT INTO realtime.schema_migrations (version, inserted_at) VALUES ($1, NOW()) ON CONFLICT (version) DO NOTHING"
328+
329+ Enum . reduce_while ( versions , :ok , fn version , _acc ->
330+ case Postgrex . query ( db_conn , backfill_insert , [ version ] , timeout: @ query_timeout ) do
331+ { :ok , _ } -> { :cont , :ok }
332+ { :error , _ } = err -> { :halt , err }
333+ end
334+ end )
335+ end
336+
259337 defp assign_error ( socket , ref , msg ) do
260338 assign ( socket ,
261339 external_id: ref ,
@@ -346,12 +424,17 @@ defmodule RealtimeWeb.Dashboard.TenantMigrations do
346424 end
347425 end
348426
349- defp apply_pg_delta ( % Tenant { } = tenant , sql ) do
427+ @ doc false
428+ def apply_pg_delta ( % Tenant { external_id: external_id } = tenant , sql ) do
350429 opts = [ query_type: :text , timeout: @ query_timeout ]
430+ versions = Enum . map ( Migrations . migrations ( ) , fn { v , _mod } -> v end )
431+ total = length ( versions )
351432
352433 with { :ok , settings } <- Database . from_tenant ( tenant , @ application_name , :stop ) ,
353434 { :ok , db_conn } <- Database . connect_db ( settings ) ,
354- { :ok , _ } <- Postgrex . query ( db_conn , sql , [ ] , opts ) do
435+ { :ok , _ } <- Postgrex . query ( db_conn , sql , [ ] , opts ) ,
436+ :ok <- insert_versions ( db_conn , versions ) ,
437+ { :ok , _ } <- Api . update_migrations_ran ( external_id , total ) do
355438 :ok
356439 else
357440 { :error , % { postgres: % { message: message } } } ->
0 commit comments