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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +410 -0
- data/app/controllers/shiplog/admin/application_controller.rb +37 -0
- data/app/controllers/shiplog/admin/boards_controller.rb +60 -0
- data/app/controllers/shiplog/admin/entries/approvals_controller.rb +15 -0
- data/app/controllers/shiplog/admin/entries/discards_controller.rb +25 -0
- data/app/controllers/shiplog/admin/entries/rewrites_controller.rb +13 -0
- data/app/controllers/shiplog/admin/entries_controller.rb +55 -0
- data/app/controllers/shiplog/admin/syncs_controller.rb +21 -0
- data/app/controllers/shiplog/application_controller.rb +5 -0
- data/app/controllers/shiplog/changelogs_controller.rb +24 -0
- data/app/controllers/shiplog/frontends_controller.rb +21 -0
- data/app/controllers/shiplog/public_controller.rb +19 -0
- data/app/controllers/shiplog/roadmaps_controller.rb +14 -0
- data/app/controllers/shiplog/webhooks_controller.rb +121 -0
- data/app/frontend/shiplog/admin.css +137 -0
- data/app/frontend/shiplog/board.js +77 -0
- data/app/frontend/shiplog/shiplog.css +184 -0
- data/app/helpers/shiplog/admin/entries_helper.rb +27 -0
- data/app/helpers/shiplog/application_helper.rb +14 -0
- data/app/helpers/shiplog/frontend_helper.rb +17 -0
- data/app/helpers/shiplog/host_routes_helper.rb +25 -0
- data/app/helpers/shiplog/styling_helper.rb +24 -0
- data/app/jobs/shiplog/application_job.rb +14 -0
- data/app/jobs/shiplog/refresh_job.rb +38 -0
- data/app/jobs/shiplog/rewrite_entry_job.rb +9 -0
- data/app/jobs/shiplog/scan_changes_job.rb +36 -0
- data/app/jobs/shiplog/sync_job.rb +20 -0
- data/app/models/shiplog/application_record.rb +5 -0
- data/app/models/shiplog/board_arrangement.rb +38 -0
- data/app/models/shiplog/change_candidates.rb +77 -0
- data/app/models/shiplog/commit.rb +9 -0
- data/app/models/shiplog/drafter.rb +69 -0
- data/app/models/shiplog/entry.rb +42 -0
- data/app/models/shiplog/issue.rb +68 -0
- data/app/models/shiplog/issue_form.rb +43 -0
- data/app/models/shiplog/issue_link.rb +6 -0
- data/app/models/shiplog/pull_request.rb +11 -0
- data/app/models/shiplog/repository.rb +9 -0
- data/app/models/shiplog/rewriter/prompt.rb +71 -0
- data/app/models/shiplog/rewriter.rb +89 -0
- data/app/models/shiplog/roadmap/column_resolver.rb +46 -0
- data/app/models/shiplog/roadmap.rb +34 -0
- data/app/models/shiplog/sync/commit_scanner.rb +39 -0
- data/app/models/shiplog/sync/issue_importer.rb +50 -0
- data/app/models/shiplog/sync/node_attributes.rb +50 -0
- data/app/models/shiplog/sync/pull_request_importer.rb +54 -0
- data/app/models/shiplog/sync/repository_sync.rb +80 -0
- data/app/models/shiplog/webhook_delivery.rb +5 -0
- data/app/models/shiplog/webhook_signature.rb +16 -0
- data/app/views/layouts/shiplog/admin.html.erb +16 -0
- data/app/views/layouts/shiplog/application.html.erb +15 -0
- data/app/views/shiplog/_empty.html.erb +3 -0
- data/app/views/shiplog/_page.html.erb +6 -0
- data/app/views/shiplog/_page_header.html.erb +6 -0
- data/app/views/shiplog/_pagination.html.erb +12 -0
- data/app/views/shiplog/admin/_assets.html.erb +3 -0
- data/app/views/shiplog/admin/_custom_head.html.erb +0 -0
- data/app/views/shiplog/admin/_nav.html.erb +9 -0
- data/app/views/shiplog/admin/_page.html.erb +14 -0
- data/app/views/shiplog/admin/boards/_card.html.erb +22 -0
- data/app/views/shiplog/admin/boards/show.html.erb +17 -0
- data/app/views/shiplog/admin/entries/_badges.html.erb +10 -0
- data/app/views/shiplog/admin/entries/_entry_row.html.erb +21 -0
- data/app/views/shiplog/admin/entries/edit.html.erb +61 -0
- data/app/views/shiplog/admin/entries/index.html.erb +47 -0
- data/app/views/shiplog/admin/syncs/show.html.erb +71 -0
- data/app/views/shiplog/changelogs/_entry.html.erb +8 -0
- data/app/views/shiplog/changelogs/_month.html.erb +9 -0
- data/app/views/shiplog/changelogs/show.html.erb +16 -0
- data/app/views/shiplog/roadmaps/_card.html.erb +6 -0
- data/app/views/shiplog/roadmaps/_column.html.erb +17 -0
- data/app/views/shiplog/roadmaps/show.html.erb +9 -0
- data/config/admin_routes.rb +13 -0
- data/config/locales/en.yml +106 -0
- data/config/routes.rb +6 -0
- data/db/migrate/20261026000001_create_shiplog_tables.rb +102 -0
- data/lib/generators/shiplog/install/install_generator.rb +94 -0
- data/lib/generators/shiplog/install/templates/initializer.rb.tt +86 -0
- data/lib/generators/shiplog/install/templates/shiplog-feature.yml +23 -0
- data/lib/shiplog/admin/engine.rb +9 -0
- data/lib/shiplog/classes.rb +38 -0
- data/lib/shiplog/configuration.rb +92 -0
- data/lib/shiplog/engine.rb +5 -0
- data/lib/shiplog/github/client.rb +96 -0
- data/lib/shiplog/github/errors.rb +17 -0
- data/lib/shiplog/github/queries.rb +108 -0
- data/lib/shiplog/version.rb +3 -0
- data/lib/shiplog.rb +16 -0
- metadata +154 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f0f17beb628a8bf038407c3a1b0e5caa404db7f51eba9472dbf507df7da84431
|
|
4
|
+
data.tar.gz: da38c5e1bc625bcae2ed31ef037db2440bc94a612760c11cc842cb91b02cdf28
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 588b5d5e87c63b828d0a75ef4aa01591efec6c94a954622d3ad2e36a6cf88deeb46ae72b19b0f865828606f147dfcdb2b6d30f34ce75741acc23543f9a853d15
|
|
7
|
+
data.tar.gz: d6a7f52d6df8768e5586a47bbded5e5b867dfaac2ec07e85fd0ac86b37b0a8e4a275db62f7893a1ea8b0f607027924cc50bb4925ff2efb41c9b10e4158bd1a95
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-10-26)
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- Public roadmap and changelog engine (`Shiplog::Engine`) and a separate admin engine (`Shiplog::Admin::Engine`) with a drafts inbox, entry editing, a roadmap board with drag and drop, and sync status.
|
|
8
|
+
- GitHub GraphQL sync across several repositories, a signed webhook endpoint (`issues`, `pull_request`, `sub_issues`), and scheduled jobs (`Shiplog::SyncJob`, `Shiplog::ScanChangesJob`).
|
|
9
|
+
- Roadmap columns derived from GitHub state (open PR or assignee, milestone or `soon` label), with admins able to pin a card to a column.
|
|
10
|
+
- Changelog drafts for shipped features (from the issue form's Problem / Before / After) and for small merged PRs and commits, skipping bots, internal conventional-commit types and excluded labels.
|
|
11
|
+
- Host-supplied LLM rewriter. Nothing is public until approved, and approved text is never silently replaced.
|
|
12
|
+
- Styling: `config.classes` (per-element classes next to stable `shiplog-*` BEM classes), `config.stylesheet`, and a bundled stylesheet in `@layer shiplog` with `--shiplog-*` variables. Public views are split into overridable partials.
|
|
13
|
+
- Rendering inside host layouts: `public_parent_controller` / `public_layout`, `admin_parent_controller` / `admin_layout` / `admin_chrome` / `admin_flash`, and unknown `*_path` / `*_url` helpers forwarded to `main_app`.
|
|
14
|
+
- Admin access fails closed (`config.authorize_admin` defaults to deny).
|
|
15
|
+
- `rails generate shiplog:install` (with `--tailwind`) and a `shiplog-feature.yml` issue form.
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2026 Trackberry
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# Shiplog
|
|
2
|
+
|
|
3
|
+
A mountable Rails engine that turns your GitHub issues, pull requests and commits into a public roadmap and changelog written for customers, not developers. An LLM that you supply drafts the plain-language text. Nothing is public until an admin approves it.
|
|
4
|
+
|
|
5
|
+
- **Roadmap**: issues labelled `public` appear in three columns (In progress, Coming soon, Later) once approved.
|
|
6
|
+
- **Changelog**: shipped features and small changes, grouped by month.
|
|
7
|
+
- **Admin**: drafts inbox, entry editing, a drag-and-drop roadmap board and a sync status page.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Ruby 4.0 or newer
|
|
12
|
+
- Rails 8.1.4 or newer (< 9)
|
|
13
|
+
- PostgreSQL (tables use `jsonb`)
|
|
14
|
+
- A job backend for Active Job (GoodJob, Solid Queue, ...)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
# Gemfile
|
|
20
|
+
gem "shiplog"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bundle install
|
|
25
|
+
bin/rails generate shiplog:install
|
|
26
|
+
bin/rails db:migrate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The generator:
|
|
30
|
+
|
|
31
|
+
- creates `config/initializers/shiplog.rb` with every setting, commented;
|
|
32
|
+
- adds the public mount (and a commented admin mount) to `config/routes.rb`;
|
|
33
|
+
- runs `shiplog:install:migrations` (all tables are prefixed `shiplog_`);
|
|
34
|
+
- copies the issue form to `.github/ISSUE_TEMPLATE/shiplog-feature.yml`. It has Problem / Before / After fields and applies the `public` label.
|
|
35
|
+
|
|
36
|
+
With `--tailwind` the initializer also sets `config.stylesheet = false` and a `config.classes` hash of Tailwind classes, and the generator adds the `@source` line to `app/assets/tailwind/application.css`. See [Styling](#styling).
|
|
37
|
+
|
|
38
|
+
## Mounting
|
|
39
|
+
|
|
40
|
+
Shiplog ships two engines so that the admin can sit behind your own authentication while the public pages and the GitHub webhook stay open (the same approach as GoodJob):
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
# config/routes.rb
|
|
44
|
+
mount Shiplog::Engine => "/" # /roadmap, /changelog, /shiplog/github/webhook, engine assets
|
|
45
|
+
|
|
46
|
+
authenticate :user, ->(user) { user.admin? } do # Devise shown; use whatever fits your app
|
|
47
|
+
mount Shiplog::Admin::Engine => "/admin/shiplog"
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The generator writes the admin mount as a comment because authentication differs between apps. Uncomment it once it is wrapped in a constraint.
|
|
52
|
+
|
|
53
|
+
`config.authorize_admin` is checked by the admin controllers and a falsy result returns 403. It is fail-closed: the default denies everyone, so a mount that is not configured is never open. The generated initializer sets `config.authorize_admin = ->(controller) { true }` because the route constraint around the mount does the authentication. If you do not wrap the mount in a constraint, replace it with a real check (for example `controller.current_user&.admin?`).
|
|
54
|
+
|
|
55
|
+
The public paths come from `config.roadmap_path` and `config.changelog_path`, read when routes are drawn.
|
|
56
|
+
|
|
57
|
+
### Customising the admin layout
|
|
58
|
+
|
|
59
|
+
By default the admin uses its own layout (`layouts/shiplog/admin`) and its own CSS and JS, served by the engine (and aware of your CSP nonce). To change it, create the same file in your app, which takes precedence over the engine's:
|
|
60
|
+
|
|
61
|
+
```erb
|
|
62
|
+
<%# app/views/layouts/shiplog/admin.html.erb %>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To render the admin inside your own admin layout instead, see [Rendering inside your layouts](#rendering-inside-your-layouts).
|
|
66
|
+
|
|
67
|
+
For smaller tweaks override a partial instead:
|
|
68
|
+
|
|
69
|
+
- `app/views/shiplog/admin/_custom_head.html.erb`: rendered inside `<head>` of the engine layout (extra stylesheets, favicon, ...).
|
|
70
|
+
- `app/views/shiplog/admin/_nav.html.erb`: the navigation bar (a compact sub-nav when a host layout is used).
|
|
71
|
+
- `app/views/shiplog/admin/_assets.html.erb`: links `admin.css`; every admin page renders it, so it works in any layout.
|
|
72
|
+
|
|
73
|
+
Engine views use the engine's route helpers, so inside a host layout or partial rendered by a Shiplog controller your own routes are available as `main_app.` helpers, or directly (see [host route helpers](#host-route-helpers)).
|
|
74
|
+
|
|
75
|
+
### Public pages
|
|
76
|
+
|
|
77
|
+
The public controllers inherit from `config.public_parent_controller` and render with `config.public_layout`.
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
All settings live in `config/initializers/shiplog.rb`:
|
|
82
|
+
|
|
83
|
+
| Key | Default | Description |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `repositories` | `[]` | GitHub repositories to sync, as `"owner/name"`. Sub-issues may cross repositories of the same owner. |
|
|
86
|
+
| `github_token` | `nil` | A String, or a callable evaluated on every GitHub request. |
|
|
87
|
+
| `webhook_secret` | `nil` | Secret of the GitHub webhook. While blank, the webhook endpoint answers 503. |
|
|
88
|
+
| `public_label` | `"public"` | Only issues with this label reach the roadmap. |
|
|
89
|
+
| `soon_labels` | `["soon"]` | Labels that put an issue in "Coming soon". A milestone does the same. |
|
|
90
|
+
| `excluded_labels` | `["internal", "dependencies", "refactor", "chore"]` | Pull requests with one of these labels never become changelog entries. |
|
|
91
|
+
| `excluded_authors` | `[/\[bot\]\z/]` | Patterns (matched with `===`) for authors whose pull requests and commits are skipped. |
|
|
92
|
+
| `commit_types` | `["feat", "fix", "perf"]` | Conventional-commit types eligible for the changelog. Commits need one of these types; pull requests whose title has a different type (`ci:`, `chore(deps):`, ...) are skipped, untyped titles are kept. |
|
|
93
|
+
| `rewriter` | `nil` | Callable that rewrites text with an LLM. See [The rewriter](#the-rewriter). |
|
|
94
|
+
| `product_name` | `nil` | Your product's name, used in the prompts. |
|
|
95
|
+
| `audience` | `nil` | Who your customers are, used in the prompts. |
|
|
96
|
+
| `tone` | `"Friendly, concise, no jargon. Address the customer as 'you'."` | Writing style used in the prompts. |
|
|
97
|
+
| `prompt_builder` | `nil` | Optional `->(kind:, context:) { String }` replacing the built-in prompts. `kind` is `"roadmap"`, `"feature"` or `"change"`. |
|
|
98
|
+
| `public_parent_controller` | `"ActionController::Base"` | Parent class of the public controllers. Read once when they load, so set it in the initializer; assigning it after the controllers loaded raises `ArgumentError`. |
|
|
99
|
+
| `public_layout` | `"shiplog/application"` | Layout of the public pages. |
|
|
100
|
+
| `admin_parent_controller` | `"ActionController::Base"` | Parent class of the admin controllers. Read once when they load, so set it in the initializer; assigning it after the controllers loaded raises `ArgumentError`. |
|
|
101
|
+
| `admin_layout` | `"shiplog/admin"` | Layout of the admin pages. |
|
|
102
|
+
| `admin_chrome` | `:standalone` | `:standalone` wraps the admin in Shiplog's own page chrome (nav bar, flash, background). `:hosted` is for an `admin_layout` of your own: Shiplog renders only a compact sub-nav inside your content. Anything else raises `ArgumentError`. The layout name is never inspected. |
|
|
103
|
+
| `admin_flash` | `true` | In `:hosted` mode, render Shiplog's flash messages inside the admin content. Set to `false` if your layout already shows the flash. Standalone always shows it. |
|
|
104
|
+
| `stylesheet` | `true` | Include the bundled `shiplog.css` in the public pages. Set to `false` when you style them yourself. |
|
|
105
|
+
| `classes` | `{}` | Extra classes per element, added to the stable `shiplog-*` classes. See [Styling](#styling). |
|
|
106
|
+
| `authorize_admin` | `->(controller) { false }` | Admin check; return false for a 403. Denies everyone until set; the generated initializer opens it, relying on your route constraint. |
|
|
107
|
+
| `current_admin` | `->(controller) { controller.try(:current_user)&.try(:email) }` | Stored as the approver of an entry. |
|
|
108
|
+
| `roadmap_path` | `"roadmap"` | Path of the public roadmap. |
|
|
109
|
+
| `changelog_path` | `"changelog"` | Path of the public changelog. |
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
Shiplog.configure do |config|
|
|
115
|
+
config.repositories = %w[acme/app acme/app-ios]
|
|
116
|
+
config.github_token = -> { Rails.application.credentials.dig(:github, :shiplog_token) }
|
|
117
|
+
config.webhook_secret = Rails.application.credentials.dig(:github, :shiplog_webhook_secret)
|
|
118
|
+
config.product_name = "Acme"
|
|
119
|
+
config.audience = "small business owners who invoice customers"
|
|
120
|
+
config.rewriter = ->(prompt) { RubyLLM.chat.ask(prompt).content }
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## GitHub token
|
|
125
|
+
|
|
126
|
+
Shiplog talks to the GitHub GraphQL API. Provide either:
|
|
127
|
+
|
|
128
|
+
- a **fine-grained personal access token** with access to the configured repositories and read-only permissions for **Issues**, **Pull requests**, **Contents** and **Metadata**; or
|
|
129
|
+
- a **GitHub App installation token** with the same permissions. Because `github_token` accepts a callable, you can mint a fresh token on each call:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
config.github_token = -> { MyApp::GithubApp.installation_token }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Webhook setup
|
|
136
|
+
|
|
137
|
+
Webhooks keep Shiplog fresh between scheduled syncs. In each repository (or at organisation level) add a webhook:
|
|
138
|
+
|
|
139
|
+
- **Payload URL**: `https://your-app.example.com/shiplog/github/webhook` (prefix it if you mounted `Shiplog::Engine` below `/`)
|
|
140
|
+
- **Content type**: `application/json`
|
|
141
|
+
- **Secret**: the value of `config.webhook_secret`
|
|
142
|
+
- **Events**: *Issues*, *Pull requests* and *Sub-issues*
|
|
143
|
+
|
|
144
|
+
Requests are verified with `X-Hub-Signature-256`; an invalid signature returns 401. Each `X-GitHub-Delivery` id is recorded, so redeliveries are harmless. Valid events enqueue a `Shiplog::RefreshJob` that refetches the current state of the issue or pull request and respond 202. `ping` answers 200 and other events are ignored. Recent deliveries are listed on the admin Sync page.
|
|
145
|
+
|
|
146
|
+
## Scheduling
|
|
147
|
+
|
|
148
|
+
Webhooks can be missed, so schedule two jobs:
|
|
149
|
+
|
|
150
|
+
- `Shiplog::SyncJob`: reconciles issues and pull requests and creates missing drafts. Every 30 minutes is a good start. The first run backfills everything.
|
|
151
|
+
- `Shiplog::ScanChangesJob`: looks at default-branch commits without a pull request, then drafts small changes. Run it weekly.
|
|
152
|
+
|
|
153
|
+
GoodJob (`config/initializers/good_job.rb` or `config/application.rb`):
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
config.good_job.enable_cron = true
|
|
157
|
+
config.good_job.cron = {
|
|
158
|
+
shiplog_sync: { cron: "*/30 * * * *", class: "Shiplog::SyncJob" },
|
|
159
|
+
shiplog_scan_changes: { cron: "0 6 * * 1", class: "Shiplog::ScanChangesJob" }
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Solid Queue (`config/recurring.yml`):
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
production:
|
|
167
|
+
shiplog_sync:
|
|
168
|
+
class: Shiplog::SyncJob
|
|
169
|
+
schedule: every 30 minutes
|
|
170
|
+
shiplog_scan_changes:
|
|
171
|
+
class: Shiplog::ScanChangesJob
|
|
172
|
+
schedule: every monday at 6am
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
You can also trigger a sync from the admin Sync page. Rewriting always runs in `Shiplog::RewriteEntryJob`, never in a request.
|
|
176
|
+
|
|
177
|
+
## The rewriter
|
|
178
|
+
|
|
179
|
+
`config.rewriter` is a callable that receives the prompt (a String) and returns a String containing a JSON object:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{"title": "...", "body": "...", "customer_facing": true}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Shiplog extracts the first valid JSON object from the reply, so models that wrap it in prose or code fences still work. `customer_facing: false` means the model thinks the change is irrelevant for customers; such small changes are stored as discarded and can be restored by an admin. If no rewriter is configured, the reply can't be parsed or the call raises, the entry keeps the original GitHub text and the error is shown in the admin, where you can edit the text or regenerate.
|
|
186
|
+
|
|
187
|
+
With [RubyLLM](https://rubyllm.com):
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
config.rewriter = ->(prompt) { RubyLLM.chat.ask(prompt).content }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
With the Anthropic SDK:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
client = Anthropic::Client.new
|
|
197
|
+
config.rewriter = lambda do |prompt|
|
|
198
|
+
message = client.messages.create(model: "claude-sonnet-5-5", max_tokens: 1024, messages: [ { role: "user", content: prompt } ])
|
|
199
|
+
message.content.first.text
|
|
200
|
+
end
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## How items reach the roadmap
|
|
204
|
+
|
|
205
|
+
- An issue is *public* when it has `config.public_label`.
|
|
206
|
+
- A public, open issue whose parent is not itself public is a roadmap item. Public children of a public parent are folded into the parent.
|
|
207
|
+
- A roadmap item is drafted, rewritten and visible on `/roadmap` only after an admin approves it. It leaves the roadmap when it is closed.
|
|
208
|
+
- The column is the first match of:
|
|
209
|
+
1. the column an admin pinned it to;
|
|
210
|
+
2. **In progress**: the issue or one of its sub-issues has an open linked pull request, or the issue has assignees;
|
|
211
|
+
3. **Coming soon**: it has a milestone or a label from `soon_labels`;
|
|
212
|
+
4. **Later**.
|
|
213
|
+
- Within a column cards are ordered by their board position, then by last GitHub update. Dragging a card on the admin board pins it to the target column and stores the order. "Unpin" returns it to the automatic column. Without JavaScript, each card has a column select and a Move button.
|
|
214
|
+
|
|
215
|
+
## How changelog entries are produced
|
|
216
|
+
|
|
217
|
+
- **Features**: a public top-level issue closed as completed that has at least one merged linked pull request on itself or a sub-issue. The issue form sections (`### Problem`, `### Before`, `### After`), sub-issue titles and linked pull request titles are given to the rewriter. Only issues closed in the last 90 days are drafted, so a first sync doesn't flood the inbox. The release date is the newest merge date.
|
|
218
|
+
- **Small changes**: the weekly scan looks at pull requests merged, and stored commits made, since the last scan (seven days on the first run). Commits are only considered when they are on the default branch and have no pull request, and their conventional-commit type (`feat:`, `fix(scope):`, ...) must be in `commit_types`. Pull requests are skipped when their title carries a type outside `commit_types`; untyped titles are left for the rewriter to judge. Bot authors are recorded as `name[bot]`, so the default `excluded_authors` pattern matches them.
|
|
219
|
+
- **Exclusions**: pull requests with an excluded label or an excluded author, commits from excluded authors, pull requests that belong to a feature's issue tree (the feature entry covers them), and anything that already has an entry.
|
|
220
|
+
|
|
221
|
+
## Approval workflow
|
|
222
|
+
|
|
223
|
+
Every entry starts as a **draft**. In the admin inbox you can edit the text, regenerate it, approve it or discard it. Only approved entries are public. Discarded entries are never proposed again but can be restored. If the GitHub text changes after a draft was written, the entry is flagged "GitHub text changed since this draft" and can be regenerated, and approved text is never replaced silently.
|
|
224
|
+
|
|
225
|
+
Approved changelog entries do not react to later GitHub changes (a reverted pull request, a removed label): discard them by hand in the admin. A roadmap issue that is re-labelled or reopened shows its previously approved text again, and the inbox flags it as "GitHub text changed". Issues deleted or transferred on GitHub are closed locally (state reason `deleted`), which removes them from the roadmap; their entries are kept.
|
|
226
|
+
|
|
227
|
+
Drafts whose rewrite job was lost (no result and no error after an hour) are queued again by the next sync, and the admin marks them "Not rewritten yet" until then.
|
|
228
|
+
|
|
229
|
+
## Styling
|
|
230
|
+
|
|
231
|
+
The public pages ship plain semantic HTML with stable [BEM](https://getbem.com) classes (`shiplog-card`, `shiplog-entry__title`, ...) and a small, framework-free stylesheet. Pick the level that fits your app; the same views work in all of them.
|
|
232
|
+
|
|
233
|
+
### 1. CSS variables
|
|
234
|
+
|
|
235
|
+
The bundled stylesheet is driven by variables you can set on `:root`, `body` or `.shiplog-page`. Inside your own layout it inherits your text colour and draws cards and borders as translucent mixes of that colour (`color-mix` against `currentColor`), so it works on light and dark hosts without any setting. Only Shiplog's own layout (`.shiplog-layout`), which paints the page background, switches to dark colours with `prefers-color-scheme`.
|
|
236
|
+
|
|
237
|
+
| Variable | Meaning |
|
|
238
|
+
|---|---|
|
|
239
|
+
| `--shiplog-fg` | Text colour |
|
|
240
|
+
| `--shiplog-muted` | Secondary text |
|
|
241
|
+
| `--shiplog-bg` | Page background (only painted by the engine's own layout) |
|
|
242
|
+
| `--shiplog-card` | Card and entry background |
|
|
243
|
+
| `--shiplog-border` | Borders and dividers |
|
|
244
|
+
| `--shiplog-accent` | Links, feature marker, pill, hover |
|
|
245
|
+
| `--shiplog-radius` | Corner radius (default `0.5rem`) |
|
|
246
|
+
| `--shiplog-font` | Font family |
|
|
247
|
+
| `--shiplog-space` | Base spacing unit (default `1rem`) |
|
|
248
|
+
|
|
249
|
+
```css
|
|
250
|
+
:root { --shiplog-accent: #7c3aed; --shiplog-radius: 0.75rem; }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 2. Override the BEM classes
|
|
254
|
+
|
|
255
|
+
All rules live in `@layer shiplog`, so any unlayered CSS in your app wins without extra specificity:
|
|
256
|
+
|
|
257
|
+
```css
|
|
258
|
+
.shiplog-card { border-radius: 0; }
|
|
259
|
+
.shiplog-entry--feature { border-left-width: 8px; }
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
If your own CSS is layered too, declare the order up front, for example `@layer shiplog, app;`.
|
|
263
|
+
|
|
264
|
+
### 3. `config.classes` (Tailwind and other utility frameworks)
|
|
265
|
+
|
|
266
|
+
`config.classes` maps a key to the classes added to that element, next to its BEM class. Set `config.stylesheet = false` to drop the bundled CSS:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
Shiplog.configure do |config|
|
|
270
|
+
config.stylesheet = false
|
|
271
|
+
config.classes = {
|
|
272
|
+
columns: "grid gap-6 md:grid-cols-3",
|
|
273
|
+
card: "rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-800 dark:bg-gray-900"
|
|
274
|
+
}
|
|
275
|
+
end
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
With Tailwind v4, the class names live in a Ruby file, so tell Tailwind to scan it. In `app/assets/tailwind/application.css`:
|
|
279
|
+
|
|
280
|
+
```css
|
|
281
|
+
@import "tailwindcss";
|
|
282
|
+
@source "../../../config/initializers/shiplog.rb";
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
(The path is relative to the stylesheet: `app/assets/tailwind/` up three levels is the app root.) `bin/rails generate shiplog:install --tailwind` writes both for you, with neutral gray/indigo classes covering every key.
|
|
286
|
+
|
|
287
|
+
If you keep the bundled stylesheet (`stylesheet: true`) next to Tailwind v4 utilities, declare the layer order before the import so utilities win over Shiplog's rules:
|
|
288
|
+
|
|
289
|
+
```css
|
|
290
|
+
@layer theme, base, shiplog, components, utilities;
|
|
291
|
+
@import "tailwindcss";
|
|
292
|
+
@source "../../../config/initializers/shiplog.rb";
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Without it, `shiplog` is first seen when `shiplog.css` loads, which can put it after `utilities` in the cascade order (a layer's position is fixed by its first mention), and then `.shiplog-card { border-radius: ... }` would beat a `rounded-lg` utility. Declaring the order first pins `shiplog` below `components` and `utilities`.
|
|
296
|
+
|
|
297
|
+
| Key | BEM class | Element |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| `page` | `shiplog-page` (+ `--roadmap` / `--changelog`) | Page wrapper |
|
|
300
|
+
| `page_header` | `shiplog-page__header` | Header with title and lede |
|
|
301
|
+
| `page_title` | `shiplog-page__title` | `h1` |
|
|
302
|
+
| `page_lede` | `shiplog-page__lede` | Intro paragraph |
|
|
303
|
+
| `columns` | `shiplog-columns` | Roadmap column grid |
|
|
304
|
+
| `column` | `shiplog-column` (+ `--in-progress` / `--coming-soon` / `--later`) | One roadmap column |
|
|
305
|
+
| `column_header` | `shiplog-column__header` | Column heading block |
|
|
306
|
+
| `column_title` | `shiplog-column__title` | Column `h2` |
|
|
307
|
+
| `column_count` | `shiplog-column__count` | Number of items |
|
|
308
|
+
| `column_description` | `shiplog-column__description` | One-line description |
|
|
309
|
+
| `column_empty` | `shiplog-column__empty` | Empty column notice |
|
|
310
|
+
| `cards` | `shiplog-cards` | List of roadmap cards |
|
|
311
|
+
| `card` | `shiplog-card` | Roadmap card |
|
|
312
|
+
| `card_title` | `shiplog-card__title` | Card `h3` |
|
|
313
|
+
| `card_body` | `shiplog-card__body` | Card text |
|
|
314
|
+
| `months` | `shiplog-months` | Changelog months wrapper |
|
|
315
|
+
| `month` | `shiplog-month` | One month section |
|
|
316
|
+
| `month_title` | `shiplog-month__title` | Month `h2` |
|
|
317
|
+
| `entries` | `shiplog-entries` | List of entries |
|
|
318
|
+
| `entry` | `shiplog-entry` (+ `--feature` / `--change`) | Changelog entry |
|
|
319
|
+
| `entry_feature` | `shiplog-entry--feature` | Also applied to feature entries |
|
|
320
|
+
| `entry_category` | `shiplog-entry__category` | "New" / "Improved" pill |
|
|
321
|
+
| `entry_title` | `shiplog-entry__title` | Entry `h3` |
|
|
322
|
+
| `entry_body` | `shiplog-entry__body` | Entry text |
|
|
323
|
+
| `entry_date` | `shiplog-entry__date` | `time` element |
|
|
324
|
+
| `pagination` | `shiplog-pagination` | Pagination `nav` |
|
|
325
|
+
| `pagination_link` | `shiplog-pagination__link` (+ `--newer` / `--older`) | Newer/older links |
|
|
326
|
+
| `empty` | `shiplog-empty` | "Nothing here yet" notice |
|
|
327
|
+
|
|
328
|
+
Values may be strings or arrays. The list is `Shiplog::Classes::KEYS`; assigning `config.classes` raises `ArgumentError` (listing the valid keys) for any other key, in every environment. String keys are accepted and normalised to symbols, and the hash is stored frozen. Texts come from I18n (`shiplog.roadmap.title`, `shiplog.roadmap.lede`, `shiplog.changelog.*`, `shiplog.categories.feature` / `change`, `shiplog.date_format` (default `%-d %B %Y`, used for entry dates), `shiplog.columns.<column>_description`), so override them in your locale files.
|
|
329
|
+
|
|
330
|
+
### 4. Override the partials
|
|
331
|
+
|
|
332
|
+
Copy any view from the engine's `app/views/shiplog/` into your app with the same path and edit it. Each partial documents its locals in a comment on its first lines:
|
|
333
|
+
|
|
334
|
+
- `shiplog/_page.html.erb`, `shiplog/_page_header.html.erb`, `shiplog/_pagination.html.erb`, `shiplog/_empty.html.erb`
|
|
335
|
+
- `shiplog/roadmaps/_column.html.erb`, `shiplog/roadmaps/_card.html.erb`
|
|
336
|
+
- `shiplog/changelogs/_month.html.erb`, `shiplog/changelogs/_entry.html.erb`
|
|
337
|
+
|
|
338
|
+
`shiplog/_page.html.erb` is what links the stylesheet (so it also works in layouts you don't control); keep that line if you copy it and still want the bundled CSS.
|
|
339
|
+
|
|
340
|
+
The admin has its own stylesheet, scoped under `.shiplog-admin` and also in `@layer shiplog`. Its variables (`--shiplog-admin-fg`, `-muted`, `-bg`, `-surface`, `-border`, `-accent`) can be set on `:root` or `.shiplog-admin`. The admin follows the same rule: dark colours only in the standalone chrome, inherited colours when `admin_chrome` is `:hosted`.
|
|
341
|
+
|
|
342
|
+
## Rendering inside your layouts
|
|
343
|
+
|
|
344
|
+
- **Public pages**: set `config.public_parent_controller` (default `ActionController::Base`; read once when the controller loads, so use the initializer) and `config.public_layout` (default `"shiplog/application"`). The public controllers run under the parent's `before_action`s, so pick one that lets visitors through.
|
|
345
|
+
- **Admin**: set `config.admin_parent_controller` (same rule), `config.admin_layout` and `config.admin_chrome = :hosted`. Each admin page links its own `admin.css` (and `board.js` on the board) and wraps itself in `<div class="shiplog-admin">`, so nothing depends on your layout's `<head>`. In `:hosted` mode Shiplog adds a small Drafts / Roadmap board / Sync sub-nav inside the content and shows its flash messages there too (set `config.admin_flash = false` if your layout renders the flash itself). The board reads the CSRF token from `meta[name=csrf-token]` and falls back to a `data-csrf-token` attribute, so your layout does not need `csrf_meta_tags` for drag and drop.
|
|
346
|
+
- `config.authorize_admin` and the route constraint still apply.
|
|
347
|
+
|
|
348
|
+
### Host route helpers
|
|
349
|
+
|
|
350
|
+
A layout rendered by a Shiplog controller runs in the engine's isolated routing context. Shiplog's controllers include `Shiplog::HostRoutesHelper`, which forwards any `*_path` / `*_url` helper the engine does not define to `main_app`, so `root_path`, `about_path` or `new_user_session_path` work in your layout as they do elsewhere. Helpers the engine defines win: in the public engine `roadmap_path` is Shiplog's, and in the admin engine `root_path` is the admin root, so use `main_app.root_path` there when you mean your own.
|
|
351
|
+
|
|
352
|
+
### Example: Trackberry-style setup
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
# config/routes.rb
|
|
356
|
+
mount Shiplog::Engine => "/" # /roadmap and /changelog next to the app's own pages
|
|
357
|
+
authenticate :user, ->(user) { user.admin? } do
|
|
358
|
+
mount Shiplog::Admin::Engine => "/admin/shiplog"
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# config/initializers/shiplog.rb
|
|
362
|
+
Shiplog.configure do |config|
|
|
363
|
+
config.public_parent_controller = "ApplicationController"
|
|
364
|
+
config.public_layout = "application"
|
|
365
|
+
config.admin_parent_controller = "Admin::BaseController"
|
|
366
|
+
config.admin_layout = "admin"
|
|
367
|
+
config.admin_chrome = :hosted
|
|
368
|
+
config.stylesheet = false
|
|
369
|
+
config.classes = { columns: "grid gap-6 md:grid-cols-3", card: "rounded-lg border p-3" } # see the generator for all keys
|
|
370
|
+
end
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The host layouts keep using their own helpers (`root_path`, `current_user`, ...) and Tailwind compiles the classes through the `@source` line.
|
|
374
|
+
|
|
375
|
+
## Hosting notes
|
|
376
|
+
|
|
377
|
+
- Set `public_parent_controller` and `admin_parent_controller` in an initializer (`config/initializers/shiplog.rb`). They are read once, when the engine's controller classes load, so changing them later has no effect.
|
|
378
|
+
- The public pages send an ETag covering the data, locale, gem version, `config.classes` and `config.stylesheet`. If your host layout shows per-user content, declare `etag { current_user&.id }` in the parent controller (`config.public_parent_controller`); Rails ETags are inherited, so cached responses are not shared between users.
|
|
379
|
+
|
|
380
|
+
## Running the tests
|
|
381
|
+
|
|
382
|
+
```sh
|
|
383
|
+
bin/rails db:create db:schema:load # once, creates shiplog_test on local PostgreSQL
|
|
384
|
+
bin/rails test
|
|
385
|
+
bin/rails test test/system # the roadmap board in headless Chrome
|
|
386
|
+
bin/rubocop
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
The system tests need Chrome; Selenium fetches a matching chromedriver. The database is configured in `test/dummy/config/database.yml`; set `PGHOST`, `PGUSER` and `PGPASSWORD` if your PostgreSQL needs them.
|
|
390
|
+
|
|
391
|
+
Tests that call the real GitHub API are skipped by default. To run them:
|
|
392
|
+
|
|
393
|
+
```sh
|
|
394
|
+
SHIPLOG_LIVE_GITHUB=1 GITHUB_TOKEN=... SHIPLOG_LIVE_REPO=owner/name bin/rails test test/live
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`SHIPLOG_LIVE_REPO` defaults to `Trackberry/shiplog`.
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
Released under the [MIT License](MIT-LICENSE).
|
|
402
|
+
|
|
403
|
+
## Releasing
|
|
404
|
+
|
|
405
|
+
Releases are published by `.github/workflows/release.yml` through RubyGems trusted publishing; there is no API key and no manual `gem push`.
|
|
406
|
+
|
|
407
|
+
1. Open a pull request that bumps `lib/shiplog/version.rb` and adds a `## x.y.z (date)` entry to `CHANGELOG.md`.
|
|
408
|
+
2. Merge it. The workflow reruns the tests, refuses a version that is already on RubyGems, publishes the gem, tags `vx.y.z` and creates the GitHub release with that changelog entry as its notes.
|
|
409
|
+
|
|
410
|
+
The workflow can also be started by hand (Actions → Release → Run workflow), for the first release or to retry one that failed for an unrelated reason.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Shiplog
|
|
2
|
+
module Admin
|
|
3
|
+
# Parent controller of the admin. The parent class is resolved when this file loads, so
|
|
4
|
+
# `config.admin_parent_controller` must be set in an initializer (config/initializers/shiplog.rb).
|
|
5
|
+
class ApplicationController < Shiplog.config.admin_parent_controller.constantize
|
|
6
|
+
protect_from_forgery with: :exception
|
|
7
|
+
|
|
8
|
+
helper Shiplog::FrontendHelper
|
|
9
|
+
helper Shiplog::HostRoutesHelper
|
|
10
|
+
helper Shiplog::ApplicationHelper
|
|
11
|
+
helper Shiplog::Admin::EntriesHelper
|
|
12
|
+
helper_method :current_admin_name, :shiplog_admin_hosted?
|
|
13
|
+
layout -> { Shiplog.config.admin_layout }
|
|
14
|
+
|
|
15
|
+
before_action :authorize_admin
|
|
16
|
+
|
|
17
|
+
rescue_from Shiplog::Entry::InvalidTransition do
|
|
18
|
+
redirect_to edit_entry_path(params[:entry_id]), alert: t("shiplog.admin.entries.invalid_transition")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def authorize_admin
|
|
24
|
+
head :forbidden unless Shiplog.config.authorize_admin.call(self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# True when the admin renders inside a host layout (config.admin_chrome = :hosted).
|
|
28
|
+
def shiplog_admin_hosted?
|
|
29
|
+
Shiplog.config.admin_chrome == :hosted
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def current_admin_name
|
|
33
|
+
Shiplog.config.current_admin.call(self)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Shiplog
|
|
2
|
+
module Admin
|
|
3
|
+
class BoardsController < ApplicationController
|
|
4
|
+
def show
|
|
5
|
+
@columns = Roadmap.new(published_only: false).columns
|
|
6
|
+
ActiveRecord::Associations::Preloader.new(records: @columns.values.flatten.map(&:issue), associations: :repository).call
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def update
|
|
10
|
+
if request.format.json?
|
|
11
|
+
update_json
|
|
12
|
+
else
|
|
13
|
+
update_form
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def update_json
|
|
20
|
+
ids = params[:issue_ids]
|
|
21
|
+
return render_json_error("issue_ids required") unless ids.is_a?(Array) && ids.any?
|
|
22
|
+
|
|
23
|
+
BoardArrangement.new(column: params[:column], moved_id: params[:moved_id], issue_ids: ids).save!
|
|
24
|
+
render json: { card: render_card(params[:moved_id].to_i) }
|
|
25
|
+
rescue ArgumentError, ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound => error
|
|
26
|
+
render_json_error(error.message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def update_form
|
|
30
|
+
issue = Issue.find(params[:issue_id])
|
|
31
|
+
if params[:unpin].present?
|
|
32
|
+
BoardArrangement.unpin!(issue)
|
|
33
|
+
else
|
|
34
|
+
move_to_end(issue, params[:column].to_s)
|
|
35
|
+
end
|
|
36
|
+
redirect_to board_path, notice: t("shiplog.admin.board.updated")
|
|
37
|
+
rescue ArgumentError, ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound => error
|
|
38
|
+
redirect_to board_path, alert: error.message
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def move_to_end(issue, column)
|
|
42
|
+
raise ArgumentError, "Unknown column: #{column.inspect}" unless Issue::COLUMNS.include?(column)
|
|
43
|
+
|
|
44
|
+
ids = Roadmap.new(published_only: false).columns.fetch(column).map { |item| item.issue.id } - [ issue.id ]
|
|
45
|
+
BoardArrangement.new(column:, moved_id: issue.id, issue_ids: ids + [ issue.id ]).save!
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The same partial as a full page load, so the badges, select and Unpin
|
|
49
|
+
# button the script swaps in can't drift from what a reload would show.
|
|
50
|
+
def render_card(issue_id)
|
|
51
|
+
item = Roadmap.new(published_only: false).items.find { |candidate| candidate.issue.id == issue_id }
|
|
52
|
+
render_to_string(partial: "shiplog/admin/boards/card", formats: :html, locals: { item: })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def render_json_error(message)
|
|
56
|
+
render json: { error: message }, status: :unprocessable_content
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Shiplog
|
|
2
|
+
module Admin
|
|
3
|
+
module Entries
|
|
4
|
+
class ApprovalsController < ApplicationController
|
|
5
|
+
def create
|
|
6
|
+
entry = Entry.find(params[:entry_id])
|
|
7
|
+
entry.approve!(by: current_admin_name)
|
|
8
|
+
redirect_to root_path, notice: t("shiplog.admin.entries.approved")
|
|
9
|
+
rescue ActiveRecord::RecordInvalid => error
|
|
10
|
+
redirect_to edit_entry_path(entry), alert: error.record.errors.full_messages.to_sentence
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Shiplog
|
|
2
|
+
module Admin
|
|
3
|
+
module Entries
|
|
4
|
+
class DiscardsController < ApplicationController
|
|
5
|
+
before_action :set_entry
|
|
6
|
+
|
|
7
|
+
def create
|
|
8
|
+
@entry.discard!(reason: params[:reason].to_s.strip.presence)
|
|
9
|
+
redirect_to root_path, notice: t("shiplog.admin.entries.discarded")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def destroy
|
|
13
|
+
@entry.restore!
|
|
14
|
+
redirect_to root_path, notice: t("shiplog.admin.entries.restored")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def set_entry
|
|
20
|
+
@entry = Entry.find(params[:entry_id])
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Shiplog
|
|
2
|
+
module Admin
|
|
3
|
+
module Entries
|
|
4
|
+
class RewritesController < ApplicationController
|
|
5
|
+
def create
|
|
6
|
+
entry = Entry.find(params[:entry_id])
|
|
7
|
+
RewriteEntryJob.perform_later(entry.id, force: true)
|
|
8
|
+
redirect_to edit_entry_path(entry), notice: t("shiplog.admin.entries.rewrite_queued")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|