Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for pending migrations when watcher is fired #504

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading