shiplog 0.1.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 (92) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +15 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +410 -0
  5. data/app/controllers/shiplog/admin/application_controller.rb +37 -0
  6. data/app/controllers/shiplog/admin/boards_controller.rb +60 -0
  7. data/app/controllers/shiplog/admin/entries/approvals_controller.rb +15 -0
  8. data/app/controllers/shiplog/admin/entries/discards_controller.rb +25 -0
  9. data/app/controllers/shiplog/admin/entries/rewrites_controller.rb +13 -0
  10. data/app/controllers/shiplog/admin/entries_controller.rb +55 -0
  11. data/app/controllers/shiplog/admin/syncs_controller.rb +21 -0
  12. data/app/controllers/shiplog/application_controller.rb +5 -0
  13. data/app/controllers/shiplog/changelogs_controller.rb +24 -0
  14. data/app/controllers/shiplog/frontends_controller.rb +21 -0
  15. data/app/controllers/shiplog/public_controller.rb +19 -0
  16. data/app/controllers/shiplog/roadmaps_controller.rb +14 -0
  17. data/app/controllers/shiplog/webhooks_controller.rb +121 -0
  18. data/app/frontend/shiplog/admin.css +137 -0
  19. data/app/frontend/shiplog/board.js +77 -0
  20. data/app/frontend/shiplog/shiplog.css +184 -0
  21. data/app/helpers/shiplog/admin/entries_helper.rb +27 -0
  22. data/app/helpers/shiplog/application_helper.rb +14 -0
  23. data/app/helpers/shiplog/frontend_helper.rb +17 -0
  24. data/app/helpers/shiplog/host_routes_helper.rb +25 -0
  25. data/app/helpers/shiplog/styling_helper.rb +24 -0
  26. data/app/jobs/shiplog/application_job.rb +14 -0
  27. data/app/jobs/shiplog/refresh_job.rb +38 -0
  28. data/app/jobs/shiplog/rewrite_entry_job.rb +9 -0
  29. data/app/jobs/shiplog/scan_changes_job.rb +36 -0
  30. data/app/jobs/shiplog/sync_job.rb +20 -0
  31. data/app/models/shiplog/application_record.rb +5 -0
  32. data/app/models/shiplog/board_arrangement.rb +38 -0
  33. data/app/models/shiplog/change_candidates.rb +77 -0
  34. data/app/models/shiplog/commit.rb +9 -0
  35. data/app/models/shiplog/drafter.rb +69 -0
  36. data/app/models/shiplog/entry.rb +42 -0
  37. data/app/models/shiplog/issue.rb +68 -0
  38. data/app/models/shiplog/issue_form.rb +43 -0
  39. data/app/models/shiplog/issue_link.rb +6 -0
  40. data/app/models/shiplog/pull_request.rb +11 -0
  41. data/app/models/shiplog/repository.rb +9 -0
  42. data/app/models/shiplog/rewriter/prompt.rb +71 -0
  43. data/app/models/shiplog/rewriter.rb +89 -0
  44. data/app/models/shiplog/roadmap/column_resolver.rb +46 -0
  45. data/app/models/shiplog/roadmap.rb +34 -0
  46. data/app/models/shiplog/sync/commit_scanner.rb +39 -0
  47. data/app/models/shiplog/sync/issue_importer.rb +50 -0
  48. data/app/models/shiplog/sync/node_attributes.rb +50 -0
  49. data/app/models/shiplog/sync/pull_request_importer.rb +54 -0
  50. data/app/models/shiplog/sync/repository_sync.rb +80 -0
  51. data/app/models/shiplog/webhook_delivery.rb +5 -0
  52. data/app/models/shiplog/webhook_signature.rb +16 -0
  53. data/app/views/layouts/shiplog/admin.html.erb +16 -0
  54. data/app/views/layouts/shiplog/application.html.erb +15 -0
  55. data/app/views/shiplog/_empty.html.erb +3 -0
  56. data/app/views/shiplog/_page.html.erb +6 -0
  57. data/app/views/shiplog/_page_header.html.erb +6 -0
  58. data/app/views/shiplog/_pagination.html.erb +12 -0
  59. data/app/views/shiplog/admin/_assets.html.erb +3 -0
  60. data/app/views/shiplog/admin/_custom_head.html.erb +0 -0
  61. data/app/views/shiplog/admin/_nav.html.erb +9 -0
  62. data/app/views/shiplog/admin/_page.html.erb +14 -0
  63. data/app/views/shiplog/admin/boards/_card.html.erb +22 -0
  64. data/app/views/shiplog/admin/boards/show.html.erb +17 -0
  65. data/app/views/shiplog/admin/entries/_badges.html.erb +10 -0
  66. data/app/views/shiplog/admin/entries/_entry_row.html.erb +21 -0
  67. data/app/views/shiplog/admin/entries/edit.html.erb +61 -0
  68. data/app/views/shiplog/admin/entries/index.html.erb +47 -0
  69. data/app/views/shiplog/admin/syncs/show.html.erb +71 -0
  70. data/app/views/shiplog/changelogs/_entry.html.erb +8 -0
  71. data/app/views/shiplog/changelogs/_month.html.erb +9 -0
  72. data/app/views/shiplog/changelogs/show.html.erb +16 -0
  73. data/app/views/shiplog/roadmaps/_card.html.erb +6 -0
  74. data/app/views/shiplog/roadmaps/_column.html.erb +17 -0
  75. data/app/views/shiplog/roadmaps/show.html.erb +9 -0
  76. data/config/admin_routes.rb +13 -0
  77. data/config/locales/en.yml +106 -0
  78. data/config/routes.rb +6 -0
  79. data/db/migrate/20261026000001_create_shiplog_tables.rb +102 -0
  80. data/lib/generators/shiplog/install/install_generator.rb +94 -0
  81. data/lib/generators/shiplog/install/templates/initializer.rb.tt +86 -0
  82. data/lib/generators/shiplog/install/templates/shiplog-feature.yml +23 -0
  83. data/lib/shiplog/admin/engine.rb +9 -0
  84. data/lib/shiplog/classes.rb +38 -0
  85. data/lib/shiplog/configuration.rb +92 -0
  86. data/lib/shiplog/engine.rb +5 -0
  87. data/lib/shiplog/github/client.rb +96 -0
  88. data/lib/shiplog/github/errors.rb +17 -0
  89. data/lib/shiplog/github/queries.rb +108 -0
  90. data/lib/shiplog/version.rb +3 -0
  91. data/lib/shiplog.rb +16 -0
  92. metadata +154 -0
@@ -0,0 +1,16 @@
1
+ module Shiplog
2
+ class WebhookSignature
3
+ def initialize(secret:, body:, header:)
4
+ @secret = secret
5
+ @body = body
6
+ @header = header
7
+ end
8
+
9
+ def valid?
10
+ return false if @header.blank?
11
+
12
+ expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", @secret, @body)
13
+ Rack::Utils.secure_compare(expected, @header)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= I18n.locale %>">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title><%= content_for(:title).presence || t("shiplog.admin.title") %></title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ <%= render "shiplog/admin/custom_head" %>
10
+ </head>
11
+ <body class="shiplog-admin-body">
12
+ <main>
13
+ <%= yield %>
14
+ </main>
15
+ </body>
16
+ </html>
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= I18n.locale %>">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title><%= content_for(:title) %></title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ </head>
10
+ <body class="shiplog-layout">
11
+ <main>
12
+ <%= yield %>
13
+ </main>
14
+ </body>
15
+ </html>
@@ -0,0 +1,3 @@
1
+ <%# locals: (message: t("shiplog.empty")) %>
2
+ <%# the "nothing here yet" notice of a page %>
3
+ <p class="<%= shiplog_class(:empty) %>"><%= message %></p>
@@ -0,0 +1,6 @@
1
+ <%# locals: (name:) %>
2
+ <%# rendered with `render layout:`; wraps a public page and emits the bundled stylesheet (unless config.stylesheet is false) %>
3
+ <%= shiplog_stylesheet_tag "shiplog" if Shiplog.config.stylesheet %>
4
+ <div class="<%= shiplog_class(:page, name) %>">
5
+ <%= yield %>
6
+ </div>
@@ -0,0 +1,6 @@
1
+ <%# locals: (title:, lede: nil) %>
2
+ <%# the page heading; the texts come from I18n (shiplog.<page>.title / .lede) %>
3
+ <header class="<%= shiplog_class(:page_header) %>">
4
+ <h1 class="<%= shiplog_class(:page_title) %>"><%= title %></h1>
5
+ <% if lede.present? %><p class="<%= shiplog_class(:page_lede) %>"><%= lede %></p><% end %>
6
+ </header>
@@ -0,0 +1,12 @@
1
+ <%# locals: (newer_path: nil, older_path: nil) %>
2
+ <%# previous/next page links; renders nothing when both are nil %>
3
+ <% if newer_path || older_path %>
4
+ <nav class="<%= shiplog_class(:pagination) %>" aria-label="<%= t("shiplog.changelog.pagination.label") %>">
5
+ <% if newer_path %>
6
+ <%= link_to t("shiplog.changelog.pagination.newer"), newer_path, class: shiplog_class(:pagination_link, :newer), rel: "prev" %>
7
+ <% end %>
8
+ <% if older_path %>
9
+ <%= link_to t("shiplog.changelog.pagination.older"), older_path, class: shiplog_class(:pagination_link, :older), rel: "next" %>
10
+ <% end %>
11
+ </nav>
12
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%# locals: () %>
2
+ <%# the admin stylesheet, linked by every admin page so it works in any layout (nonce-aware) %>
3
+ <%= shiplog_stylesheet_tag "admin" %>
File without changes
@@ -0,0 +1,9 @@
1
+ <%# locals: (hosted: false) %>
2
+ <%# the Drafts / Roadmap board / Sync links; `hosted` renders the compact variant used inside a host layout %>
3
+ <nav class="shiplog-admin-nav<%= " shiplog-admin-nav--sub" if hosted %>" aria-label="<%= t("shiplog.admin.title") %>">
4
+ <ul class="shiplog-admin-nav__list">
5
+ <li><%= link_to t("shiplog.admin.nav.drafts"), root_path, class: "shiplog-admin-nav__link" %></li>
6
+ <li><%= link_to t("shiplog.admin.nav.board"), board_path, class: "shiplog-admin-nav__link" %></li>
7
+ <li><%= link_to t("shiplog.admin.nav.sync"), sync_path, class: "shiplog-admin-nav__link" %></li>
8
+ </ul>
9
+ </nav>
@@ -0,0 +1,14 @@
1
+ <%# locals: () %>
2
+ <%# rendered with `render layout:`; wraps an admin page in the scoped .shiplog-admin container with its assets, nav and flash %>
3
+ <% hosted = shiplog_admin_hosted? %>
4
+ <%= render "shiplog/admin/assets" %>
5
+ <div class="shiplog-admin<%= " shiplog-admin--standalone" unless hosted %>">
6
+ <%= render "shiplog/admin/nav", hosted: %>
7
+ <div class="shiplog-admin__main">
8
+ <% if !hosted || Shiplog.config.admin_flash %>
9
+ <% if notice %><p class="shiplog-admin__flash shiplog-admin__flash--notice" role="status"><%= notice %></p><% end %>
10
+ <% if alert %><p class="shiplog-admin__flash shiplog-admin__flash--alert" role="alert"><%= alert %></p><% end %>
11
+ <% end %>
12
+ <%= yield %>
13
+ </div>
14
+ </div>
@@ -0,0 +1,22 @@
1
+ <% issue = item.issue %>
2
+ <article class="shiplog-card" draggable="true" data-issue-id="<%= issue.id %>">
3
+ <h3 class="shiplog-card__title"><%= item.entry&.title.presence || issue.title %></h3>
4
+ <p class="shiplog-card__meta">
5
+ <%= shiplog_github_link("#{issue.repository.full_name}##{issue.number}", issue.url) %>
6
+ <% unless item.entry&.approved? %>
7
+ <span class="shiplog-badge shiplog-badge--warning"><%= t("shiplog.admin.board.not_approved") %></span>
8
+ <% end %>
9
+ <% if issue.pinned_column %>
10
+ <span class="shiplog-badge"><%= t("shiplog.admin.board.pinned") %></span>
11
+ <% end %>
12
+ </p>
13
+ <%= form_with url: board_path, method: :patch, class: "shiplog-card__form" do |form| %>
14
+ <%= form.hidden_field :issue_id, value: issue.id, id: nil %>
15
+ <%= form.label :column, t("shiplog.admin.board.move_to"), for: "column-#{issue.id}", class: "shiplog-card__label" %>
16
+ <%= form.select :column, Shiplog::Issue::COLUMNS.map { |column| [ t("shiplog.columns.#{column}"), column ] }, { selected: item.column }, id: "column-#{issue.id}" %>
17
+ <%= form.submit t("shiplog.admin.board.move"), class: "shiplog-button", data: { disable_with: false } %>
18
+ <% end %>
19
+ <% if issue.pinned_column %>
20
+ <%= button_to t("shiplog.admin.board.unpin"), board_path, method: :patch, params: { issue_id: issue.id, unpin: "1" }, class: "shiplog-button" %>
21
+ <% end %>
22
+ </article>
@@ -0,0 +1,17 @@
1
+ <%= render layout: "shiplog/admin/page" do %>
2
+ <h1 class="shiplog-admin__title"><%= t("shiplog.admin.board.title") %></h1>
3
+ <p class="shiplog-admin__muted"><%= t("shiplog.admin.board.help") %></p>
4
+
5
+ <div class="shiplog-board" data-board-url="<%= board_path %>" data-csrf-token="<%= form_authenticity_token %>">
6
+ <% @columns.each do |column, items| %>
7
+ <section class="shiplog-board__column" aria-labelledby="shiplog-board-<%= column %>">
8
+ <h2 id="shiplog-board-<%= column %>" class="shiplog-board__title"><%= t("shiplog.columns.#{column}") %></h2>
9
+ <div class="shiplog-board__cards" data-column="<%= column %>">
10
+ <%= render partial: "shiplog/admin/boards/card", collection: items, as: :item %>
11
+ </div>
12
+ </section>
13
+ <% end %>
14
+ </div>
15
+
16
+ <%= shiplog_javascript_tag "board" %>
17
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <% if entry.source_changed? %>
2
+ <span class="shiplog-badge shiplog-badge--warning"><%= t("shiplog.admin.entries.source_changed") %></span>
3
+ <% end %>
4
+ <% if entry.draft? && entry.rewritten_at.nil? && entry.rewrite_error.blank? %>
5
+ <span class="shiplog-badge"><%= t("shiplog.admin.entries.not_rewritten") %></span>
6
+ <% end %>
7
+ <% if entry.rewrite_error.present? %>
8
+ <span class="shiplog-badge shiplog-badge--error"><%= t("shiplog.admin.entries.rewrite_error") %></span>
9
+ <div class="shiplog-admin__muted"><%= entry.rewrite_error %></div>
10
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <tr class="shiplog-entry-row">
2
+ <td>
3
+ <%= link_to entry_heading(entry), edit_entry_path(entry) %>
4
+ <%= render "shiplog/admin/entries/badges", entry: %>
5
+ <% if entry.discarded? && entry.discard_reason.present? %>
6
+ <div class="shiplog-admin__muted"><%= entry.discard_reason %></div>
7
+ <% end %>
8
+ </td>
9
+ <td><%= t("shiplog.admin.entries.kinds.#{entry.kind}") %></td>
10
+ <td>
11
+ <%= shiplog_github_link(source_reference(entry.subject), entry.subject.url) %>
12
+ </td>
13
+ <td class="shiplog-admin-table__actions">
14
+ <%= link_to t("shiplog.admin.entries.edit"), edit_entry_path(entry), class: "shiplog-button" %>
15
+ <% if entry.draft? %>
16
+ <%= button_to t("shiplog.admin.entries.approve"), entry_approval_path(entry), class: "shiplog-button shiplog-button--primary" %>
17
+ <% elsif entry.discarded? %>
18
+ <%= button_to t("shiplog.admin.entries.restore"), entry_discard_path(entry), method: :delete, class: "shiplog-button" %>
19
+ <% end %>
20
+ </td>
21
+ </tr>
@@ -0,0 +1,61 @@
1
+ <%= render layout: "shiplog/admin/page" do %>
2
+ <h1 class="shiplog-admin__title"><%= t("shiplog.admin.entries.edit_title", kind: t("shiplog.admin.entries.kinds.#{@entry.kind}")) %></h1>
3
+ <p>
4
+ <span class="shiplog-badge"><%= t("shiplog.admin.entries.statuses.#{@entry.status}") %></span>
5
+ <%= render "shiplog/admin/entries/badges", entry: @entry %>
6
+ </p>
7
+
8
+ <div class="shiplog-admin-split">
9
+ <section class="shiplog-admin-panel">
10
+ <h2><%= t("shiplog.admin.entries.public_text") %></h2>
11
+ <%= form_with model: @entry, url: entry_path(@entry), method: :patch, class: "shiplog-admin-form" do |form| %>
12
+ <% if @entry.errors.any? %>
13
+ <div class="shiplog-admin__flash shiplog-admin__flash--alert" role="alert">
14
+ <%= @entry.errors.full_messages.to_sentence %>
15
+ </div>
16
+ <% end %>
17
+ <p>
18
+ <%= form.label :title, t("shiplog.admin.entries.fields.title") %>
19
+ <%= form.text_field :title %>
20
+ </p>
21
+ <p>
22
+ <%= form.label :body, t("shiplog.admin.entries.fields.body") %>
23
+ <%= form.text_area :body, rows: 8 %>
24
+ </p>
25
+ <p>
26
+ <%= form.label :released_on, t("shiplog.admin.entries.fields.released_on") %>
27
+ <%= form.date_field :released_on %>
28
+ </p>
29
+ <%= form.submit t("shiplog.admin.entries.save"), class: "shiplog-button shiplog-button--primary" %>
30
+ <% end %>
31
+
32
+ <div class="shiplog-admin-actions">
33
+ <% if @entry.draft? %>
34
+ <%= button_to t("shiplog.admin.entries.approve"), entry_approval_path(@entry), class: "shiplog-button shiplog-button--primary" %>
35
+ <%= form_with url: entry_discard_path(@entry), class: "shiplog-admin-inline-form" do |form| %>
36
+ <%= form.label :reason, t("shiplog.admin.entries.fields.reason") %>
37
+ <%= form.text_field :reason %>
38
+ <%= form.submit t("shiplog.admin.entries.discard"), class: "shiplog-button" %>
39
+ <% end %>
40
+ <% elsif @entry.discarded? %>
41
+ <%= button_to t("shiplog.admin.entries.restore"), entry_discard_path(@entry), method: :delete, class: "shiplog-button" %>
42
+ <% end %>
43
+ <%= button_to t("shiplog.admin.entries.regenerate"), entry_rewrite_path(@entry), class: "shiplog-button" %>
44
+ <% if @entry.approved? %>
45
+ <p class="shiplog-admin__muted"><%= t("shiplog.admin.entries.regenerate_warning") %></p>
46
+ <% end %>
47
+ </div>
48
+ </section>
49
+
50
+ <aside class="shiplog-admin-panel">
51
+ <h2><%= t("shiplog.admin.entries.source") %></h2>
52
+ <p>
53
+ <strong><%= source_title(@entry.subject) %></strong><br>
54
+ <%= shiplog_github_link(source_reference(@entry.subject), @entry.subject.url) %>
55
+ </p>
56
+ <pre class="shiplog-admin-source"><%= source_body(@entry.subject) %></pre>
57
+ </aside>
58
+ </div>
59
+
60
+ <p><%= link_to t("shiplog.admin.entries.back"), root_path %></p>
61
+ <% end %>
@@ -0,0 +1,47 @@
1
+ <%= render layout: "shiplog/admin/page" do %>
2
+ <h1 class="shiplog-admin__title"><%= t("shiplog.admin.entries.title") %></h1>
3
+
4
+ <nav class="shiplog-admin-filters" aria-label="<%= t("shiplog.admin.entries.filters") %>">
5
+ <ul class="shiplog-admin-filters__list">
6
+ <% Shiplog::Entry.statuses.each_key do |status| %>
7
+ <li><%= link_to t("shiplog.admin.entries.statuses.#{status}"), entries_filter_path(status:, kind: @kind),
8
+ class: "shiplog-admin-filters__link", "aria-current": (status == @status ? "true" : nil) %></li>
9
+ <% end %>
10
+ </ul>
11
+ <ul class="shiplog-admin-filters__list">
12
+ <li><%= link_to t("shiplog.admin.entries.all_kinds"), entries_filter_path(status: @status),
13
+ class: "shiplog-admin-filters__link", "aria-current": (@kind.nil? ? "true" : nil) %></li>
14
+ <% Shiplog::Entry.kinds.each_key do |kind| %>
15
+ <li><%= link_to t("shiplog.admin.entries.kinds.#{kind}"), entries_filter_path(status: @status, kind:),
16
+ class: "shiplog-admin-filters__link", "aria-current": (kind == @kind ? "true" : nil) %></li>
17
+ <% end %>
18
+ </ul>
19
+ </nav>
20
+
21
+ <% if @entries.empty? %>
22
+ <p class="shiplog-admin__empty"><%= t("shiplog.admin.entries.empty") %></p>
23
+ <% else %>
24
+ <table class="shiplog-admin-table">
25
+ <thead>
26
+ <tr>
27
+ <th scope="col"><%= t("shiplog.admin.entries.columns.entry") %></th>
28
+ <th scope="col"><%= t("shiplog.admin.entries.columns.kind") %></th>
29
+ <th scope="col"><%= t("shiplog.admin.entries.columns.source") %></th>
30
+ <th scope="col"><%= t("shiplog.admin.entries.columns.actions") %></th>
31
+ </tr>
32
+ </thead>
33
+ <tbody>
34
+ <%= render partial: "shiplog/admin/entries/entry_row", collection: @entries, as: :entry %>
35
+ </tbody>
36
+ </table>
37
+ <% end %>
38
+
39
+ <nav class="shiplog-admin-pagination" aria-label="<%= t("shiplog.admin.entries.pagination") %>">
40
+ <% if @page > 1 %>
41
+ <%= link_to t("shiplog.admin.entries.newer"), entries_filter_path(status: @status, kind: @kind, page: (@page - 1 if @page > 2)), class: "shiplog-admin-pagination__prev" %>
42
+ <% end %>
43
+ <% if @more %>
44
+ <%= link_to t("shiplog.admin.entries.older"), entries_filter_path(status: @status, kind: @kind, page: @page + 1), class: "shiplog-admin-pagination__next" %>
45
+ <% end %>
46
+ </nav>
47
+ <% end %>
@@ -0,0 +1,71 @@
1
+ <%= render layout: "shiplog/admin/page" do %>
2
+ <h1 class="shiplog-admin__title"><%= t("shiplog.admin.sync.title") %></h1>
3
+
4
+ <% if @webhook_secret_missing %>
5
+ <p class="shiplog-admin__flash shiplog-admin__flash--alert shiplog-warning" role="alert"><%= t("shiplog.admin.sync.webhook_secret_missing") %></p>
6
+ <% end %>
7
+ <% if @github_token_missing %>
8
+ <p class="shiplog-admin__flash shiplog-admin__flash--alert shiplog-warning" role="alert"><%= t("shiplog.admin.sync.github_token_missing") %></p>
9
+ <% end %>
10
+
11
+ <div class="shiplog-admin-actions">
12
+ <%= button_to t("shiplog.admin.sync.sync_now"), sync_path, class: "shiplog-button shiplog-button--primary" %>
13
+ <%= button_to t("shiplog.admin.sync.sync_and_scan"), sync_path, params: { scan: "1" }, class: "shiplog-button" %>
14
+ </div>
15
+
16
+ <h2><%= t("shiplog.admin.sync.repositories") %></h2>
17
+ <% if @repositories.empty? %>
18
+ <p class="shiplog-admin__empty"><%= t("shiplog.admin.sync.no_repositories") %></p>
19
+ <% else %>
20
+ <table class="shiplog-admin-table">
21
+ <thead>
22
+ <tr>
23
+ <th scope="col"><%= t("shiplog.admin.sync.repository") %></th>
24
+ <th scope="col"><%= t("shiplog.admin.sync.synced_at") %></th>
25
+ <th scope="col"><%= t("shiplog.admin.sync.changes_scanned_at") %></th>
26
+ <th scope="col"><%= t("shiplog.admin.sync.last_error") %></th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <% @repositories.each do |full_name, repository| %>
31
+ <tr class="shiplog-repository-row">
32
+ <td><%= full_name %></td>
33
+ <td><%= repository&.synced_at ? l(repository.synced_at, format: :short) : t("shiplog.admin.sync.never") %></td>
34
+ <td><%= repository&.changes_scanned_at ? l(repository.changes_scanned_at, format: :short) : t("shiplog.admin.sync.never") %></td>
35
+ <td><%= repository&.last_error %></td>
36
+ </tr>
37
+ <% end %>
38
+ </tbody>
39
+ </table>
40
+ <% end %>
41
+
42
+ <h2><%= t("shiplog.admin.sync.deliveries") %></h2>
43
+ <% if @deliveries.empty? %>
44
+ <p class="shiplog-admin__empty"><%= t("shiplog.admin.sync.no_deliveries") %></p>
45
+ <% else %>
46
+ <table class="shiplog-admin-table">
47
+ <thead>
48
+ <tr>
49
+ <th scope="col"><%= t("shiplog.admin.sync.event") %></th>
50
+ <th scope="col"><%= t("shiplog.admin.sync.action") %></th>
51
+ <th scope="col"><%= t("shiplog.admin.sync.repository") %></th>
52
+ <th scope="col"><%= t("shiplog.admin.sync.received_at") %></th>
53
+ <th scope="col"><%= t("shiplog.admin.sync.processed_at") %></th>
54
+ <th scope="col"><%= t("shiplog.admin.sync.error") %></th>
55
+ </tr>
56
+ </thead>
57
+ <tbody>
58
+ <% @deliveries.each do |delivery| %>
59
+ <tr class="shiplog-delivery-row">
60
+ <td><%= delivery.event %></td>
61
+ <td><%= delivery.action %></td>
62
+ <td><%= delivery.repository_full_name %></td>
63
+ <td><%= delivery.received_at && l(delivery.received_at, format: :short) %></td>
64
+ <td><%= delivery.processed_at && l(delivery.processed_at, format: :short) %></td>
65
+ <td><%= delivery.error %></td>
66
+ </tr>
67
+ <% end %>
68
+ </tbody>
69
+ </table>
70
+ <% end %>
71
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%# locals: (entry:) %>
2
+ <%# one changelog entry: category pill ("New" for features, "Improved" for changes), date, title and body %>
3
+ <li class="<%= token_list(shiplog_class(:entry, entry.kind), (shiplog_class(:entry_feature) if entry.feature?)) %>">
4
+ <span class="<%= shiplog_class(:entry_category) %>"><%= t("shiplog.categories.#{entry.kind}") %></span>
5
+ <time class="<%= shiplog_class(:entry_date) %>" datetime="<%= entry.released_on.iso8601 %>"><%= l(entry.released_on, format: t("shiplog.date_format", default: "%-d %B %Y")) %></time>
6
+ <h3 class="<%= shiplog_class(:entry_title) %>"><%= entry.title %></h3>
7
+ <div class="<%= shiplog_class(:entry_body) %>"><%= simple_format(h(entry.body)) %></div>
8
+ </li>
@@ -0,0 +1,9 @@
1
+ <%# locals: (month:, entries:) %>
2
+ <%# one month of the changelog (month is its first day) and its entries, newest first %>
3
+ <% heading_id = "shiplog-month-#{month.strftime("%Y-%m")}" %>
4
+ <section class="<%= shiplog_class(:month) %>" aria-labelledby="<%= heading_id %>">
5
+ <h2 class="<%= shiplog_class(:month_title) %>" id="<%= heading_id %>"><%= l(month, format: "%B %Y") %></h2>
6
+ <ul class="<%= shiplog_class(:entries) %>">
7
+ <%= render partial: "shiplog/changelogs/entry", collection: entries, as: :entry %>
8
+ </ul>
9
+ </section>
@@ -0,0 +1,16 @@
1
+ <% content_for :title, t("shiplog.changelog.title") %>
2
+ <%= render layout: "shiplog/page", locals: { name: "changelog" } do %>
3
+ <%= render "shiplog/page_header", title: t("shiplog.changelog.title"), lede: t("shiplog.changelog.lede") %>
4
+ <% if @months.empty? %>
5
+ <%= render "shiplog/empty" %>
6
+ <% else %>
7
+ <div class="<%= shiplog_class(:months) %>">
8
+ <% @months.each do |month, entries| %>
9
+ <%= render "shiplog/changelogs/month", month:, entries: %>
10
+ <% end %>
11
+ </div>
12
+ <% end %>
13
+ <%= render "shiplog/pagination",
14
+ newer_path: (changelog_path(page: (@page - 1 if @page > 2)) if @page > 1),
15
+ older_path: (changelog_path(page: @page + 1) if @older) %>
16
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <%# locals: (entry:) %>
2
+ <%# one roadmap item: its public title and body %>
3
+ <li class="<%= shiplog_class(:card) %>">
4
+ <h3 class="<%= shiplog_class(:card_title) %>"><%= entry.title %></h3>
5
+ <div class="<%= shiplog_class(:card_body) %>"><%= simple_format(h(entry.body)) %></div>
6
+ </li>
@@ -0,0 +1,17 @@
1
+ <%# locals: (column:, entries:) %>
2
+ <%# one roadmap column (in_progress, coming_soon or later) and its approved entries %>
3
+ <% heading_id = "shiplog-column-#{column.dasherize}" %>
4
+ <section class="<%= shiplog_class(:column, column) %>" aria-labelledby="<%= heading_id %>">
5
+ <header class="<%= shiplog_class(:column_header) %>">
6
+ <h2 class="<%= shiplog_class(:column_title) %>" id="<%= heading_id %>"><%= t("shiplog.columns.#{column}") %></h2>
7
+ <span class="<%= shiplog_class(:column_count) %>"><%= entries.size %></span>
8
+ <p class="<%= shiplog_class(:column_description) %>"><%= t("shiplog.columns.#{column}_description") %></p>
9
+ </header>
10
+ <% if entries.empty? %>
11
+ <p class="<%= shiplog_class(:column_empty) %>"><%= t("shiplog.empty") %></p>
12
+ <% else %>
13
+ <ol class="<%= shiplog_class(:cards) %>">
14
+ <%= render partial: "shiplog/roadmaps/card", collection: entries, as: :entry %>
15
+ </ol>
16
+ <% end %>
17
+ </section>
@@ -0,0 +1,9 @@
1
+ <% content_for :title, t("shiplog.roadmap.title") %>
2
+ <%= render layout: "shiplog/page", locals: { name: "roadmap" } do %>
3
+ <%= render "shiplog/page_header", title: t("shiplog.roadmap.title"), lede: t("shiplog.roadmap.lede") %>
4
+ <div class="<%= shiplog_class(:columns) %>">
5
+ <% @columns.each do |column, items| %>
6
+ <%= render "shiplog/roadmaps/column", column:, entries: items.map(&:entry) %>
7
+ <% end %>
8
+ </div>
9
+ <% end %>
@@ -0,0 +1,13 @@
1
+ Shiplog::Admin::Engine.routes.draw do
2
+ root to: "entries#index"
3
+ resources :entries, only: %i[index edit update] do
4
+ scope module: :entries do
5
+ resource :approval, only: :create
6
+ resource :discard, only: %i[create destroy]
7
+ resource :rewrite, only: :create
8
+ end
9
+ end
10
+ resource :board, only: %i[show update]
11
+ resource :sync, only: %i[show create]
12
+ get "assets/:version/:name", to: "/shiplog/frontends#show", as: :frontend, constraints: { version: /[^\/]+/, name: /[a-z_]+\.(css|js)/ }
13
+ end
@@ -0,0 +1,106 @@
1
+ en:
2
+ shiplog:
3
+ columns:
4
+ in_progress: In progress
5
+ coming_soon: Coming soon
6
+ later: Later
7
+ in_progress_description: What we're building now
8
+ coming_soon_description: Planned next
9
+ later_description: On our radar
10
+ categories:
11
+ feature: New
12
+ change: Improved
13
+ empty: Nothing here yet
14
+ date_format: "%-d %B %Y"
15
+ roadmap:
16
+ title: Roadmap
17
+ lede: What we're working on and what comes next.
18
+ changelog:
19
+ title: Changelog
20
+ lede: New features and improvements, as they ship.
21
+ pagination:
22
+ label: Pagination
23
+ older: Older
24
+ newer: Newer
25
+ admin:
26
+ title: Shiplog admin
27
+ nav:
28
+ drafts: Drafts
29
+ board: Roadmap board
30
+ sync: Sync
31
+ entries:
32
+ title: Entries
33
+ filters: Filter entries
34
+ all_kinds: All kinds
35
+ empty: No entries match.
36
+ pagination: Pagination
37
+ older: Older
38
+ newer: Newer
39
+ saved: Entry saved.
40
+ approved: Entry approved.
41
+ discarded: Entry discarded.
42
+ restored: Entry restored to draft.
43
+ invalid_transition: That action is not available for this entry in its current state.
44
+ rewrite_queued: Rewrite queued. Refresh in a moment.
45
+ edit: Edit
46
+ edit_title: "Edit %{kind}"
47
+ approve: Approve
48
+ discard: Discard
49
+ restore: Restore
50
+ save: Save
51
+ regenerate: Regenerate
52
+ regenerate_warning: Regenerating unpublishes this entry until re-approved.
53
+ source_changed: GitHub text changed since this draft
54
+ rewrite_error: Rewrite failed
55
+ not_rewritten: Not rewritten yet
56
+ public_text: Public text
57
+ source: GitHub source
58
+ back: Back to drafts
59
+ statuses:
60
+ draft: Drafts
61
+ approved: Approved
62
+ discarded: Discarded
63
+ kinds:
64
+ roadmap: Roadmap
65
+ feature: Feature
66
+ change: Change
67
+ columns:
68
+ entry: Entry
69
+ kind: Kind
70
+ source: Source
71
+ actions: Actions
72
+ fields:
73
+ title: Title
74
+ body: Text
75
+ released_on: Released on
76
+ reason: Reason
77
+ board:
78
+ title: Roadmap board
79
+ help: Drag cards between columns to pin and reorder them. Without JavaScript, use the Move button on each card.
80
+ updated: Board updated.
81
+ not_approved: Not approved
82
+ pinned: Pinned
83
+ move_to: Move to
84
+ move: Move
85
+ unpin: Unpin
86
+ sync:
87
+ title: Sync status
88
+ queued: Sync queued.
89
+ webhook_secret_missing: The webhook secret is not configured, so GitHub webhooks are rejected.
90
+ github_token_missing: The GitHub token is not configured, so syncing cannot run.
91
+ sync_now: Sync now
92
+ sync_and_scan: Sync and scan for changes
93
+ repositories: Repositories
94
+ no_repositories: No repositories are configured.
95
+ repository: Repository
96
+ synced_at: Last synced
97
+ changes_scanned_at: Changes scanned
98
+ last_error: Last error
99
+ never: Never
100
+ deliveries: Recent webhook deliveries
101
+ no_deliveries: No deliveries yet.
102
+ event: Event
103
+ action: Action
104
+ received_at: Received
105
+ processed_at: Processed
106
+ error: Error
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ Shiplog::Engine.routes.draw do
2
+ get Shiplog.config.roadmap_path, to: "roadmaps#show", as: :roadmap
3
+ get Shiplog.config.changelog_path, to: "changelogs#show", as: :changelog
4
+ post "shiplog/github/webhook", to: "webhooks#create", as: :github_webhook
5
+ get "shiplog/assets/:version/:name", to: "frontends#show", as: :frontend, constraints: { version: /[^\/]+/, name: /[a-z_]+\.(css|js)/ }
6
+ end