maintenance_tasks 2.9.0 → 2.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f05710c874610ef0698738773b70923d2e6a4ec4ae4b452ac768b6bf2e696d2c
4
- data.tar.gz: 2ab31dae65aa383d5edbccee747199960f5167cd33833230c044fd00f0ad6cfc
3
+ metadata.gz: 4b660fdfcef27420bf9b08ddc50c9cfac5ff0977c4435b54ecd2b468a3e4fde8
4
+ data.tar.gz: 30bac0da63839406dc623d07c85904b7a727d3af5c177736cbd72ea828ca41c9
5
5
  SHA512:
6
- metadata.gz: 8f0f7eee33cc6615f658f49ec9d74322f7952f2afa47b4b539dd0a49131cea10df15f072402d51d611bbfbf4d8d297ace54da1896ab457e54280bc17284641c9
7
- data.tar.gz: af3992b2c6de8c8b70d840d8c06d6a8cb2646063a9b23222a1814bfc1cc7866a9745277aafe32ef8ee48627b7916e51c672c9321be87a49a5efd443f353722fc
6
+ metadata.gz: 071dafa23a89bd949e40f10ef199ae892d5ae81ca2ad1e15e6c7de0260dafbd55f735d67c310f5de7ef499991982eb3bad69fce4024f8f8761e4357ac3fdc42b
7
+ data.tar.gz: 77265626bdeafaa6486a3843846d519168be0712399ebe3cd7a4ecbb42f5888f208f566346d6d628643ee0dcf06ff1c4b914f2ab3ef9ec73f60b9444ef3f05c0
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
@@ -33,6 +33,7 @@ module MaintenanceTasks
33
33
  def build_enumerator(_run, cursor:)
34
34
  cursor ||= @run.cursor
35
35
  self.cursor_position = cursor
36
+ enumerator_builder = self.enumerator_builder
36
37
  @collection_enum = @task.enumerator_builder(cursor: cursor)
37
38
 
38
39
  @collection_enum ||= case (collection = @task.collection)
@@ -75,6 +76,9 @@ module MaintenanceTasks
75
76
  MSG
76
77
  end
77
78
 
79
+ unless @collection_enum.is_a?(JobIteration.enumerator_builder::Wrapper)
80
+ @collection_enum = enumerator_builder.wrap(enumerator_builder, @collection_enum)
81
+ end
78
82
  throttle_enumerator(@collection_enum)
79
83
  end
80
84
 
@@ -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.1
5
5
  platform: ruby
6
+ original_platform: ''
6
7
  authors:
7
8
  - Shopify Engineering
8
- autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-15 00:00:00.000000000 Z
11
+ date: 2024-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -108,7 +108,6 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: 2.6.2
111
- description:
112
111
  email: gems@shopify.com
113
112
  executables:
114
113
  - maintenance_tasks
@@ -185,9 +184,8 @@ homepage: https://github.com/Shopify/maintenance_tasks
185
184
  licenses:
186
185
  - MIT
187
186
  metadata:
188
- source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.9.0
187
+ source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.10.1
189
188
  allowed_push_host: https://rubygems.org
190
- post_install_message:
191
189
  rdoc_options: []
192
190
  require_paths:
193
191
  - lib
@@ -202,8 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
202
200
  - !ruby/object:Gem::Version
203
201
  version: '0'
204
202
  requirements: []
205
- rubygems_version: 3.5.23
206
- signing_key:
203
+ rubygems_version: 3.6.1
207
204
  specification_version: 4
208
205
  summary: A Rails engine for queuing and managing maintenance tasks
209
206
  test_files: []