maintenance_tasks 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f05710c874610ef0698738773b70923d2e6a4ec4ae4b452ac768b6bf2e696d2c
4
- data.tar.gz: 2ab31dae65aa383d5edbccee747199960f5167cd33833230c044fd00f0ad6cfc
3
+ metadata.gz: 347d742ae5f71881fe2d54f7fff83b4129144eb72af60c2364a8069fc41412fe
4
+ data.tar.gz: 20ff959d1c2db4a003883955ef938414a462f16fa843d8f4ec526ff3333a0609
5
5
  SHA512:
6
- metadata.gz: 8f0f7eee33cc6615f658f49ec9d74322f7952f2afa47b4b539dd0a49131cea10df15f072402d51d611bbfbf4d8d297ace54da1896ab457e54280bc17284641c9
7
- data.tar.gz: af3992b2c6de8c8b70d840d8c06d6a8cb2646063a9b23222a1814bfc1cc7866a9745277aafe32ef8ee48627b7916e51c672c9321be87a49a5efd443f353722fc
6
+ metadata.gz: 7f9e56bd030e2a6918d7e8e136108d6097cbcdaa8b73ebb9683072878f8789cb0b7da1b67c122d7dddf97553ca3dcb37b4e1af7670cbdf931825ec965cbbf9af
7
+ data.tar.gz: 384ec37a77b25d689ff46f965fd6cddd4ddc7669f520a99db808edc76c290c21ed8925b5d791839dd0d61738da6b944267fa65535b91ebd4ee6d8913293bb824
data/README.md CHANGED
@@ -90,6 +90,13 @@ take a look at the [Active Job documentation][active-job-docs].
90
90
  [async-adapter]: https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html
91
91
  [active-job-docs]: https://guides.rubyonrails.org/active_job_basics.html#setting-the-backend
92
92
 
93
+ ### Action Controller & Action View Dependency
94
+
95
+ The Maintenance Tasks framework relies on Action Controller and Action View to
96
+ render the UI. If you're using Rails in API-only mode, see [Using Maintenance
97
+ Tasks in API-only
98
+ applications](#using-maintenance-tasks-in-api-only-applications).
99
+
93
100
  ### Autoloading
94
101
 
95
102
  The Maintenance Tasks framework does not support autoloading in `:classic` mode.
@@ -888,6 +895,42 @@ a Task can be in:
888
895
  * **succeeded**: A Task that finished successfully.
889
896
  * **errored**: A Task that encountered an unhandled exception while performing.
890
897
 
898
+ ### Using Maintenance Tasks in API-only applications
899
+
900
+ The Maintenance Tasks engine uses Rails sessions for flash messages and storing
901
+ the CSRF token. For the engine to work in an API-only Rails application, you
902
+ need to add a [session middleware][] and the `ActionDispatch::Flash`
903
+ middleware. The engine also defines a strict [Content Security Policy][], make
904
+ sure to include `ActionDispatch::ContentSecurityPolicy::Middleware` in your
905
+ app's middleware stack to ensure the CSP is delivered to the user's browser.
906
+
907
+ [session middleware]: https://guides.rubyonrails.org/api_app.html#using-session-middlewares
908
+ [Content Security Policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
909
+
910
+ Configuring Rails applications is beyond the scope of this documentation, but
911
+ one way to do this is to add these lines to your application configuration:
912
+
913
+ ```ruby
914
+ # config/application.rb
915
+ module YourApplication
916
+ class Application < Rails::Application
917
+ # ...
918
+ config.api_only = true
919
+
920
+ config.middleware.insert_before ::Rack::Head, ::ActionDispatch::Flash
921
+ config.middleware.insert_before ::Rack::Head, ::ActionDispatch::ContentSecurityPolicy::Middleware
922
+ config.session_store :cookie_store, key: "_#{railtie_name.chomp("_application")}_session", secure: true
923
+ config.middleware.insert_before ::ActionDispatch::Flash, config.session_store, config.session_options
924
+ config.middleware.insert_before config.session_store, ActionDispatch::Cookies
925
+ end
926
+ end
927
+ ```
928
+
929
+ You can read more in the [Using Rails for API-only Applications][rails api] Rails
930
+ guide.
931
+
932
+ [rails api]: https://guides.rubyonrails.org/api_app.html
933
+
891
934
  ### How Maintenance Tasks runs a Task
892
935
 
893
936
  Maintenance tasks can be running for a long time, and the purpose of the gem is
@@ -1145,7 +1188,7 @@ The value for `MaintenanceTasks.stuck_task_duration` must be an
1145
1188
  `ActiveSupport::Duration`. If no value is specified, it will default to 5
1146
1189
  minutes.
1147
1190
 
1148
- ### Metadata
1191
+ #### Metadata
1149
1192
 
1150
1193
  `MaintenanceTasks.metadata` can be configured to specify a proc from which to
1151
1194
  get extra information about the run. Since this proc will be ran in the context
@@ -6,6 +6,7 @@ module MaintenanceTasks
6
6
  #
7
7
  # @api private
8
8
  class RunsController < ApplicationController
9
+ helper TasksHelper
9
10
  before_action :set_run, except: :create
10
11
 
11
12
  # Creates a Run for a given Task and redirects to the Task page.
@@ -19,7 +20,9 @@ module MaintenanceTasks
19
20
  )
20
21
  redirect_to(task_path(task))
21
22
  rescue ActiveRecord::RecordInvalid => error
22
- redirect_to(task_path(error.record.task_name), alert: error.message)
23
+ flash.now.alert = error.message
24
+ @task = TaskDataShow.prepare(error.record.task_name, arguments: error.record.arguments)
25
+ render(template: "maintenance_tasks/tasks/show")
23
26
  rescue ActiveRecord::ValueTooLong => error
24
27
  task_name = params.fetch(:task_id)
25
28
  redirect_to(task_path(task_name), alert: error.message)
@@ -18,14 +18,11 @@ module MaintenanceTasks
18
18
  # Renders the page responsible for providing Task actions to users.
19
19
  # Shows running and completed instances of the Task.
20
20
  def show
21
- task_name = params.fetch(:id)
22
- @task = TaskDataShow.new(task_name)
23
- @task.active_runs.load
24
- set_refresh if @task.active_runs.any?
25
- @runs_page = RunsPage.new(@task.completed_runs, params[:cursor])
26
- if @task.active_runs.none? && @runs_page.records.none?
27
- Task.named(task_name)
28
- end
21
+ @task = TaskDataShow.prepare(
22
+ params.fetch(:id),
23
+ runs_cursor: params[:cursor],
24
+ arguments: params.except(:id, :controller, :action).permit!,
25
+ )
29
26
  end
30
27
 
31
28
  private
@@ -21,6 +21,9 @@ module MaintenanceTasks
21
21
  @cursor = cursor
22
22
  end
23
23
 
24
+ # @return [String, nil] the cursor for the page of Runs.
25
+ attr_reader :cursor
26
+
24
27
  # Returns the records for a Page, taking into account the cursor if one is
25
28
  # present. Limits the number of records to 20.
26
29
  #
@@ -11,17 +11,39 @@ module MaintenanceTasks
11
11
  #
12
12
  # @api private
13
13
  class TaskDataShow
14
- # Initializes a Task Data with a name and optionally a related run.
14
+ # Initializes a Task Data with a name.
15
15
  #
16
16
  # @param name [String] the name of the Task subclass.
17
- def initialize(name)
17
+ # @param runs_cursor [String, nil] the cursor for the runs page.
18
+ # @param arguments [Hash, nil] the Task arguments.
19
+ def initialize(name, runs_cursor: nil, arguments: nil)
18
20
  @name = name
21
+ @arguments = arguments
22
+ @runs_page = RunsPage.new(completed_runs, runs_cursor)
23
+ end
24
+
25
+ class << self
26
+ # Prepares a Task Data from a task name.
27
+ #
28
+ # @param name [String] the name of the Task subclass.
29
+ # @param runs_cursor [String, nil] the cursor for the runs page.
30
+ # @param arguments [Hash, nil] the Task arguments.
31
+ # @raise [Task::NotFoundError] if the Task doesn't have runs (for the given cursor) and doesn't exist.
32
+ def prepare(name, runs_cursor: nil, arguments: nil)
33
+ new(name, runs_cursor:, arguments:)
34
+ .load_active_runs
35
+ .ensure_task_exists
36
+ end
19
37
  end
20
38
 
21
39
  # @return [String] the name of the Task.
22
40
  attr_reader :name
23
41
  alias_method :to_s, :name
24
42
 
43
+ # @return [RunsPage] the current page of completed runs, based on the cursor
44
+ # passed in initialize.
45
+ attr_reader :runs_page
46
+
25
47
  # The Task's source code.
26
48
  #
27
49
  # @return [String] the contents of the file which defines the Task.
@@ -38,6 +60,11 @@ module MaintenanceTasks
38
60
  File.read(file)
39
61
  end
40
62
 
63
+ # @return [Boolean] whether the task data needs to be refreshed.
64
+ def refresh?
65
+ active_runs.any?
66
+ end
67
+
41
68
  # Returns the set of currently active Run records associated with the Task.
42
69
  #
43
70
  # @return [ActiveRecord::Relation<MaintenanceTasks::Run>] the relation of
@@ -79,12 +106,34 @@ module MaintenanceTasks
79
106
  end
80
107
  end
81
108
 
82
- # @return [MaintenanceTasks::Task, nil] an instance of the Task class.
109
+ # @return [MaintenanceTasks::Task] an instance of the Task class.
83
110
  # @return [nil] if the Task file was deleted.
84
111
  def new
85
112
  return if deleted?
86
113
 
87
- MaintenanceTasks::Task.named(name).new
114
+ task = MaintenanceTasks::Task.named(name).new
115
+ begin
116
+ task.assign_attributes(@arguments) if @arguments
117
+ rescue ActiveModel::UnknownAttributeError
118
+ # nothing to do
119
+ end
120
+ task
121
+ end
122
+
123
+ # Preloads the records from the active_runs ActiveRecord::Relation
124
+ # @return [self]
125
+ def load_active_runs
126
+ active_runs.load
127
+ self
128
+ end
129
+
130
+ # @raise [Task::NotFoundError] if the Task doesn't have Runs (for the given cursor) and doesn't exist.
131
+ # @return [self]
132
+ def ensure_task_exists
133
+ if active_runs.none? && runs_page.records.none?
134
+ Task.named(name)
135
+ end
136
+ self
88
137
  end
89
138
 
90
139
  private
@@ -23,23 +23,23 @@
23
23
 
24
24
  <div class="buttons">
25
25
  <% if run.paused? %>
26
- <%= button_to 'Resume', resume_task_run_path(@task, run), method: :put, class: 'button is-primary', disabled: @task.deleted? %>
27
- <%= button_to 'Cancel', cancel_task_run_path(@task, run), method: :put, class: 'button is-danger' %>
26
+ <%= button_to 'Resume', resume_task_run_path(@task, run), class: 'button is-primary', disabled: @task.deleted? %>
27
+ <%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger' %>
28
28
  <% elsif run.errored? %>
29
- <%= button_to 'Resume', resume_task_run_path(@task, run), method: :put, class: 'button is-primary', disabled: @task.deleted? %>
29
+ <%= button_to 'Resume', resume_task_run_path(@task, run), class: 'button is-primary', disabled: @task.deleted? %>
30
30
  <% elsif run.cancelling? %>
31
31
  <% if run.stuck? %>
32
- <%= button_to 'Cancel', cancel_task_run_path(@task, run), method: :put, class: 'button is-danger', disabled: @task.deleted? %>
32
+ <%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger', disabled: @task.deleted? %>
33
33
  <% end %>
34
34
  <% elsif run.pausing? %>
35
- <%= button_to 'Pausing', pause_task_run_path(@task, run), method: :put, class: 'button is-warning', disabled: true %>
36
- <%= button_to 'Cancel', cancel_task_run_path(@task, run), method: :put, class: 'button is-danger' %>
35
+ <%= button_to 'Pausing', pause_task_run_path(@task, run), class: 'button is-warning', disabled: true %>
36
+ <%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger' %>
37
37
  <% if run.stuck? %>
38
- <%= button_to 'Force pause', pause_task_run_path(@task, run), method: :put, class: 'button is-danger', disabled: @task.deleted? %>
38
+ <%= button_to 'Force pause', pause_task_run_path(@task, run), class: 'button is-danger', disabled: @task.deleted? %>
39
39
  <% end %>
40
40
  <% elsif run.active? %>
41
- <%= button_to 'Pause', pause_task_run_path(@task, run), method: :put, class: 'button is-warning', disabled: @task.deleted? %>
42
- <%= button_to 'Cancel', cancel_task_run_path(@task, run), method: :put, class: 'button is-danger' %>
41
+ <%= button_to 'Pause', pause_task_run_path(@task, run), class: 'button is-warning', disabled: @task.deleted? %>
42
+ <%= button_to 'Cancel', cancel_task_run_path(@task, run), class: 'button is-danger' %>
43
43
  <% end%>
44
44
  </div>
45
45
  </div>
@@ -38,7 +38,7 @@
38
38
  <pre><code><%= highlight_code(code) %></code></pre>
39
39
  <% end %>
40
40
 
41
- <%= tag.div(data: { refresh: (defined?(@refresh) && @refresh) || "" }) do %>
41
+ <%= tag.div(data: { refresh: @task.refresh? || "" }) do %>
42
42
  <% if @task.active_runs.any? %>
43
43
  <hr/>
44
44
 
@@ -47,13 +47,13 @@
47
47
  <%= render partial: "maintenance_tasks/runs/run", collection: @task.active_runs %>
48
48
  <% end %>
49
49
 
50
- <% if @runs_page.records.present? %>
50
+ <% if @task.runs_page.records.present? %>
51
51
  <hr/>
52
52
 
53
53
  <h4 class="title is-4">Previous Runs</h4>
54
54
 
55
- <%= render partial: "maintenance_tasks/runs/run", collection: @runs_page.records %>
55
+ <%= render partial: "maintenance_tasks/runs/run", collection: @task.runs_page.records %>
56
56
 
57
- <%= link_to "Next page", task_path(@task, cursor: @runs_page.next_cursor) unless @runs_page.last? %>
57
+ <%= link_to "Next page", task_path(@task, cursor: @task.runs_page.next_cursor) unless @task.runs_page.last? %>
58
58
  <% end %>
59
59
  <% end %>
data/config/routes.rb CHANGED
@@ -4,11 +4,12 @@ MaintenanceTasks::Engine.routes.draw do
4
4
  resources :tasks, only: [:index, :show], format: false do
5
5
  resources :runs, only: [:create], format: false do
6
6
  member do
7
- put "pause"
8
- put "cancel"
9
- put "resume"
7
+ post "pause"
8
+ post "cancel"
9
+ post "resume"
10
10
  end
11
11
  end
12
+ get :runs, to: redirect("tasks/%{task_id}")
12
13
  end
13
14
 
14
15
  root to: "tasks#index"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maintenance_tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.0
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-15 00:00:00.000000000 Z
11
+ date: 2024-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -185,7 +185,7 @@ homepage: https://github.com/Shopify/maintenance_tasks
185
185
  licenses:
186
186
  - MIT
187
187
  metadata:
188
- source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.9.0
188
+ source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.10.0
189
189
  allowed_push_host: https://rubygems.org
190
190
  post_install_message:
191
191
  rdoc_options: []