Skip to content

Commit

Permalink
Check for pending migrations when watcher is fired (#504)
Browse files Browse the repository at this point in the history
Check for pending migrations, notify the user and offer to run them. The idea is that if any modifications are made to the `db/migrate` directory, we check if there are pending migrations.

If there are, we show a dialog and offers to run them. If the user decides to do it, we then fire a request to the server to run the migrations.

Note: we only show the dialog if a migration is deleted or created, otherwise it can become really annoying when you are editing the migration you just created.
  • Loading branch information
vinistock authored Nov 5, 2024
1 parent 2a3558b commit 2267360
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 6 deletions.
101 changes: 95 additions & 6 deletions lib/ruby_lsp/ruby_lsp_rails/addon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ module Rails
class Addon < ::RubyLsp::Addon
extend T::Sig

RUN_MIGRATIONS_TITLE = "Run Migrations"

sig { void }
def initialize
super
Expand All @@ -37,6 +39,7 @@ def initialize
@addon_mutex.synchronize do
# We need to ensure the Rails client is fully loaded before we activate the server addons
@client_mutex.synchronize { @rails_runner_client = RunnerClient.create_client(T.must(@outgoing_queue)) }
offer_to_run_pending_migrations
end
end
end
Expand Down Expand Up @@ -119,11 +122,80 @@ def create_definition_listener(response_builder, uri, node_context, dispatcher)

sig { params(changes: T::Array[{ uri: String, type: Integer }]).void }
def workspace_did_change_watched_files(changes)
if changes.any? do |change|
change[:uri].end_with?("db/schema.rb") || change[:uri].end_with?("structure.sql")
end
if changes.any? { |c| c[:uri].end_with?("db/schema.rb") || c[:uri].end_with?("structure.sql") }
@rails_runner_client.trigger_reload
end

if changes.any? do |c|
%r{db/migrate/.*\.rb}.match?(c[:uri]) && c[:type] != Constant::FileChangeType::CHANGED
end

offer_to_run_pending_migrations
end
end

sig { override.returns(String) }
def name
"Ruby LSP Rails"
end

sig { override.params(title: String).void }
def handle_window_show_message_response(title)
if title == RUN_MIGRATIONS_TITLE

begin_progress("run-migrations", "Running Migrations")
response = @rails_runner_client.run_migrations

if response && @outgoing_queue
if response[:status] == 0
# Both log the message and show it as part of progress because sometimes running migrations is so fast you
# can't see the progress notification
@outgoing_queue << Notification.window_log_message(response[:message])
report_progress("run-migrations", message: response[:message])
else
@outgoing_queue << Notification.window_show_message(
"Migrations failed to run\n\n#{response[:message]}",
type: Constant::MessageType::ERROR,
)
end
end

end_progress("run-migrations")
end
end

private

sig { params(id: String, title: String, percentage: T.nilable(Integer), message: T.nilable(String)).void }
def begin_progress(id, title, percentage: nil, message: nil)
return unless @global_state&.client_capabilities&.supports_progress && @outgoing_queue

@outgoing_queue << Request.new(
id: "progress-request-#{id}",
method: "window/workDoneProgress/create",
params: Interface::WorkDoneProgressCreateParams.new(token: id),
)

@outgoing_queue << Notification.progress_begin(
id,
title,
percentage: percentage,
message: "#{percentage}% completed",
)
end

sig { params(id: String, percentage: T.nilable(Integer), message: T.nilable(String)).void }
def report_progress(id, percentage: nil, message: nil)
return unless @global_state&.client_capabilities&.supports_progress && @outgoing_queue

@outgoing_queue << Notification.progress_report(id, percentage: percentage, message: message)
end

sig { params(id: String).void }
def end_progress(id)
return unless @global_state&.client_capabilities&.supports_progress && @outgoing_queue

@outgoing_queue << Notification.progress_end(id)
end

sig { params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
Expand Down Expand Up @@ -152,9 +224,26 @@ def register_additional_file_watchers(global_state:, outgoing_queue:)
)
end

sig { override.returns(String) }
def name
"Ruby LSP Rails"
sig { void }
def offer_to_run_pending_migrations
return unless @outgoing_queue
return unless @global_state&.client_capabilities&.window_show_message_supports_extra_properties

migration_message = @rails_runner_client.pending_migrations_message
return unless migration_message

@outgoing_queue << Request.new(
id: "rails-pending-migrations",
method: "window/showMessageRequest",
params: {
type: Constant::MessageType::INFO,
message: migration_message,
actions: [
{ title: RUN_MIGRATIONS_TITLE, addon_name: name, method: "window/showMessageRequest" },
{ title: "Cancel", addon_name: name, method: "window/showMessageRequest" },
],
},
)
end
end
end
Expand Down
43 changes: 43 additions & 0 deletions test/ruby_lsp_rails/addon_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,49 @@ class AddonTest < ActiveSupport::TestCase
addon = Addon.new
addon.workspace_did_change_watched_files(changes)
end

test "handling window show message response to run migrations" do
RunnerClient.any_instance.expects(:run_migrations).once.returns({ message: "Ran migrations!", status: 0 })
outgoing_queue = Thread::Queue.new
global_state = GlobalState.new
global_state.apply_options({ capabilities: { window: { workDoneProgress: true } } })

addon = Addon.new
addon.activate(global_state, outgoing_queue)

# Wait until activation is done
Thread.new do
addon.rails_runner_client
end.join

addon.handle_window_show_message_response("Run Migrations")

progress_request = pop_message(outgoing_queue) { |message| message.is_a?(Request) }
assert_instance_of(Request, progress_request)

progress_begin = pop_message(outgoing_queue) do |message|
message.is_a?(Notification) && message.method == "$/progress"
end
assert_equal("begin", progress_begin.params.value.kind)

report_log = pop_message(outgoing_queue) do |message|
message.is_a?(Notification) && message.method == "window/logMessage"
end
assert_equal("Ran migrations!", report_log.params.message)

progress_report = pop_message(outgoing_queue) do |message|
message.is_a?(Notification) && message.method == "$/progress"
end
assert_equal("report", progress_report.params.value.kind)
assert_equal("Ran migrations!", progress_report.params.value.message)

progress_end = pop_message(outgoing_queue) do |message|
message.is_a?(Notification) && message.method == "$/progress"
end
assert_equal("end", progress_end.params.value.kind)
ensure
T.must(outgoing_queue).close
end
end
end
end
8 changes: 8 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,13 @@ def pop_log_notification(message_queue, type)
log = message_queue.pop until log.params.type == type
log
end

def pop_message(outgoing_queue, &block)
message = outgoing_queue.pop
return message if block.call(message)

message = outgoing_queue.pop until block.call(message)
message
end
end
end

0 comments on commit 2267360

Please sign in to comment.