shiplog 0.2.0 → 0.3.0

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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/README.md +140 -5
  4. data/app/controllers/shiplog/admin/area_detections_controller.rb +15 -0
  5. data/app/controllers/shiplog/admin/area_translations_controller.rb +19 -0
  6. data/app/controllers/shiplog/admin/areas/merges_controller.rb +19 -0
  7. data/app/controllers/shiplog/admin/areas/moves_controller.rb +13 -0
  8. data/app/controllers/shiplog/admin/areas_controller.rb +48 -0
  9. data/app/controllers/shiplog/admin/entries/images/moves_controller.rb +20 -0
  10. data/app/controllers/shiplog/admin/entries/images_controller.rb +76 -0
  11. data/app/controllers/shiplog/admin/entries/translations/retranslations_controller.rb +23 -0
  12. data/app/controllers/shiplog/admin/entries/translations_controller.rb +35 -0
  13. data/app/controllers/shiplog/admin/entries_controller.rb +9 -3
  14. data/app/controllers/shiplog/changelog_entries_controller.rb +26 -0
  15. data/app/controllers/shiplog/changelog_feeds_controller.rb +19 -0
  16. data/app/controllers/shiplog/changelogs_controller.rb +21 -5
  17. data/app/controllers/shiplog/public_controller.rb +55 -0
  18. data/app/controllers/shiplog/roadmaps_controller.rb +3 -1
  19. data/app/frontend/shiplog/admin.css +9 -0
  20. data/app/frontend/shiplog/shiplog.css +47 -2
  21. data/app/helpers/shiplog/admin/entries_helper.rb +15 -0
  22. data/app/helpers/shiplog/permalink_helper.rb +113 -0
  23. data/app/helpers/shiplog/roadmap_helper.rb +9 -0
  24. data/app/helpers/shiplog/unseen_helper.rb +9 -0
  25. data/app/jobs/shiplog/detect_areas_job.rb +18 -0
  26. data/app/jobs/shiplog/rewrite_entry_job.rb +4 -1
  27. data/app/jobs/shiplog/translate_area_job.rb +25 -0
  28. data/app/jobs/shiplog/translate_entry_job.rb +25 -0
  29. data/app/models/shiplog/area.rb +156 -0
  30. data/app/models/shiplog/area_detector.rb +146 -0
  31. data/app/models/shiplog/area_translator.rb +77 -0
  32. data/app/models/shiplog/entry/images.rb +127 -0
  33. data/app/models/shiplog/entry.rb +204 -5
  34. data/app/models/shiplog/entry_translation.rb +15 -0
  35. data/app/models/shiplog/image_fetcher/downloader.rb +98 -0
  36. data/app/models/shiplog/image_fetcher/sources.rb +41 -0
  37. data/app/models/shiplog/image_fetcher.rb +72 -0
  38. data/app/models/shiplog/image_type.rb +111 -0
  39. data/app/models/shiplog/rewriter/prompt.rb +31 -7
  40. data/app/models/shiplog/rewriter.rb +41 -23
  41. data/app/models/shiplog/setting.rb +19 -0
  42. data/app/models/shiplog/translator/prompt.rb +40 -0
  43. data/app/models/shiplog/translator.rb +99 -0
  44. data/app/views/layouts/shiplog/application.html.erb +3 -0
  45. data/app/views/shiplog/_empty.html.erb +1 -1
  46. data/app/views/shiplog/_pagination.html.erb +3 -3
  47. data/app/views/shiplog/admin/_nav.html.erb +2 -1
  48. data/app/views/shiplog/admin/areas/index.html.erb +78 -0
  49. data/app/views/shiplog/admin/entries/_entry_row.html.erb +3 -0
  50. data/app/views/shiplog/admin/entries/_images.html.erb +41 -0
  51. data/app/views/shiplog/admin/entries/_translations.html.erb +42 -0
  52. data/app/views/shiplog/admin/entries/edit.html.erb +12 -0
  53. data/app/views/shiplog/changelog_entries/show.html.erb +25 -0
  54. data/app/views/shiplog/changelog_feeds/show.atom.builder +24 -0
  55. data/app/views/shiplog/changelogs/_areas.html.erb +8 -0
  56. data/app/views/shiplog/changelogs/_entry.html.erb +2 -5
  57. data/app/views/shiplog/changelogs/_entry_content.html.erb +10 -0
  58. data/app/views/shiplog/changelogs/_search.html.erb +4 -2
  59. data/app/views/shiplog/changelogs/show.html.erb +11 -4
  60. data/app/views/shiplog/roadmaps/_card.html.erb +2 -2
  61. data/app/views/shiplog/roadmaps/_column.html.erb +3 -3
  62. data/app/views/shiplog/roadmaps/_shipped.html.erb +1 -1
  63. data/app/views/shiplog/roadmaps/show.html.erb +2 -2
  64. data/config/admin_routes.rb +16 -0
  65. data/config/locales/en.yml +85 -0
  66. data/config/locales/es.yml +223 -0
  67. data/config/routes.rb +5 -0
  68. data/db/migrate/20261027000002_shiplog_0_3_0.rb +105 -0
  69. data/lib/generators/shiplog/install/install_generator.rb +10 -1
  70. data/lib/generators/shiplog/install/templates/initializer.rb.tt +45 -1
  71. data/lib/shiplog/classes.rb +10 -1
  72. data/lib/shiplog/configuration.rb +92 -3
  73. data/lib/shiplog/engine.rb +3 -0
  74. data/lib/shiplog/github/queries.rb +13 -1
  75. data/lib/shiplog/version.rb +1 -1
  76. data/lib/shiplog.rb +73 -0
  77. data/lib/tasks/shiplog.rake +29 -0
  78. metadata +39 -1
@@ -97,7 +97,12 @@ Shiplog.configure do |config|
97
97
 
98
98
  # Public paths, read when routes are drawn.
99
99
  # config.roadmap_path = "roadmap"
100
- # config.changelog_path = "changelog"
100
+ # config.changelog_path = "changelog" # a-z, 0-9, - and _; "docs/news" nests; outer slashes are stripped
101
+
102
+ # The site's canonical origin (scheme, host, optional port; no path). The Atom feed's ids and links, og:url
103
+ # and the other absolute URLs use it instead of the request's host, which varies (apex vs www, a forged
104
+ # X-Forwarded-Host). Behind a proxy, also set config.assume_ssl = true (or send X-Forwarded-Proto) in Rails.
105
+ # config.canonical_url = "https://example.com"
101
106
 
102
107
  # Pages added to the Roadmap / Changelog switcher. label and path are Strings, or callables evaluated in the
103
108
  # view (route helpers, I18n). Render the same switcher on your own pages with `helper Shiplog::SwitcherHelper`
@@ -117,6 +122,45 @@ Shiplog.configure do |config|
117
122
  # The public changelog's search box and ?q= filtering. Set to false to turn search off.
118
123
  # config.changelog_search = true
119
124
 
125
+ # "What's new". Shiplog doesn't know your users, so you supply the "last seen" time. This is called with the
126
+ # controller on every changelog index or permalink view, BEFORE the page renders (a 304 revisit included), so a
127
+ # navbar badge in the same response is current. It may write and runs on every visit: keep it cheap, and write
128
+ # only when something is new. Show `Shiplog.unseen_count(since: user.changelog_seen_at)` (at most 99, counted on
129
+ # approved_at; `helper Shiplog::UnseenHelper` and `shiplog_unseen_count(since)` in views) next to your Changelog
130
+ # link. current_user is often private, hence controller.send (or read a Current attribute).
131
+ # config.on_changelog_seen = lambda do |controller|
132
+ # user = controller.send(:current_user)
133
+ # user.update_column(:changelog_seen_at, Time.current) if user && Shiplog.unseen_count(since: user.changelog_seen_at).positive?
134
+ # end
135
+
136
+ # The 0.3.0 migration gives entries approved so far their permalink slug. The public pages never write (a GET
137
+ # may hit a read replica), so an entry approved by older code during a deploy window has no permalink until
138
+ # `bin/rails shiplog:backfill_slugs` runs. Permalink pages set `content_for :shiplog_og` (Open Graph tags),
139
+ # `content_for :shiplog_description` and `content_for :shiplog_feed` (the Atom discovery link); Shiplog's own
140
+ # layout renders them, so emit them in your layout's <head> when you set `config.public_layout`.
141
+
142
+ # Product areas. The rewriter's reply may name an area (an existing one, or a new name of 1-3 words); Shiplog
143
+ # creates it while fewer than max_areas exist, so the AI cannot sprawl. The changelog offers an area filter
144
+ # (?area=slug) once two areas have published entries. Manage areas in the admin ("Areas"): rename, reorder,
145
+ # merge, delete, and "Detect areas" to backfill entries that have none (it reuses config.rewriter).
146
+ # Entries you set by hand on the edit page are never changed by the AI.
147
+ # config.max_areas = 8
148
+
149
+ # Screenshots in entries. They need Active Storage in your app (require "active_storage/engine", and its tables);
150
+ # without it they stay off and Shiplog logs that once at boot. When a draft is written, the images in the GitHub
151
+ # issue or pull request body are attached to the entry (GitHub image hosts only, 5 MB and PNG, JPEG, GIF or
152
+ # WebP at most; the token needs no extra permission). You can also upload images on the entry edit page.
153
+ # config.images = true
154
+ # config.max_images_per_entry = 4
155
+
156
+ # Translations of entries. Entries are translated into each of these locales with config.rewriter when they are
157
+ # approved, and again when the English title or body of an approved entry changes; the translation is published
158
+ # at once, with no second approval. One LLM call per entry, locale and change. Admins can edit a translation on
159
+ # the entry page (kept until the English text changes) or retranslate it. Pages render the translation when
160
+ # I18n.locale is one of these and it matches the current English text, and English otherwise. Your routes
161
+ # decide which URLs set the locale (e.g. a /es scope). The default locale is dropped from the list.
162
+ # config.locales = [] # e.g. [:es]
163
+
120
164
  # Callables above cannot be part of the public pages' ETag. Bump this when the output of a callable you
121
165
  # configured changes (a new label, another path) so cached pages are refreshed.
122
166
  # config.cache_version = nil
@@ -49,7 +49,16 @@ module Shiplog
49
49
  search_button: "shiplog-search__button",
50
50
  search_summary: "shiplog-search__summary",
51
51
  search_clear: "shiplog-search__clear",
52
- search_empty: "shiplog-search__empty"
52
+ search_empty: "shiplog-search__empty",
53
+ permalink: "shiplog-permalink",
54
+ permalink_back: "shiplog-permalink__back",
55
+ permalink_nav: "shiplog-permalink__nav",
56
+ permalink_nav_link: "shiplog-permalink__nav-link",
57
+ areas: "shiplog-areas",
58
+ area_link: "shiplog-areas__link",
59
+ entry_area: "shiplog-entry__area",
60
+ entry_images: "shiplog-entry__images",
61
+ entry_image: "shiplog-entry__image"
53
62
  }.freeze
54
63
 
55
64
  KEYS = BEM.keys.freeze
@@ -5,7 +5,7 @@ module Shiplog
5
5
  :rewriter, :product_name, :audience, :tone, :prompt_builder,
6
6
  :public_layout, :admin_layout,
7
7
  :stylesheet, :admin_flash, :authorize_admin, :current_admin,
8
- :roadmap_path, :changelog_path
8
+ :roadmap_path
9
9
 
10
10
  ADMIN_CHROMES = %i[standalone hosted].freeze
11
11
  # Segments of only dots ("." and "..") are path traversal, not names.
@@ -16,16 +16,40 @@ module Shiplog
16
16
  # Path of the lockfile read in merged pull requests to detect gem upgrades (see config.repositories `gem:`).
17
17
  attr_accessor :lockfile_path
18
18
 
19
+ # The public changelog's path: segments of a-z, 0-9, dashes and underscores, joined by slashes ("changelog",
20
+ # "docs/whats_new"); surrounding slashes are stripped. Permalinks live below it and the Atom feed is
21
+ # "<path>.atom". Read when routes are drawn.
22
+ attr_reader :changelog_path
23
+ CHANGELOG_PATH = %r{\A[A-Za-z0-9_-]+(?:/[A-Za-z0-9_-]+)*\z}
24
+
25
+ # The site's canonical origin, e.g. "https://trackberry.com" (nil by default). When set, the Atom feed's ids
26
+ # and links, entry links, og:url and the other absolute URLs use it instead of the request's host, which
27
+ # varies (apex and www, a forged X-Forwarded-Host). An origin only: scheme, host and optional port. Any path
28
+ # prefix of the engine's mount comes from the routes, so it needs no place here.
29
+ attr_reader :canonical_url
30
+
19
31
  attr_reader :admin_chrome, :classes, :public_parent_controller, :admin_parent_controller
20
32
 
21
33
  # Product updates navigation and roadmap options.
22
34
  attr_accessor :feedback_path
23
35
  # Whether the public changelog has a search box and honours ?q= (default true).
24
36
  attr_accessor :changelog_search
37
+ # Called with the controller on each changelog index or permalink view (200 or 304 answers), e.g. to stamp
38
+ # the visitor's "last seen" time. It runs BEFORE the page renders (so a badge in the same response is
39
+ # current), on every visit, and may write: keep it cheap, e.g. write only when the stored time is older than
40
+ # the newest entry. Nil by default. Not part of the ETag: it doesn't change the markup.
41
+ attr_reader :on_changelog_seen
25
42
  # Bump it when the output of a callable you configured changes (extra_pages, repository_labels, feedback_path):
26
43
  # callables cannot be part of the ETag, so this is how cached public pages learn about it.
27
44
  attr_accessor :cache_version
28
45
  attr_reader :extra_pages, :repository_labels, :roadmap_shipped_count
46
+ # How many product areas the AI may create (default 8). Past it, entries can only join existing areas.
47
+ # Not part of render_digest: the markup depends on the areas themselves, which the ETag carries.
48
+ attr_reader :max_areas
49
+ # Screenshots in entries. `images` (default true) is effective only when Active Storage is loaded in the host
50
+ # (see Shiplog.images?). `max_images_per_entry` (default 4) caps the images fetched from GitHub per entry.
51
+ attr_reader :images, :max_images_per_entry
52
+ LOCALE_FORMAT = /\A[A-Za-z][A-Za-z0-9_-]*\z/
29
53
 
30
54
  EXTRA_PAGE_KEYS = %i[label path].freeze
31
55
 
@@ -43,10 +67,10 @@ module Shiplog
43
67
  stable = ->(value) { value.respond_to?(:call) ? "callable" : value }
44
68
  data = [
45
69
  classes.sort_by { |key, _| key.to_s }.map { |key, value| [ key.to_s, Array(value).map(&:to_s) ] },
46
- stylesheet, roadmap_shipped_count, cache_version,
70
+ stylesheet, roadmap_shipped_count, cache_version, product_name, canonical_url,
47
71
  extra_pages.map { |page| [ stable.(page[:label]), stable.(page[:path]) ] },
48
72
  repository_labels.sort_by { |name, _| name.to_s }.map { |name, label| [ name, stable.(label) ] },
49
- stable.(feedback_path), changelog_search
73
+ stable.(feedback_path), changelog_search, images, max_images_per_entry, locales
50
74
  ]
51
75
  Digest::SHA256.hexdigest(JSON.generate(data))
52
76
  end
@@ -92,6 +116,51 @@ module Shiplog
92
116
  @roadmap_shipped_count = value
93
117
  end
94
118
 
119
+ def max_areas=(value)
120
+ raise ArgumentError, "max_areas must be a non-negative Integer, got #{value.inspect}" unless value.is_a?(Integer) && value >= 0
121
+ @max_areas = value
122
+ end
123
+
124
+ def images=(value)
125
+ raise ArgumentError, "images must be true or false, got #{value.inspect}" unless [ true, false ].include?(value)
126
+ @images = value
127
+ end
128
+
129
+ def max_images_per_entry=(value)
130
+ raise ArgumentError, "max_images_per_entry must be a non-negative Integer, got #{value.inspect}" unless value.is_a?(Integer) && value >= 0
131
+ @max_images_per_entry = value
132
+ end
133
+
134
+ # Locales entries are translated into when approved, e.g. [:es] (default none), as symbols and without the
135
+ # default locale (I18n.default_locale, read each time so an initializer that sets it later still counts): it
136
+ # is the language entries are written in. See Shiplog::Translator.
137
+ def locales = @locales - [ I18n.default_locale.to_sym ]
138
+
139
+ # Sets the locales to translate into: an Array of locale Symbols or Strings (see #locales).
140
+ def locales=(value)
141
+ unless value.is_a?(Array) && value.all? { |item| (item.is_a?(Symbol) || item.is_a?(String)) && LOCALE_FORMAT.match?(item.to_s) }
142
+ raise ArgumentError, "locales must be an Array of locale Symbols or Strings like [:es, \"pt-BR\"], got #{value.inspect}"
143
+ end
144
+ @locales = value.map(&:to_sym).uniq.freeze
145
+ end
146
+
147
+ def changelog_path=(value)
148
+ path = value.gsub(%r{\A/+|/+\z}, "") if value.is_a?(String)
149
+ unless path && CHANGELOG_PATH.match?(path)
150
+ raise ArgumentError, "changelog_path must be a path of letters, digits, dashes and underscores, with / between segments (like \"changelog\"), got #{value.inspect}"
151
+ end
152
+ @changelog_path = path
153
+ end
154
+
155
+ def canonical_url=(value)
156
+ @canonical_url = normalize_canonical_url(value)
157
+ end
158
+
159
+ def on_changelog_seen=(value)
160
+ raise ArgumentError, "on_changelog_seen must be nil or a callable, got #{value.inspect}" unless value.nil? || value.respond_to?(:call)
161
+ @on_changelog_seen = value
162
+ end
163
+
95
164
  # :standalone (default) wraps the admin in Shiplog's own page chrome; :hosted renders it inside the
96
165
  # layout of config.admin_layout (compact sub-nav, no page chrome of its own).
97
166
  def admin_chrome=(value)
@@ -158,6 +227,10 @@ module Shiplog
158
227
  @repository_labels = {}.freeze
159
228
  @roadmap_shipped_count = 4
160
229
  @changelog_search = true
230
+ @max_areas = 8
231
+ @images = true
232
+ @max_images_per_entry = 4
233
+ @locales = [].freeze
161
234
  end
162
235
 
163
236
  def resolved_github_token
@@ -196,6 +269,22 @@ module Shiplog
196
269
 
197
270
  private
198
271
 
272
+ def normalize_canonical_url(value)
273
+ return if value.nil? || (value.is_a?(String) && value.strip.empty?)
274
+ raise ArgumentError, "canonical_url must be a String like \"https://example.com\", got #{value.inspect}" unless value.is_a?(String)
275
+
276
+ uri = begin
277
+ URI.parse(value.strip)
278
+ rescue URI::InvalidURIError
279
+ nil
280
+ end
281
+ valid = uri.is_a?(URI::HTTP) && uri.host.present? && [ "", "/" ].include?(uri.path) && uri.query.nil? && uri.fragment.nil? && uri.userinfo.nil?
282
+ raise ArgumentError, "canonical_url must be an http(s) origin without path, query or credentials (\"https://example.com\"), got #{value.inspect}" unless valid
283
+
284
+ port = ":#{uri.port}" unless uri.port == uri.default_port
285
+ "#{uri.scheme}://#{uri.host.downcase}#{port}"
286
+ end
287
+
199
288
  def normalize_repositories(value)
200
289
  raise ArgumentError, "config.repositories must be an Array, got #{value.inspect}" unless value.is_a?(Array)
201
290
 
@@ -1,5 +1,8 @@
1
1
  module Shiplog
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Shiplog
4
+
5
+ # Once per boot, after the host's initializers have set config.images: say why screenshots are off.
6
+ config.after_initialize { Shiplog.log_images_status }
4
7
  end
5
8
  end
@@ -133,7 +133,19 @@ module Shiplog
133
133
  }
134
134
  GRAPHQL
135
135
 
136
- ALL = { ISSUES:, PULL_REQUESTS:, ISSUE:, PULL_REQUEST:, COMMITS:, BLOB_TEXT:, COMMIT_PULL_REQUESTS: }.freeze
136
+ # The rendered HTML of one issue or pull request body (`$id` is its node id). Kept out of the sync documents so
137
+ # their payloads don't grow: it is only asked for when a draft is written, to find screenshots. On private
138
+ # repositories its <img src> URLs are short-lived signed URLs.
139
+ BODY_HTML = <<~GRAPHQL.freeze
140
+ query($id: ID!) {
141
+ node(id: $id) {
142
+ ... on Issue { bodyHTML }
143
+ ... on PullRequest { bodyHTML }
144
+ }
145
+ }
146
+ GRAPHQL
147
+
148
+ ALL = { ISSUES:, PULL_REQUESTS:, ISSUE:, PULL_REQUEST:, COMMITS:, BLOB_TEXT:, COMMIT_PULL_REQUESTS:, BODY_HTML: }.freeze
137
149
  end
138
150
  end
139
151
  end
@@ -1,3 +1,3 @@
1
1
  module Shiplog
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/shiplog.rb CHANGED
@@ -12,5 +12,78 @@ module Shiplog
12
12
  def config = (@config ||= Configuration.new)
13
13
  def configure = yield(config)
14
14
  def reset_config! = (@config = nil)
15
+
16
+ # The number of approved features and changes approved after `since`, or all of them while it is nil, capped
17
+ # at 99. Counted on approved_at (when the entry was approved), not released_on (the list's order). The host supplies the timestamp (Shiplog doesn't know its users): see config.on_changelog_seen.
18
+ def unseen_count(since: nil) = Entry.unseen_count(since:)
19
+
20
+ ACTIVE_STORAGE_TABLES = %w[active_storage_attachments active_storage_blobs].freeze
21
+
22
+ # Whether the host loaded Active Storage (its engine and the model macros). The declaration on Entry needs
23
+ # only this, so it never touches the database.
24
+ def active_storage_loaded? = !!(defined?(ActiveStorage::Engine) && ActiveRecord::Base.respond_to?(:has_many_attached))
25
+
26
+ # Whether screenshots can work: Active Storage is loaded and its tables exist (a host that loads
27
+ # `rails/all` but never ran active_storage:install would otherwise fail on every query). Without either,
28
+ # every image feature stays off instead of failing.
29
+ def active_storage? = active_storage_loaded? && active_storage_tables?
30
+
31
+ # Whether the Active Storage tables exist. Checked lazily, on first use, then remembered for the life of the
32
+ # process, so public pages never look at the schema again. When the database can't be reached (at boot, say)
33
+ # the answer is "not yet known": false for now, and asked again next time. The reason is logged once.
34
+ def active_storage_tables?
35
+ return @active_storage_tables unless @active_storage_tables.nil?
36
+
37
+ present = ActiveRecord::Base.with_connection { |connection| ACTIVE_STORAGE_TABLES.all? { |table| connection.data_source_exists?(table) } }
38
+ log_images_off("Active Storage's tables are missing (#{ACTIVE_STORAGE_TABLES.join(", ")}). Run bin/rails active_storage:install and db:migrate to enable them.") unless present || !config.images
39
+ @active_storage_tables = present
40
+ rescue ActiveRecord::ActiveRecordError
41
+ false
42
+ end
43
+
44
+ # Forgets what was learned about the schema and what was logged (tests, and hosts that create the tables at runtime).
45
+ def reset_images_checks!
46
+ @active_storage_tables = nil
47
+ @images_off_logged = nil
48
+ @image_variants = nil
49
+ end
50
+
51
+ # Logged once at boot (see Shiplog::Engine): screenshots wanted but Active Storage missing. A missing
52
+ # table is logged by the check itself, the first time it runs.
53
+ def log_images_status
54
+ return unless config.images
55
+
56
+ if !active_storage_loaded? then log_images_off("Active Storage is not loaded. Require active_storage/engine to enable them.")
57
+ else active_storage_tables?
58
+ end
59
+ end
60
+
61
+ # Whether the admin can show thumbnails as variants: the image_processing gem is loaded and so is a
62
+ # processor for it (ruby-vips, which must load, or mini_magick). Otherwise the originals are shown.
63
+ def image_variants?
64
+ return @image_variants unless @image_variants.nil?
65
+
66
+ @image_variants = Gem.loaded_specs.key?("image_processing") && (vips_loads? || Gem.loaded_specs.key?("mini_magick"))
67
+ end
68
+
69
+ # Whether screenshots are in use: config.images and Active Storage.
70
+ def images? = config.images && active_storage?
71
+
72
+ private
73
+
74
+ def vips_loads?
75
+ return true if defined?(::Vips)
76
+
77
+ Gem.loaded_specs.key?("ruby-vips") && require("vips")
78
+ rescue LoadError
79
+ false
80
+ end
81
+
82
+ def log_images_off(reason)
83
+ @images_off_logged ||= Set.new
84
+ return unless @images_off_logged.add?(reason)
85
+
86
+ Rails.logger&.info("[shiplog] Screenshots are off: #{reason}")
87
+ end
15
88
  end
16
89
  end
@@ -0,0 +1,29 @@
1
+ # Shiplog::Engine and Shiplog::Admin::Engine share the gem's root, so Rails loads this file once for each: the
2
+ # guard keeps every task from running twice.
3
+ unless Rake::Task.task_defined?("shiplog:backfill_slugs")
4
+ namespace :shiplog do
5
+ desc "Give approved changelog entries that have no permalink slug one (entries approved by code older than 0.3.0 after the migration ran)"
6
+ task backfill_slugs: :environment do
7
+ count = Shiplog::Entry.backfill_slugs
8
+ puts "Shiplog: gave #{count} #{count == 1 ? "entry" : "entries"} a slug."
9
+ end
10
+
11
+ desc "Translate approved entries and area names that have no current translation into config.locales (entries approved before 0.3.0 get none). BATCH_SIZE=100"
12
+ task translate_approved: :environment do
13
+ if Shiplog.config.locales.empty?
14
+ puts "Shiplog: config.locales is empty, nothing to translate."
15
+ elsif Shiplog.config.rewriter.nil?
16
+ puts "Shiplog: config.rewriter is not set, so nothing can be translated."
17
+ else
18
+ result = Shiplog::Entry.enqueue_missing_translations(batch_size: ENV.fetch("BATCH_SIZE", 100).to_i.clamp(1, 10_000))
19
+ approved = result[:approved]
20
+ puts "Shiplog: #{approved} approved #{approved == 1 ? "entry" : "entries"}."
21
+ result[:enqueued].each do |locale, count|
22
+ puts "Shiplog: #{locale}: #{count} enqueued, #{approved - count} current."
23
+ end
24
+ areas = Shiplog::Area.enqueue_missing_translations
25
+ puts "Shiplog: #{areas} #{areas == 1 ? "area" : "areas"} enqueued for name translation."
26
+ end
27
+ end
28
+ end
29
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shiplog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Trackberry
@@ -40,14 +40,25 @@ files:
40
40
  - MIT-LICENSE
41
41
  - README.md
42
42
  - app/controllers/shiplog/admin/application_controller.rb
43
+ - app/controllers/shiplog/admin/area_detections_controller.rb
44
+ - app/controllers/shiplog/admin/area_translations_controller.rb
45
+ - app/controllers/shiplog/admin/areas/merges_controller.rb
46
+ - app/controllers/shiplog/admin/areas/moves_controller.rb
47
+ - app/controllers/shiplog/admin/areas_controller.rb
43
48
  - app/controllers/shiplog/admin/boards_controller.rb
44
49
  - app/controllers/shiplog/admin/entries/approvals_controller.rb
45
50
  - app/controllers/shiplog/admin/entries/discards_controller.rb
51
+ - app/controllers/shiplog/admin/entries/images/moves_controller.rb
52
+ - app/controllers/shiplog/admin/entries/images_controller.rb
46
53
  - app/controllers/shiplog/admin/entries/rewrites_controller.rb
54
+ - app/controllers/shiplog/admin/entries/translations/retranslations_controller.rb
55
+ - app/controllers/shiplog/admin/entries/translations_controller.rb
47
56
  - app/controllers/shiplog/admin/entries_controller.rb
48
57
  - app/controllers/shiplog/admin/inbox_filters.rb
49
58
  - app/controllers/shiplog/admin/syncs_controller.rb
50
59
  - app/controllers/shiplog/application_controller.rb
60
+ - app/controllers/shiplog/changelog_entries_controller.rb
61
+ - app/controllers/shiplog/changelog_feeds_controller.rb
51
62
  - app/controllers/shiplog/changelogs_controller.rb
52
63
  - app/controllers/shiplog/frontends_controller.rb
53
64
  - app/controllers/shiplog/public_controller.rb
@@ -62,20 +73,34 @@ files:
62
73
  - app/helpers/shiplog/callable_helper.rb
63
74
  - app/helpers/shiplog/frontend_helper.rb
64
75
  - app/helpers/shiplog/host_routes_helper.rb
76
+ - app/helpers/shiplog/permalink_helper.rb
65
77
  - app/helpers/shiplog/roadmap_helper.rb
66
78
  - app/helpers/shiplog/styling_helper.rb
67
79
  - app/helpers/shiplog/switcher_helper.rb
80
+ - app/helpers/shiplog/unseen_helper.rb
68
81
  - app/jobs/shiplog/application_job.rb
82
+ - app/jobs/shiplog/detect_areas_job.rb
69
83
  - app/jobs/shiplog/refresh_job.rb
70
84
  - app/jobs/shiplog/rewrite_entry_job.rb
71
85
  - app/jobs/shiplog/scan_changes_job.rb
72
86
  - app/jobs/shiplog/sync_job.rb
87
+ - app/jobs/shiplog/translate_area_job.rb
88
+ - app/jobs/shiplog/translate_entry_job.rb
73
89
  - app/models/shiplog/application_record.rb
90
+ - app/models/shiplog/area.rb
91
+ - app/models/shiplog/area_detector.rb
92
+ - app/models/shiplog/area_translator.rb
74
93
  - app/models/shiplog/board_arrangement.rb
75
94
  - app/models/shiplog/change_candidates.rb
76
95
  - app/models/shiplog/commit.rb
77
96
  - app/models/shiplog/drafter.rb
78
97
  - app/models/shiplog/entry.rb
98
+ - app/models/shiplog/entry/images.rb
99
+ - app/models/shiplog/entry_translation.rb
100
+ - app/models/shiplog/image_fetcher.rb
101
+ - app/models/shiplog/image_fetcher/downloader.rb
102
+ - app/models/shiplog/image_fetcher/sources.rb
103
+ - app/models/shiplog/image_type.rb
79
104
  - app/models/shiplog/issue.rb
80
105
  - app/models/shiplog/issue_form.rb
81
106
  - app/models/shiplog/issue_link.rb
@@ -86,12 +111,15 @@ files:
86
111
  - app/models/shiplog/rewriter/prompt.rb
87
112
  - app/models/shiplog/roadmap.rb
88
113
  - app/models/shiplog/roadmap/column_resolver.rb
114
+ - app/models/shiplog/setting.rb
89
115
  - app/models/shiplog/sync/commit_scanner.rb
90
116
  - app/models/shiplog/sync/issue_importer.rb
91
117
  - app/models/shiplog/sync/node_attributes.rb
92
118
  - app/models/shiplog/sync/pull_request_importer.rb
93
119
  - app/models/shiplog/sync/repository_sync.rb
94
120
  - app/models/shiplog/sync/upgrade_detector.rb
121
+ - app/models/shiplog/translator.rb
122
+ - app/models/shiplog/translator/prompt.rb
95
123
  - app/models/shiplog/webhook_delivery.rb
96
124
  - app/models/shiplog/webhook_signature.rb
97
125
  - app/views/layouts/shiplog/admin.html.erb
@@ -105,14 +133,21 @@ files:
105
133
  - app/views/shiplog/admin/_custom_head.html.erb
106
134
  - app/views/shiplog/admin/_nav.html.erb
107
135
  - app/views/shiplog/admin/_page.html.erb
136
+ - app/views/shiplog/admin/areas/index.html.erb
108
137
  - app/views/shiplog/admin/boards/_card.html.erb
109
138
  - app/views/shiplog/admin/boards/show.html.erb
110
139
  - app/views/shiplog/admin/entries/_badges.html.erb
111
140
  - app/views/shiplog/admin/entries/_entry_row.html.erb
141
+ - app/views/shiplog/admin/entries/_images.html.erb
142
+ - app/views/shiplog/admin/entries/_translations.html.erb
112
143
  - app/views/shiplog/admin/entries/edit.html.erb
113
144
  - app/views/shiplog/admin/entries/index.html.erb
114
145
  - app/views/shiplog/admin/syncs/show.html.erb
146
+ - app/views/shiplog/changelog_entries/show.html.erb
147
+ - app/views/shiplog/changelog_feeds/show.atom.builder
148
+ - app/views/shiplog/changelogs/_areas.html.erb
115
149
  - app/views/shiplog/changelogs/_entry.html.erb
150
+ - app/views/shiplog/changelogs/_entry_content.html.erb
116
151
  - app/views/shiplog/changelogs/_month.html.erb
117
152
  - app/views/shiplog/changelogs/_search.html.erb
118
153
  - app/views/shiplog/changelogs/show.html.erb
@@ -122,9 +157,11 @@ files:
122
157
  - app/views/shiplog/roadmaps/show.html.erb
123
158
  - config/admin_routes.rb
124
159
  - config/locales/en.yml
160
+ - config/locales/es.yml
125
161
  - config/routes.rb
126
162
  - db/migrate/20261026000001_create_shiplog_tables.rb
127
163
  - db/migrate/20261027000001_create_shiplog_pull_request_upgrades.rb
164
+ - db/migrate/20261027000002_shiplog_0_3_0.rb
128
165
  - lib/generators/shiplog/install/install_generator.rb
129
166
  - lib/generators/shiplog/install/templates/initializer.rb.tt
130
167
  - lib/generators/shiplog/install/templates/shiplog-feature.yml
@@ -137,6 +174,7 @@ files:
137
174
  - lib/shiplog/github/errors.rb
138
175
  - lib/shiplog/github/queries.rb
139
176
  - lib/shiplog/version.rb
177
+ - lib/tasks/shiplog.rake
140
178
  homepage: https://github.com/Trackberry/shiplog
141
179
  licenses:
142
180
  - MIT