maintenance_tasks 1.0.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +180 -22
  3. data/Rakefile +16 -16
  4. data/app/controllers/maintenance_tasks/application_controller.rb +3 -6
  5. data/app/controllers/maintenance_tasks/runs_controller.rb +1 -12
  6. data/app/controllers/maintenance_tasks/tasks_controller.rb +7 -4
  7. data/app/helpers/maintenance_tasks/application_helper.rb +2 -48
  8. data/app/helpers/maintenance_tasks/{task_helper.rb → tasks_helper.rb} +26 -18
  9. data/app/jobs/maintenance_tasks/task_job.rb +49 -6
  10. data/app/models/maintenance_tasks/application_record.rb +2 -2
  11. data/app/models/maintenance_tasks/csv_collection.rb +33 -0
  12. data/app/models/maintenance_tasks/progress.rb +19 -14
  13. data/app/models/maintenance_tasks/run.rb +39 -1
  14. data/app/models/maintenance_tasks/runner.rb +20 -5
  15. data/app/models/maintenance_tasks/runs_page.rb +55 -0
  16. data/app/models/maintenance_tasks/task_data.rb +9 -5
  17. data/app/models/maintenance_tasks/ticker.rb +0 -1
  18. data/app/tasks/maintenance_tasks/task.rb +43 -16
  19. data/app/validators/maintenance_tasks/run_status_validator.rb +15 -13
  20. data/app/views/layouts/maintenance_tasks/_navbar.html.erb +0 -6
  21. data/app/views/maintenance_tasks/runs/_info.html.erb +6 -0
  22. data/app/views/maintenance_tasks/runs/_run.html.erb +1 -9
  23. data/app/views/maintenance_tasks/runs/info/_running.html.erb +0 -2
  24. data/app/views/maintenance_tasks/tasks/show.html.erb +31 -4
  25. data/config/routes.rb +4 -6
  26. data/db/migrate/20210225152418_remove_index_on_task_name.rb +14 -0
  27. data/exe/maintenance_tasks +2 -2
  28. data/lib/generators/maintenance_tasks/install_generator.rb +4 -5
  29. data/lib/generators/maintenance_tasks/task_generator.rb +15 -9
  30. data/lib/generators/maintenance_tasks/templates/csv_task.rb.tt +13 -0
  31. data/lib/generators/maintenance_tasks/templates/task_spec.rb.tt +1 -1
  32. data/lib/generators/maintenance_tasks/templates/task_test.rb.tt +1 -1
  33. data/lib/maintenance_tasks.rb +34 -30
  34. data/lib/maintenance_tasks/cli.rb +18 -4
  35. data/lib/maintenance_tasks/engine.rb +6 -3
  36. metadata +9 -31
  37. data/app/views/maintenance_tasks/runs/index.html.erb +0 -15
  38. data/app/views/maintenance_tasks/tasks/actions/_cancelled.html.erb +0 -1
  39. data/app/views/maintenance_tasks/tasks/actions/_cancelling.html.erb +0 -4
  40. data/app/views/maintenance_tasks/tasks/actions/_enqueued.html.erb +0 -2
  41. data/app/views/maintenance_tasks/tasks/actions/_errored.html.erb +0 -1
  42. data/app/views/maintenance_tasks/tasks/actions/_interrupted.html.erb +0 -2
  43. data/app/views/maintenance_tasks/tasks/actions/_new.html.erb +0 -1
  44. data/app/views/maintenance_tasks/tasks/actions/_paused.html.erb +0 -2
  45. data/app/views/maintenance_tasks/tasks/actions/_pausing.html.erb +0 -2
  46. data/app/views/maintenance_tasks/tasks/actions/_running.html.erb +0 -2
  47. data/app/views/maintenance_tasks/tasks/actions/_succeeded.html.erb +0 -1
  48. data/lib/maintenance_tasks/integrations/bugsnag_handler.rb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d591c21fb76efef72185bf183b0f4b0bc0a5b97a2606e083f6fbfc11325e029b
4
- data.tar.gz: 6195855524490ebc663f1970134b6aed2abe7887345b6c17d2d0dccea9d81fb4
3
+ metadata.gz: c673afea63371c8c8f69ccda520b66fb1391d17ff6f241eb5610e45c60493561
4
+ data.tar.gz: a1b914d6fdf7391bda0691444f8af2e263c71313c295bf2fd38e6e655f661a99
5
5
  SHA512:
6
- metadata.gz: 3fa1e498d50830521cabeb8babda612a9762438cd1fdccf5cecf776ae069ec85cdbaedbded208bdfc1e72479b69ffb76a39b999e4c2f8d810811dc8672f0b5cc
7
- data.tar.gz: 2d9aa8e0c134baa3864f2cd5e9fe53944a5d13a89c21caab26dcd394b3eb8845af62397f2d1be880c9f27d71cbe77db3865d16c7da53f0b70cc64cc7bf4153a4
6
+ metadata.gz: d161e823b16c12abd127d725cccf985d0bca50253cb40937d265685d6078730beff127913eea7973ff23211576d5d143508ac61968ebb2460f1c12fc89ff5d82
7
+ data.tar.gz: 46de8ac9e6819829de191a0fcfaf40c48b2922f1cc3024df58a41a7e6763658149f3f32baa168045bae6c00790da1b789647d36bad880714bdf63e75d67aeb32
data/README.md CHANGED
@@ -3,11 +3,16 @@
3
3
  A Rails engine for queuing and managing maintenance tasks.
4
4
 
5
5
  ## Table of Contents
6
+
7
+ * [Demo](#demo)
6
8
  * [Installation](#installation)
9
+ * [Active Job Dependency](#active-job-dependency)
7
10
  * [Usage](#usage)
8
11
  * [Creating a Task](#creating-a-task)
9
- * [Considerations when writing Tasks](#considerations-when-writing-tasks)
12
+ * [Creating a CSV Task](#creating-a-csv-task)
13
+ * [Considerations when writing Tasks](#considerations-when-writing-tasks)
10
14
  * [Writing tests for a Task](#writing-tests-for-a-task)
15
+ * [Writing tests for a CSV Task](#writing-tests-for-a-csv-task)
11
16
  * [Running a Task](#running-a-task)
12
17
  * [Monitoring your Task's status](#monitoring-your-tasks-status)
13
18
  * [How Maintenance Tasks runs a Task](#how-maintenance-tasks-runs-a-task)
@@ -17,22 +22,23 @@ A Rails engine for queuing and managing maintenance tasks.
17
22
  * [Customizing the maintenance tasks module](#customizing-the-maintenance-tasks-module)
18
23
  * [Customizing the underlying job class](#customizing-the-underlying-job-class)
19
24
  * [Customizing the rate at which task progress gets updated](#customizing-the-rate-at-which-task-progress-gets-updated)
25
+ * [Customizing which Active Storage service to use](#customizing-which-active-storage-service-to-use)
20
26
  * [Upgrading](#upgrading)
21
27
  * [Contributing](#contributing)
22
28
  * [Releasing new versions](#releasing-new-versions)
23
29
 
24
- ## Installation
30
+ ## Demo
25
31
 
26
- Add this line to your application's Gemfile:
32
+ Watch this demo video to see the gem in action:
27
33
 
28
- ```ruby
29
- gem 'maintenance_tasks'
30
- ```
34
+ [![Link to demo video](static/demo.png)](https://www.youtube.com/watch?v=BTuvTQxlFzs)
31
35
 
32
- And then execute:
36
+ ## Installation
37
+
38
+ To install the gem and run the install generator, execute:
33
39
 
34
40
  ```bash
35
- $ bundle
41
+ $ bundle add maintenance_tasks
36
42
  $ rails generate maintenance_tasks:install
37
43
  ```
38
44
 
@@ -40,6 +46,22 @@ The generator creates and runs a migration to add the necessary table to your
40
46
  database. It also mounts Maintenance Tasks in your `config/routes.rb`. By
41
47
  default the web UI can be accessed in the new `/maintenance_tasks` path.
42
48
 
49
+ In case you use an exception reporting service (e.g. Bugsnag) you might want to
50
+ define an error handler. See [Customizing the error
51
+ handler](#customizing-the-error-handler) for more information.
52
+
53
+ ### Active Job Dependency
54
+
55
+ The Maintenance Tasks framework relies on ActiveJob behind the scenes to run
56
+ Tasks. The default queuing backend for ActiveJob is
57
+ [asynchronous][async-adapter]. It is **strongly recommended** to change this to
58
+ a persistent backend so that Task progress is not lost during code or
59
+ infrastructure changes. For more information on configuring a queuing backend,
60
+ take a look at the [ActiveJob documentation][active-job-docs].
61
+
62
+ [async-adapter]: https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html
63
+ [active-job-docs]: https://guides.rubyonrails.org/active_job_basics.html#setting-the-backend
64
+
43
65
  ## Usage
44
66
 
45
67
  ### Creating a Task
@@ -81,7 +103,45 @@ module Maintenance
81
103
  end
82
104
  ```
83
105
 
84
- #### Considerations when writing Tasks
106
+ ### Creating a CSV Task
107
+
108
+ You can also write a Task that iterates on a CSV file. Note that writing CSV
109
+ Tasks **requires ActiveStorage to be configured**. Ensure that the dependency
110
+ is specified in your application's Gemfile, and that you've followed the
111
+ [setup instuctions][setup].
112
+
113
+ [setup]: https://edgeguides.rubyonrails.org/active_storage_overview.html#setup
114
+
115
+ Generate a CSV Task by running:
116
+
117
+ ```bash
118
+ $ rails generate maintenance_tasks:task import_posts --csv
119
+ ```
120
+
121
+ The generated task is a subclass of `MaintenanceTasks::Task` that implements:
122
+
123
+ * `process`: do the work of your maintenance task on a `CSV::Row`
124
+
125
+ ```ruby
126
+ # app/tasks/maintenance/import_posts_task.rb
127
+ module Maintenance
128
+ class ImportPostsTask < MaintenanceTasks::Task
129
+ csv_collection
130
+
131
+ def process(row)
132
+ Post.create!(title: row["title"], content: row["content"])
133
+ end
134
+ end
135
+ end
136
+ ```
137
+
138
+ ```csv
139
+ # posts.csv
140
+ title,content
141
+ My Title,Hello World!
142
+ ```
143
+
144
+ ### Considerations when writing Tasks
85
145
 
86
146
  MaintenanceTasks relies on the queue adapter configured for your application to
87
147
  run the job which is processing your Task. The guidelines for writing Task may
@@ -120,7 +180,7 @@ module Maintenance
120
180
  test "#process performs a task iteration" do
121
181
  post = Post.new
122
182
 
123
- Maintenance::UpdatePostsTask.new.process(post)
183
+ Maintenance::UpdatePostsTask.process(post)
124
184
 
125
185
  assert_equal 'New content!', post.content
126
186
  end
@@ -128,6 +188,32 @@ module Maintenance
128
188
  end
129
189
  ```
130
190
 
191
+ ### Writing tests for a CSV Task
192
+
193
+ You should write tests for your `#process` method in a CSV Task as well. It
194
+ takes a `CSV::Row` as an argument. You can pass a row, or a hash with string
195
+ keys to `#process` from your test.
196
+
197
+ ```ruby
198
+ # app/tasks/maintenance/import_posts_task_test.rb
199
+ module Maintenance
200
+ class ImportPostsTaskTest < ActiveSupport::TestCase
201
+ test "#process performs a task iteration" do
202
+ assert_difference -> { Post.count } do
203
+ Maintenance::UpdatePostsTask.process({
204
+ 'title' => 'My Title',
205
+ 'content' => 'Hello World!',
206
+ })
207
+ end
208
+
209
+ post = Post.last
210
+ assert_equal 'My Title', post.title
211
+ assert_equal 'Hello World!', post.content
212
+ end
213
+ end
214
+ end
215
+ ```
216
+
131
217
  ### Running a Task
132
218
 
133
219
  You can run your new Task by accessing the Web UI and clicking on "Run".
@@ -138,11 +224,26 @@ Alternatively, you can run your Task in the command line:
138
224
  $ bundle exec maintenance_tasks perform Maintenance::UpdatePostsTask
139
225
  ```
140
226
 
141
- You can also run a Task in Ruby by sending `run` with a Task name to a Runner
142
- instance:
227
+ To run a Task that processes CSVs from the command line, use the --csv option:
228
+
229
+ ```bash
230
+ $ bundle exec maintenance_tasks perform Maintenance::ImportPostsTask --csv 'path/to/my_csv.csv'
231
+ ```
232
+
233
+ You can also run a Task in Ruby by sending `run` with a Task name to Runner:
234
+
235
+ ```ruby
236
+ MaintenanceTasks::Runner.run(name: 'Maintenance::UpdatePostsTask')
237
+ ```
238
+
239
+ To run a Task that processes CSVs using the Runner, provide a Hash containing an
240
+ open IO object and a filename to `run`:
143
241
 
144
242
  ```ruby
145
- MaintenanceTasks::Runner.new.run('Maintenance::UpdatePostsTask')
243
+ MaintenanceTasks::Runner.run(
244
+ name: 'Maintenance::ImportPostsTask'
245
+ csv_file: { io: File.open('path/to/my_csv.csv'), filename: 'my_csv.csv' }
246
+ )
146
247
  ```
147
248
 
148
249
  ### Monitoring your Task's status
@@ -224,17 +325,39 @@ be placed in a `maintenance_tasks.rb` initializer.
224
325
  Exceptions raised while a Task is performing are rescued and information about
225
326
  the error is persisted and visible in the UI.
226
327
 
227
- If your application uses Bugsnag to monitor errors, the gem will automatically
228
- notify Bugsnag of any errors raised while a Task is performing.
229
-
230
- If you want to integrate with another exception monitoring service or customize
231
- error handling, a callback can be defined:
328
+ If you want to integrate with an exception monitoring service (e.g. Bugsnag),
329
+ you can define an error handler:
232
330
 
233
331
  ```ruby
234
332
  # config/initializers/maintenance_tasks.rb
235
- MaintenanceTasks.error_handler = ->(error) { MyErrorMonitor.notify(error) }
333
+ MaintenanceTasks.error_handler = ->(error, task_context, _errored_element) do
334
+ Bugsnag.notify(error) do |notification|
335
+ notification.add_tab(:task, task_context)
336
+ end
337
+ end
236
338
  ```
237
339
 
340
+ The error handler should be a lambda that accepts three arguments:
341
+
342
+ * `error`: The exception that was raised.
343
+ * `task_context`: A hash with additional information about the Task and the
344
+ error:
345
+ * `task_name`: The name of the Task that errored
346
+ * `started_at`: The time the Task started
347
+ * `ended_at`: The time the Task errored
348
+ Note that `task_context` may be empty if the Task produced an error before any
349
+ context could be gathered (for example, if deserializing the job to process
350
+ your Task failed).
351
+ * `errored_element`: The element, if any, that was being processed when the
352
+ Task raised an exception. If you would like to pass this object to your
353
+ exception monitoring service, make sure you **sanitize the object** to avoid
354
+ leaking sensitive data and **convert it to a format** that is compatible with
355
+ your bug tracker. For example, Bugsnag only sends the id and class name of
356
+ ActiveRecord objects in order to protect sensitive data. CSV rows, on the
357
+ other hand, are converted to strings and passed raw to Bugsnag, so make sure
358
+ to filter any personal data from these objects before adding them to a
359
+ report.
360
+
238
361
  #### Customizing the maintenance tasks module
239
362
 
240
363
  `MaintenanceTasks.tasks_module` can be configured to define the module in which
@@ -281,6 +404,36 @@ MaintenanceTasks.ticker_delay = 2.seconds
281
404
 
282
405
  If no value is specified, it will default to 1 second.
283
406
 
407
+ #### Customizing which Active Storage service to use
408
+
409
+ The Active Storage framework in Rails 6.1 and up supports multiple storage
410
+ services per environment. To specify which service to use,
411
+ `MaintenanceTasks.active_storage_service` can be configured with the service's
412
+ key, as specified in your application's `config/storage.yml`:
413
+
414
+ ```yaml
415
+ # config/storage.yml
416
+ user_data:
417
+ service: GCS
418
+ credentials: <%= Rails.root.join("path/to/user/data/keyfile.json") %>
419
+ project: "my-project"
420
+ bucket: "user-data-bucket"
421
+
422
+ internal:
423
+ service: GCS
424
+ credentials: <%= Rails.root.join("path/to/internal/keyfile.json") %>
425
+ project: "my-project"
426
+ bucket: "internal-bucket"
427
+ ```
428
+
429
+ ```ruby
430
+ # config/initializers/maintenance_tasks.rb
431
+ MaintenanceTasks.active_storage_service = :internal
432
+ ```
433
+
434
+ There is no need to configure this option if your application uses only one
435
+ storage service per environment.
436
+
284
437
  ## Upgrading
285
438
 
286
439
  Use bundler to check for and upgrade to newer versions. After installing a new
@@ -302,15 +455,20 @@ pull requests. You can find the contribution guidelines on
302
455
 
303
456
  ## Releasing new versions
304
457
 
305
- This gem is published to packagecloud. The procedure to publish a new version:
458
+ Updates should be added to the latest draft release on GitHub as Pull Requests
459
+ are merged.
460
+
461
+ Once a release is ready, follow these steps:
306
462
 
307
463
  * Update `spec.version` in `maintenance_tasks.gemspec`.
308
464
  * Run `bundle install` to bump the `Gemfile.lock` version of the gem.
309
465
  * Open a PR and merge on approval.
310
- * Create a [release on GitHub][release] with a version number that matches the
311
- version defined in the gemspec.
312
466
  * Deploy via [Shipit][shipit] and see the new version on
313
467
  <https://rubygems.org/gems/maintenance_tasks>.
468
+ * Ensure the release has documented all changes and publish it.
469
+ * Create a new [draft release on GitHub][release] with the title
470
+ 'Upcoming Release'. The tag version can be left blank. This will be the
471
+ starting point for documenting changes related to the next release.
314
472
 
315
473
  [release]: https://help.github.com/articles/creating-releases/
316
474
  [shipit]: https://shipit.shopify.io/shopify/maintenance_tasks/rubygems
data/Rakefile CHANGED
@@ -1,29 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
  begin
3
- require 'bundler/setup'
3
+ require "bundler/setup"
4
4
  rescue LoadError
5
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
6
6
  end
7
7
 
8
- require 'rdoc/task'
8
+ require "rdoc/task"
9
9
  RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'MaintenanceTasks'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.md')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
10
+ rdoc.rdoc_dir = "rdoc"
11
+ rdoc.title = "MaintenanceTasks"
12
+ rdoc.options << "--line-numbers"
13
+ rdoc.rdoc_files.include("README.md")
14
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
15
  end
16
16
 
17
- APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
18
- load('rails/tasks/engine.rake')
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load("rails/tasks/engine.rake")
19
19
 
20
- load('rails/tasks/statistics.rake')
20
+ load("rails/tasks/statistics.rake")
21
21
 
22
- require 'bundler/gem_tasks'
22
+ require "bundler/gem_tasks"
23
23
 
24
- require 'rubocop/rake_task'
24
+ require "rubocop/rake_task"
25
25
  RuboCop::RakeTask.new
26
26
 
27
- task(test: 'app:test')
28
- task('test:system' => 'app:test:system')
29
- task(default: ['db:setup', 'test', 'test:system', 'rubocop'])
27
+ task(test: "app:test")
28
+ task("test:system" => "app:test:system")
29
+ task(default: ["db:test:prepare", "test", "test:system", "rubocop"])
@@ -3,11 +3,9 @@
3
3
  module MaintenanceTasks
4
4
  # Base class for all controllers used by this engine.
5
5
  #
6
- # @api private
6
+ # Can be extended to add different authentication and authorization code.
7
7
  class ApplicationController < ActionController::Base
8
- include Pagy::Backend
9
-
10
- BULMA_CDN = 'https://cdn.jsdelivr.net'
8
+ BULMA_CDN = "https://cdn.jsdelivr.net"
11
9
 
12
10
  content_security_policy do |policy|
13
11
  policy.style_src(BULMA_CDN)
@@ -17,10 +15,9 @@ module MaintenanceTasks
17
15
  before_action do
18
16
  request.content_security_policy_nonce_generator ||=
19
17
  ->(_request) { SecureRandom.base64(16) }
20
- request.content_security_policy_nonce_directives = ['style-src']
18
+ request.content_security_policy_nonce_directives = ["style-src"]
21
19
  end
22
20
 
23
21
  protect_from_forgery with: :exception
24
22
  end
25
- private_constant :ApplicationController
26
23
  end
@@ -6,17 +6,7 @@ module MaintenanceTasks
6
6
  #
7
7
  # @api private
8
8
  class RunsController < ApplicationController
9
- before_action :set_run, except: :index
10
-
11
- # Shows a full list of Runs.
12
- def index
13
- query = Run.all.order(id: :desc)
14
- if params[:task_name].present?
15
- task_name = Run.sanitize_sql_like(params[:task_name])
16
- query = query.where('task_name LIKE ?', "%#{task_name}%")
17
- end
18
- @pagy, @runs = pagy(query)
19
- end
9
+ before_action :set_run
20
10
 
21
11
  # Updates a Run status to paused.
22
12
  def pause
@@ -40,5 +30,4 @@ module MaintenanceTasks
40
30
  @run = Run.find(params.fetch(:id))
41
31
  end
42
32
  end
43
- private_constant :RunsController
44
33
  end
@@ -7,7 +7,7 @@ module MaintenanceTasks
7
7
  #
8
8
  # @api private
9
9
  class TasksController < ApplicationController
10
- before_action :set_refresh, only: [:index, :show]
10
+ before_action :set_refresh, only: [:index]
11
11
 
12
12
  # Renders the maintenance_tasks/tasks page, displaying
13
13
  # available tasks to users, grouped by category.
@@ -19,12 +19,16 @@ module MaintenanceTasks
19
19
  # Shows running and completed instances of the Task.
20
20
  def show
21
21
  @task = TaskData.find(params.fetch(:id))
22
- @pagy, @previous_runs = pagy(@task.previous_runs)
22
+ set_refresh if @task.last_run&.active?
23
+ @runs_page = RunsPage.new(@task.previous_runs, params[:cursor])
23
24
  end
24
25
 
25
26
  # Runs a given Task and redirects to the Task page.
26
27
  def run
27
- task = Runner.new.run(name: params.fetch(:id))
28
+ task = Runner.run(
29
+ name: params.fetch(:id),
30
+ csv_file: params[:csv_file]
31
+ )
28
32
  redirect_to(task_path(task))
29
33
  rescue ActiveRecord::RecordInvalid => error
30
34
  redirect_to(task_path(error.record.task_name), alert: error.message)
@@ -38,5 +42,4 @@ module MaintenanceTasks
38
42
  @refresh = 3
39
43
  end
40
44
  end
41
- private_constant :TasksController
42
45
  end
@@ -4,17 +4,6 @@ module MaintenanceTasks
4
4
  #
5
5
  # @api private
6
6
  module ApplicationHelper
7
- include Pagy::Frontend
8
-
9
- # Renders pagination for the page, if there is more than one page present.
10
- #
11
- # @param pagy [Pagy] the pagy instance containing pagination details,
12
- # including the number of pages the results are spread across.
13
- # @return [String] the HTML to render for pagination.
14
- def pagination(pagy)
15
- raw(pagy_bulma_nav(pagy)) if pagy.pages > 1
16
- end
17
-
18
7
  # Renders a time element with the given datetime, worded as relative to the
19
8
  # current time.
20
9
  #
@@ -24,44 +13,9 @@ module MaintenanceTasks
24
13
  # @param datetime [ActiveSupport::TimeWithZone] the time to be presented.
25
14
  # @return [String] the HTML to render with the relative datetime in words.
26
15
  def time_ago(datetime)
27
- time_tag(datetime, title: datetime.utc.iso8601, class: 'is-clickable') do
28
- time_ago_in_words(datetime) + ' ago'
29
- end
30
- end
31
-
32
- # Fix stylesheet_link_tag to handle integrity when preloading.
33
- # To be reverted once fixed upstream in Rails.
34
- def stylesheet_link_tag(*sources)
35
- return super unless respond_to?(:send_preload_links_header, true)
36
- options = sources.extract_options!.stringify_keys
37
- path_options = options.extract!('protocol', 'host', 'skip_pipeline')
38
- .symbolize_keys
39
- preload_links = []
40
- crossorigin = options.delete('crossorigin')
41
- crossorigin = 'anonymous' if crossorigin == true
42
- nopush = options['nopush'].nil? ? true : options.delete('nopush')
43
- integrity = options['integrity']
44
-
45
- sources_tags = sources.uniq.map do |source|
46
- href = path_to_stylesheet(source, path_options)
47
- preload_link = "<#{href}>; rel=preload; as=style"
48
- preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
49
- preload_link += "; integrity=#{integrity}" unless integrity.nil?
50
- preload_link += '; nopush' if nopush
51
- preload_links << preload_link
52
- tag_options = {
53
- 'rel' => 'stylesheet',
54
- 'media' => 'screen',
55
- 'crossorigin' => crossorigin,
56
- 'href' => href,
57
- }.merge!(options)
58
- tag(:link, tag_options)
16
+ time_tag(datetime, title: datetime.utc.iso8601, class: "is-clickable") do
17
+ time_ago_in_words(datetime) + " ago"
59
18
  end
60
-
61
- send_preload_links_header(preload_links)
62
-
63
- safe_join(sources_tags)
64
19
  end
65
20
  end
66
- private_constant :ApplicationHelper
67
21
  end