plan_my_stuff 0.6.0 → 0.8.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 +4 -4
- data/CHANGELOG.md +41 -1
- data/README.md +100 -103
- data/app/controllers/plan_my_stuff/application_controller.rb +22 -3
- data/app/controllers/plan_my_stuff/comments_controller.rb +14 -16
- data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +23 -13
- data/app/controllers/plan_my_stuff/issues/closures_controller.rb +7 -5
- data/app/controllers/plan_my_stuff/issues/links_controller.rb +14 -18
- data/app/controllers/plan_my_stuff/issues/takes_controller.rb +99 -28
- data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +13 -5
- data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +7 -5
- data/app/controllers/plan_my_stuff/issues_controller.rb +24 -28
- data/app/controllers/plan_my_stuff/labels_controller.rb +21 -5
- data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +13 -6
- data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +5 -4
- data/app/controllers/plan_my_stuff/project_items_controller.rb +30 -5
- data/app/controllers/plan_my_stuff/projects_controller.rb +16 -16
- data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +21 -11
- data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +9 -4
- data/app/controllers/plan_my_stuff/testing_projects_controller.rb +30 -14
- data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +50 -17
- data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +32 -49
- data/app/jobs/plan_my_stuff/application_job.rb +2 -3
- data/app/jobs/plan_my_stuff/reminders_sweep_job.rb +15 -22
- data/app/views/plan_my_stuff/comments/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/comments/partials/_form.html.erb +1 -0
- data/app/views/plan_my_stuff/issues/edit.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/index.html.erb +2 -2
- data/app/views/plan_my_stuff/issues/new.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +23 -2
- data/app/views/plan_my_stuff/issues/partials/_form.html.erb +1 -0
- data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +2 -1
- data/app/views/plan_my_stuff/issues/partials/_links.html.erb +50 -7
- data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +2 -1
- data/app/views/plan_my_stuff/issues/show.html.erb +5 -2
- data/app/views/plan_my_stuff/partials/_flash.html.erb +4 -0
- data/app/views/plan_my_stuff/projects/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/projects/index.html.erb +1 -1
- data/app/views/plan_my_stuff/projects/new.html.erb +1 -3
- data/app/views/plan_my_stuff/projects/partials/_form.html.erb +1 -0
- data/app/views/plan_my_stuff/projects/show.html.erb +13 -3
- data/app/views/plan_my_stuff/testing_project_items/new.html.erb +1 -3
- data/app/views/plan_my_stuff/testing_project_items/results/new.html.erb +1 -3
- data/app/views/plan_my_stuff/testing_projects/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/testing_projects/new.html.erb +1 -3
- data/app/views/plan_my_stuff/testing_projects/partials/_form.html.erb +4 -3
- data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +1 -0
- data/app/views/plan_my_stuff/testing_projects/partials/items/_form.html.erb +1 -0
- data/app/views/plan_my_stuff/testing_projects/show.html.erb +2 -2
- data/config/routes.rb +2 -2
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +56 -3
- data/lib/plan_my_stuff/approval.rb +12 -4
- data/lib/plan_my_stuff/aws_sns_simulator.rb +12 -6
- data/lib/plan_my_stuff/base_metadata.rb +4 -15
- data/lib/plan_my_stuff/base_project.rb +68 -55
- data/lib/plan_my_stuff/base_project_item.rb +61 -57
- data/lib/plan_my_stuff/base_project_metadata.rb +1 -1
- data/lib/plan_my_stuff/client.rb +136 -48
- data/lib/plan_my_stuff/comment.rb +57 -57
- data/lib/plan_my_stuff/comment_metadata.rb +1 -1
- data/lib/plan_my_stuff/configuration.rb +95 -82
- data/lib/plan_my_stuff/errors.rb +10 -10
- data/lib/plan_my_stuff/graphql/queries.rb +1 -1
- data/lib/plan_my_stuff/issue.rb +501 -322
- data/lib/plan_my_stuff/issue_metadata.rb +10 -10
- data/lib/plan_my_stuff/label.rb +32 -16
- data/lib/plan_my_stuff/link.rb +15 -15
- data/lib/plan_my_stuff/markdown.rb +12 -6
- data/lib/plan_my_stuff/metadata_parser.rb +3 -1
- data/lib/plan_my_stuff/notifications.rb +1 -1
- data/lib/plan_my_stuff/pipeline/completed_sweep.rb +2 -2
- data/lib/plan_my_stuff/pipeline/issue_linker.rb +1 -1
- data/lib/plan_my_stuff/pipeline.rb +61 -83
- data/lib/plan_my_stuff/project.rb +4 -4
- data/lib/plan_my_stuff/project_item_metadata.rb +1 -1
- data/lib/plan_my_stuff/project_metadata.rb +1 -1
- data/lib/plan_my_stuff/reminders/closer.rb +1 -1
- data/lib/plan_my_stuff/reminders/fire.rb +3 -3
- data/lib/plan_my_stuff/reminders/sweep.rb +4 -4
- data/lib/plan_my_stuff/repo.rb +12 -6
- data/lib/plan_my_stuff/test_helpers.rb +11 -11
- data/lib/plan_my_stuff/testing_project.rb +12 -11
- data/lib/plan_my_stuff/testing_project_item.rb +11 -9
- data/lib/plan_my_stuff/testing_project_metadata.rb +2 -2
- data/lib/plan_my_stuff/version.rb +1 -1
- data/lib/plan_my_stuff/webhook_replayer.rb +14 -2
- data/lib/plan_my_stuff.rb +26 -2
- data/lib/tasks/plan_my_stuff.rake +33 -20
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6799008cb353423175bdd3132e0fa5f14981b3710378c3b0886a8e9a2394b610
|
|
4
|
+
data.tar.gz: 75b8336ec9aabd1d77a2642de77c9e46310dda3da49e6abd97b997927e906d05
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdb5b8790905ff4f529496ad2a5c63b13a59a58ce524a8df79b20b0efc536854bc986f0e2fe7896b77bfe9b0a15598b6212a83991d46c9925e4dbf9eecab4871
|
|
7
|
+
data.tar.gz: b5c150efa84a7ed3143935216d0ac52ae9c8fa7d99cb45b6add6552c13f3b66fad2f604e1aa8871cb05fc69b3aa16e650bce14a60ad1490d0650af3a3e646d91
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- Issue show "Take" button is now hidden when the issue already has assignees, and `Issues::TakesController#create` rejects the request with a flash error naming the existing assignee. Best-effort race guard for two users clicking Take simultaneously — without it, GitHub silently piles the second user on as a co-assignee.
|
|
8
|
+
- Guard lives in the controller, not `Pipeline.take!` itself, because webhook paths (`handle_issue_assigned`, `handle_projects_v2_item`, `handle_draft_opened`, `handle_converted_to_draft`) legitimately call `take!` on already-assigned issues
|
|
9
|
+
- `Issue#add_viewers` / `Issue#remove_viewers` renamed to `Issue#add_viewers!` / `Issue#remove_viewers!` for consistency with other mutating instance methods (`approve!`, `close!`, etc.)
|
|
10
|
+
- `Approval#status` extended to a 3-state model (`pending`, `approved`, `rejected`); `Approval` gains a `rejected_at` attribute serialized in `to_h` and the metadata blob. Existing approvals deserialize unchanged (`rejected_at` defaults to `nil`)
|
|
11
|
+
- `Issue#fully_approved?` now requires every approver to be `approved` — a single rejection blocks the gate until revoked. Previously equivalent to `pending_approvals.empty?`, which silently treated rejections as "done"
|
|
12
|
+
- `Issue#revoke_approval!` accepts either an `approved` or `rejected` source state (previously only `approved`); raises `ValidationError` when the target is still `pending`. Authorization error message updated to "another user's response"
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `CONFIGURATION.md` documents every `PlanMyStuff::Configuration` option grouped by concern; `README.md` Configuration section trimmed to the two required options plus a link
|
|
17
|
+
- Install generator's initializer template now includes the four pipeline options previously missing from it: `pipeline_testing_field_name`, `pipeline_testing_values`, `pipeline_completion_purge_enabled`, `pipeline_completion_ttl_hours`
|
|
18
|
+
- Project show "Remove from project" button (and `DELETE /projects/:project_id/items/:id` route on `ProjectItemsController`) lets the user remove an item from a project board without bouncing out to GitHub. Calls `ProjectItem#destroy!` which fires `plan_my_stuff.project_item.removed`
|
|
19
|
+
- Issue show "Release" button (and `DELETE /issues/:issue_id/take` route on `Issues::TakesController`) lets a dev who took an issue undo their assignment from the same view, without bouncing out to GitHub or the project board. When the current user is the sole assignee the issue's GitHub assignees are cleared and the project item is removed from the pipeline (`project_item.destroy!`); when other assignees remain the current user is unassigned via `project_item.assign!(remaining)` and the item stays on the project
|
|
20
|
+
- `config.import_access_token` classic PAT (requires `repo` scope) used exclusively for the Issues Import API (`golden-comet-preview`); fine-grained tokens are not supported by that endpoint. Optional — defaults to `nil`
|
|
21
|
+
- `Issue.import!(payloads)` POSTs an `Array<Hash>` (one POST per payload) to GitHub's "Import Issues" preview endpoint and returns one status hash per input. Each payload must include `:repo` plus the GitHub-shaped `:issue` / `:comments` keys; payloads are passed through unchanged otherwise
|
|
22
|
+
- `Issue.check_import!(import_id, repo:)` polls a previously-submitted import for its status
|
|
23
|
+
- `Issue#created_at` / `Comment#created_at` attributes hydrated from GitHub's `created_at` field
|
|
24
|
+
- `Issue#issue_type` reader hydrated from GitHub's native `type.name`. `Issue.create!` and `Issue.update!` accept an `issue_type:` kwarg as a String (passed through), Symbol shortcut (`:bug`, `:feature`, `:it_issue`, `:other`, `:performance`, `:question`, `:task`), or `nil`. On `update!`, omitting the kwarg leaves the type untouched; passing `nil` clears it
|
|
25
|
+
- `config.issue_types` Hash{String => String} maps canonical type names to org-specific display names so consuming apps can rename without touching call sites (e.g. `{ 'Feature' => 'Enhancement' }`); missing keys pass through unchanged
|
|
26
|
+
- `config.controller_rescue` Proc invoked from every user-facing controller `rescue` block (after the gem logs the error and stack trace) so consuming apps can forward swallowed errors to their monitoring service
|
|
27
|
+
- `Issue#reject!(user:)` symmetric with `approve!`; accepts either `pending` or `approved` source state and raises `ValidationError` when already rejected. Fires `plan_my_stuff.issue.approval_rejected` and, when the flip drops the issue out of `fully_approved?`, `approvals_invalidated(trigger: :rejected)`
|
|
28
|
+
- `Issue#approve!` now accepts either `pending` or `rejected` source state; raises `ValidationError` only on `approved -> approved`. Clears `rejected_at` on the rejected -> approved transition
|
|
29
|
+
- `Issue#rejected_approvals` reader returns the subset of approvers with `status == 'rejected'`
|
|
30
|
+
- New events: `plan_my_stuff.issue.approval_rejected` and `plan_my_stuff.issue.rejection_revoked` (the latter fires when revoking a rejection back to pending)
|
|
31
|
+
- Approvals partial renders a `Reject` button alongside `Approve` for pending approvers, a `rejected at <ts>` row, a parenthetical rejected count in the header, and a `Revoke` button for either non-pending state
|
|
32
|
+
|
|
33
|
+
## 0.7.0
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- `Issue#user_link` returns the per-issue URL in the consuming app, computed from `config.issues_url_prefix` + issue number
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- GitHub issues created/updated by PMS now render a markdown link `[Org/Repo#number](user_link)` as the visible body (previously empty); skipped when `issues_url_prefix` is unset
|
|
42
|
+
|
|
3
43
|
## 0.6.0
|
|
4
44
|
|
|
5
45
|
### Breaking
|
|
@@ -7,7 +47,7 @@
|
|
|
7
47
|
- `Pipeline.submit!` removed; consuming apps that called it directly should switch to `ProjectItem.create!` + `Pipeline.take!`
|
|
8
48
|
- `Pipeline::Status::SUBMITTED` and `Pipeline::Status::TESTING` constants removed; `Status::ALL` no longer includes them
|
|
9
49
|
- `Pipeline.request_testing!` no longer moves the `Status` field — it now writes to a separate `Testing` single-select custom field on the pipeline project. The pipeline project must have a `Testing` field with `Testing` / `Not testing` options
|
|
10
|
-
- Active pipeline status set is now `Started
|
|
50
|
+
- Active pipeline status set is now `Started -> In Review -> Ready for Release -> Release in Progress -> Completed`
|
|
11
51
|
|
|
12
52
|
### Added
|
|
13
53
|
|
data/README.md
CHANGED
|
@@ -43,7 +43,7 @@ The gem supports three markdown rendering options. The chosen gem must be in you
|
|
|
43
43
|
| None (raw) | Nothing | `config.markdown_renderer = nil` |
|
|
44
44
|
|
|
45
45
|
> [!NOTE]
|
|
46
|
-
> If you pick `:commonmarker`, use v1.0+ — the v0.23
|
|
46
|
+
> If you pick `:commonmarker`, use v1.0+ — the v0.23 -> v1.0 rewrite renamed the module to `Commonmarker`, swapped `render_html` for `to_html`, and moved options from a symbol array to a nested hash. Apps still on commonmarker < 1.0 must upgrade or switch to `:redcarpet` / `nil`.
|
|
47
47
|
|
|
48
48
|
### Overriding views
|
|
49
49
|
|
|
@@ -55,58 +55,21 @@ rails generate plan_my_stuff:views
|
|
|
55
55
|
|
|
56
56
|
## Configuration
|
|
57
57
|
|
|
58
|
+
The install generator drops a fully-commented initializer at
|
|
59
|
+
`config/initializers/plan_my_stuff.rb`. Two options are required:
|
|
60
|
+
|
|
58
61
|
```ruby
|
|
59
|
-
|
|
60
|
-
PlanMyStuff.configure do |config|
|
|
61
|
-
# Auth (PAT from a bot account with repo + project scopes)
|
|
62
|
+
PMS.configure do |config|
|
|
62
63
|
config.access_token = Rails.application.credentials.dig(:plan_my_stuff, :github_token)
|
|
63
|
-
|
|
64
|
-
# Organization
|
|
65
64
|
config.organization = 'YourOrganization'
|
|
66
|
-
|
|
67
|
-
# Named repo configs
|
|
68
|
-
config.repos[:marketing_website] = 'YourOrganization/MarketingWebsite'
|
|
69
|
-
config.repos[:cms_website] = 'YourOrganization/CMSWebsite'
|
|
70
|
-
config.default_repo = :cms_website
|
|
71
|
-
|
|
72
|
-
# Default project board
|
|
73
|
-
config.default_project_number = 123
|
|
74
|
-
|
|
75
|
-
# User class (your app's model)
|
|
76
|
-
config.user_class = 'User'
|
|
77
|
-
config.display_name_method = :full_name
|
|
78
|
-
config.user_id_method = :id
|
|
79
|
-
|
|
80
|
-
# Support role check (symbol or proc)
|
|
81
|
-
config.support_method = :support?
|
|
82
|
-
|
|
83
|
-
# Markdown rendering (:commonmarker, :redcarpet, or nil)
|
|
84
|
-
config.markdown_renderer = :commonmarker
|
|
85
|
-
|
|
86
|
-
# Request gateway (proc or nil; nil = always send)
|
|
87
|
-
config.should_send_request = nil
|
|
88
|
-
|
|
89
|
-
# Background jobs (when gateway defers a request)
|
|
90
|
-
config.job_classes = {
|
|
91
|
-
create_ticket: 'PmsCreateTicketJob',
|
|
92
|
-
post_comment: 'PmsPostCommentJob',
|
|
93
|
-
update_status: 'PmsUpdateStatusJob'
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
# Fallback actor for notification events when a caller does not pass user:
|
|
97
|
-
config.current_user = -> { Current.user }
|
|
98
|
-
|
|
99
|
-
# Custom fields (stored in issue/comment metadata)
|
|
100
|
-
config.custom_fields = {
|
|
101
|
-
ticket_type: { type: :string },
|
|
102
|
-
notification_recipients: { type: :array }
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
# App name (appears in metadata)
|
|
106
|
-
config.app_name = 'MyApp'
|
|
107
65
|
end
|
|
108
66
|
```
|
|
109
67
|
|
|
68
|
+
Everything else is optional. See [CONFIGURATION.md](CONFIGURATION.md) for the
|
|
69
|
+
full option reference grouped by concern (repos, projects, user
|
|
70
|
+
integration, markdown, request gateway, custom fields, pipeline,
|
|
71
|
+
reminders, archiving, AWS webhook, caching, routes, controllers).
|
|
72
|
+
|
|
110
73
|
The `PMS` alias is available for brevity: `PMS.configure`, `PMS::Issue.find`, etc.
|
|
111
74
|
|
|
112
75
|
## Architecture
|
|
@@ -115,7 +78,7 @@ All state lives on GitHub. The gem exposes it through ActiveRecord-style domain
|
|
|
115
78
|
|
|
116
79
|
### Domain class hierarchy
|
|
117
80
|
|
|
118
|
-
```
|
|
81
|
+
```text
|
|
119
82
|
ApplicationRecord # includes ActiveModel::Model, Attributes, Serializers::JSON
|
|
120
83
|
├── Issue # GitHub Issue
|
|
121
84
|
├── Comment # GitHub Issue comment
|
|
@@ -160,10 +123,10 @@ issue = PMS::Issue.create!(
|
|
|
160
123
|
)
|
|
161
124
|
|
|
162
125
|
# Find
|
|
163
|
-
issue = PMS::Issue.find(repo: :cms_website
|
|
126
|
+
issue = PMS::Issue.find(123, repo: :cms_website)
|
|
164
127
|
issue.title
|
|
165
128
|
issue.body # body without metadata
|
|
166
|
-
issue.metadata #
|
|
129
|
+
issue.metadata # PMS::IssueMetadata
|
|
167
130
|
issue.visible_to?(user) # visibility check
|
|
168
131
|
issue.comments # all comments
|
|
169
132
|
issue.pms_comments # only PMS-created comments
|
|
@@ -179,28 +142,63 @@ issue.update!(state: :closed)
|
|
|
179
142
|
issue.update!(state: :open)
|
|
180
143
|
|
|
181
144
|
# Viewer management (visibility allowlist)
|
|
182
|
-
issue.add_viewers(user_ids: [5, 12], user: current_user)
|
|
183
|
-
issue.remove_viewers(user_ids: [5], user: current_user)
|
|
145
|
+
issue.add_viewers!(user_ids: [5, 12], user: current_user)
|
|
146
|
+
issue.remove_viewers!(user_ids: [5], user: current_user)
|
|
184
147
|
```
|
|
185
148
|
|
|
149
|
+
#### Importing existing issues
|
|
150
|
+
|
|
151
|
+
`PMS::Issue.import!` wraps GitHub's "Import Issues" preview endpoint. It takes an array of GitHub-shaped payloads (one POST per payload) and returns one status hash per input. Payloads are passed through unchanged except for `:repo`, which is extracted to build the request URL.
|
|
152
|
+
|
|
153
|
+
> [!CAUTION]
|
|
154
|
+
> If you choose to use `PMS::Issue.import!`/`PMS::Issue.check_import!`, you must configure `import_access_token`
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
payloads = [
|
|
158
|
+
{
|
|
159
|
+
repo: :cms_website,
|
|
160
|
+
issue: {
|
|
161
|
+
title: '[Rawr-12517] Delete contractor check payment',
|
|
162
|
+
body: "Imported from YouTrack...",
|
|
163
|
+
labels: ['imported-from-youtrack', 'priority:normal'],
|
|
164
|
+
created_at: '2026-04-28T16:04:59Z',
|
|
165
|
+
},
|
|
166
|
+
comments: [
|
|
167
|
+
{ body: 'first comment', created_at: '2026-04-28T16:26:08Z' },
|
|
168
|
+
{ body: 'second comment', created_at: '2026-04-28T16:26:29Z' },
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
statuses = PMS::Issue.import!(payloads)
|
|
174
|
+
status = PMS::Issue.check_import!(statuses.first[:id], repo: :cms_website)
|
|
175
|
+
# => { id: ..., status: 'imported', issue_url: '.../issues/123' }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Notes:
|
|
179
|
+
|
|
180
|
+
- Each payload MUST include `:repo` (symbol, string, or `PMS::Repo`); a missing `:repo` raises `ArgumentError`.
|
|
181
|
+
- The gem does not embed PMS metadata into the payload — callers are responsible for any metadata or formatting they want to preserve from the source system.
|
|
182
|
+
- The endpoint is async: `import` returns `{ id:, status: 'pending', url: ... }`; poll `check_import` until `status` is `'imported'` or `'failed'`.
|
|
183
|
+
- Requires a classic GitHub PAT with the `repo` scope; fine-grained tokens are rejected with 403.
|
|
184
|
+
|
|
186
185
|
### Comments
|
|
187
186
|
|
|
188
187
|
```ruby
|
|
189
188
|
# Create
|
|
190
189
|
comment = PMS::Comment.create!(
|
|
191
|
-
|
|
192
|
-
issue_number: 123,
|
|
190
|
+
issue: issue,
|
|
193
191
|
body: 'Deployed fix to staging, please retest.',
|
|
194
192
|
user: current_user,
|
|
195
193
|
visibility: :public # or :internal (support-only)
|
|
196
194
|
)
|
|
197
195
|
|
|
198
196
|
# List
|
|
199
|
-
comments = PMS::Comment.list(
|
|
200
|
-
comments = PMS::Comment.list(
|
|
197
|
+
comments = PMS::Comment.list(issue: issue)
|
|
198
|
+
comments = PMS::Comment.list(issue: issue, pms_only: true)
|
|
201
199
|
|
|
202
200
|
comment.body # visible text
|
|
203
|
-
comment.metadata #
|
|
201
|
+
comment.metadata # PMS::CommentMetadata (nil for non-PMS comments)
|
|
204
202
|
comment.visibility # :public, :internal, or nil
|
|
205
203
|
comment.pms_comment? # true/false
|
|
206
204
|
comment.visible_to?(user)
|
|
@@ -209,8 +207,8 @@ comment.visible_to?(user)
|
|
|
209
207
|
### Labels
|
|
210
208
|
|
|
211
209
|
```ruby
|
|
212
|
-
PMS::Label.add(
|
|
213
|
-
PMS::Label.remove(
|
|
210
|
+
PMS::Label.add!(issue: issue, labels: ['in-progress'])
|
|
211
|
+
PMS::Label.remove!(issue: issue, labels: ['triage'])
|
|
214
212
|
```
|
|
215
213
|
|
|
216
214
|
### Projects
|
|
@@ -233,10 +231,10 @@ item.move_to!('In Review')
|
|
|
233
231
|
item.assign!('octocat')
|
|
234
232
|
|
|
235
233
|
# Add existing issue to project
|
|
236
|
-
PMS::
|
|
234
|
+
PMS::ProjectItem.create!(issue, project_number: 14)
|
|
237
235
|
|
|
238
236
|
# Add draft item
|
|
239
|
-
PMS::
|
|
237
|
+
PMS::ProjectItem.create!('Draft task', draft: true, body: 'Details...', project_number: 14)
|
|
240
238
|
```
|
|
241
239
|
|
|
242
240
|
### Testing tracking
|
|
@@ -258,11 +256,11 @@ Per-item sign-off:
|
|
|
258
256
|
|
|
259
257
|
```ruby
|
|
260
258
|
item = project.items.first
|
|
261
|
-
item.update_pass_mode!('all')
|
|
262
|
-
item.update_testers!(
|
|
263
|
-
item.update_watchers!(
|
|
264
|
-
item.mark_passed!(
|
|
265
|
-
item.mark_failed!(
|
|
259
|
+
item.update_pass_mode!('all') # or 'any'
|
|
260
|
+
item.update_testers!([alice.id, bob.id])
|
|
261
|
+
item.update_watchers!([carol.id])
|
|
262
|
+
item.mark_passed!(alice) # flips to Passed when Pass Mode is satisfied
|
|
263
|
+
item.mark_failed!(alice, result_notes: 'Reproduced on Safari 17')
|
|
266
264
|
```
|
|
267
265
|
|
|
268
266
|
A board and its items are editable through the mounted UI at `/testing_projects`.
|
|
@@ -279,8 +277,8 @@ Every PMS lifecycle write fires an `ActiveSupport::Notifications` event under th
|
|
|
279
277
|
| `plan_my_stuff.issue.updated` | `issue.save!` / `issue.update!` (any non-state change) |
|
|
280
278
|
| `plan_my_stuff.issue.closed` | `issue.update!(state: :closed)` |
|
|
281
279
|
| `plan_my_stuff.issue.reopened` | `issue.update!(state: :open)` |
|
|
282
|
-
| `plan_my_stuff.issue.viewers_added` | `issue.add_viewers
|
|
283
|
-
| `plan_my_stuff.issue.viewers_removed` | `issue.remove_viewers
|
|
280
|
+
| `plan_my_stuff.issue.viewers_added` | `issue.add_viewers!` |
|
|
281
|
+
| `plan_my_stuff.issue.viewers_removed` | `issue.remove_viewers!` |
|
|
284
282
|
| `plan_my_stuff.issue.approval_requested` | `issue.request_approvals!` |
|
|
285
283
|
| `plan_my_stuff.issue.approval_granted` | `issue.approve!` |
|
|
286
284
|
| `plan_my_stuff.issue.approval_revoked` | `issue.revoke_approval!` |
|
|
@@ -288,9 +286,9 @@ Every PMS lifecycle write fires an `ActiveSupport::Notifications` event under th
|
|
|
288
286
|
| `plan_my_stuff.issue.approvals_invalidated` | aggregate — fires when a revoke (or new approver) drops the set out of fully-approved |
|
|
289
287
|
| `plan_my_stuff.comment.created` | `Comment.create!` |
|
|
290
288
|
| `plan_my_stuff.comment.updated` | `comment.save!` / `comment.update!` |
|
|
291
|
-
| `plan_my_stuff.label.added` | `Label.add
|
|
292
|
-
| `plan_my_stuff.label.removed` | `Label.remove
|
|
293
|
-
| `plan_my_stuff.project_item.added` | `ProjectItem.create!`
|
|
289
|
+
| `plan_my_stuff.label.added` | `Label.add!` |
|
|
290
|
+
| `plan_my_stuff.label.removed` | `Label.remove!` |
|
|
291
|
+
| `plan_my_stuff.project_item.added` | `ProjectItem.create!` (issue or `draft: true`) |
|
|
294
292
|
| `plan_my_stuff.project_item.removed` | `project_item.destroy!` |
|
|
295
293
|
| `plan_my_stuff.project_item.assigned` | `project_item.assign!` |
|
|
296
294
|
| `plan_my_stuff.project_item.status_changed` | `project_item.move_to!` |
|
|
@@ -306,14 +304,14 @@ All events include:
|
|
|
306
304
|
|
|
307
305
|
Additional keys by event:
|
|
308
306
|
|
|
309
|
-
- `issue.updated` / `comment.updated`
|
|
310
|
-
- `issue.viewers_added` / `viewers_removed`
|
|
311
|
-
- `issue.approval_requested`
|
|
312
|
-
- `issue.approval_granted` / `approval_revoked`
|
|
313
|
-
- `issue.approvals_invalidated`
|
|
314
|
-
- `label.added` / `label.removed`
|
|
315
|
-
- `project_item.assigned`
|
|
316
|
-
- `project_item.status_changed`
|
|
307
|
+
- `issue.updated` / `comment.updated` -> `:changes` — hash of `{ attr => [old, new] }`
|
|
308
|
+
- `issue.viewers_added` / `viewers_removed` -> `:user_ids`
|
|
309
|
+
- `issue.approval_requested` -> `:approvals` (array of newly-added `PMS::Approval`)
|
|
310
|
+
- `issue.approval_granted` / `approval_revoked` -> `:approval` (the flipped `PMS::Approval`)
|
|
311
|
+
- `issue.approvals_invalidated` -> `:trigger` — `:revoked` or `:approver_added`
|
|
312
|
+
- `label.added` / `label.removed` -> `:labels`
|
|
313
|
+
- `project_item.assigned` -> `:assignees`
|
|
314
|
+
- `project_item.status_changed` -> `:status`, `:previous_status`
|
|
317
315
|
|
|
318
316
|
### Actor resolution
|
|
319
317
|
|
|
@@ -345,7 +343,7 @@ expect {
|
|
|
345
343
|
}.to(have_fired_event('plan_my_stuff.issue.created').with(user: alice))
|
|
346
344
|
|
|
347
345
|
# Raw capture
|
|
348
|
-
events =
|
|
346
|
+
events = PMS::TestHelpers::Notifications.capture do
|
|
349
347
|
PMS::Issue.create!(...)
|
|
350
348
|
end
|
|
351
349
|
events.first[:name] # => "plan_my_stuff.issue.created"
|
|
@@ -380,15 +378,14 @@ issue.remove_approvers!(user_ids: [alice.id], user: current_user)
|
|
|
380
378
|
```ruby
|
|
381
379
|
issue.approvers # Array<PMS::Approval> — all records (pending + approved)
|
|
382
380
|
issue.pending_approvals # subset still pending
|
|
383
|
-
issue.approvals_required? # true iff approvers.
|
|
381
|
+
issue.approvals_required? # true iff approvers.present?
|
|
384
382
|
issue.fully_approved? # true iff approvals_required? && pending_approvals.empty?
|
|
385
383
|
```
|
|
386
384
|
|
|
387
385
|
### Pipeline gating
|
|
388
386
|
|
|
389
|
-
`PMS::
|
|
387
|
+
`PMS::PendingApprovalsError` is raised when any pending approval exists on the linked issue. Gated transitions:
|
|
390
388
|
|
|
391
|
-
- `Pipeline.submit!`
|
|
392
389
|
- `Pipeline.take!`
|
|
393
390
|
- `Pipeline.mark_in_review!`
|
|
394
391
|
- `Pipeline.request_testing!`
|
|
@@ -406,8 +403,8 @@ See the notifications catalog above — `approval_requested`, `approval_granted`
|
|
|
406
403
|
require 'plan_my_stuff/test_helpers'
|
|
407
404
|
|
|
408
405
|
# Build an issue with some approvals already in place (no API call)
|
|
409
|
-
issue =
|
|
410
|
-
|
|
406
|
+
issue = PMS::TestHelpers.build_issue
|
|
407
|
+
PMS::TestHelpers.stub_approvals(issue, approved: [alice], pending: [bob])
|
|
411
408
|
|
|
412
409
|
issue.fully_approved? # false
|
|
413
410
|
issue.pending_approvals # [PMS::Approval(user_id: bob.id, status: 'pending')]
|
|
@@ -493,7 +490,7 @@ An end-user reply on a `closed_by_inactivity` issue auto-reopens it, strips the
|
|
|
493
490
|
|
|
494
491
|
### Scheduling the sweep
|
|
495
492
|
|
|
496
|
-
The gem ships `
|
|
493
|
+
The gem ships `PMS::RemindersSweepJob`, an ActiveJob that walks a repo's waiting issues and dispatches reminders + auto-closes. Each job self-requeues after perform (default cadence: 6:30am ET next day), so you only need to kick off the initial enqueue — a rake task handles that:
|
|
497
494
|
|
|
498
495
|
```bash
|
|
499
496
|
# Enqueue one job per configured repo:
|
|
@@ -506,7 +503,7 @@ rake plan_my_stuff:reminders:sweep REPO=element
|
|
|
506
503
|
You can also schedule from Ruby if you prefer:
|
|
507
504
|
|
|
508
505
|
```ruby
|
|
509
|
-
|
|
506
|
+
PMS::RemindersSweepJob.requeue(:your_repo_key) # schedules for next_run
|
|
510
507
|
```
|
|
511
508
|
|
|
512
509
|
`retry_on StandardError, attempts: 1` prevents geometric duplicate pile-up on Delayed-style adapters — if perform raises, the follow-up run (already enqueued by the `around_perform` ensure) picks up tomorrow.
|
|
@@ -514,7 +511,7 @@ PlanMyStuff::RemindersSweepJob.requeue(:your_repo_key) # schedules for next_run
|
|
|
514
511
|
Override the cadence by subclassing:
|
|
515
512
|
|
|
516
513
|
```ruby
|
|
517
|
-
class MyRemindersJob <
|
|
514
|
+
class MyRemindersJob < PMS::RemindersSweepJob
|
|
518
515
|
def self.next_run
|
|
519
516
|
4.hours.from_now.utc # run every 4 hours
|
|
520
517
|
end
|
|
@@ -539,7 +536,7 @@ Closed PMS issues that have aged past `config.archive_closed_after_days` (defaul
|
|
|
539
536
|
4. Stamp `metadata.archived_at`.
|
|
540
537
|
5. Emit `plan_my_stuff.issue.archived` with `reason: :aged_closed`.
|
|
541
538
|
|
|
542
|
-
The sweep runs inside `
|
|
539
|
+
The sweep runs inside `PMS::RemindersSweepJob` alongside the follow-up reminders sweep — same cadence, same rake task.
|
|
543
540
|
|
|
544
541
|
### Exclusions
|
|
545
542
|
|
|
@@ -660,7 +657,7 @@ issue.save! # validates; raises ActiveMode
|
|
|
660
657
|
|
|
661
658
|
### Statuses
|
|
662
659
|
|
|
663
|
-
`PMS::Pipeline::Status::ALL` (in order): `
|
|
660
|
+
`PMS::Pipeline::Status::ALL` (in order): `Started` -> `In Review` -> `Ready for Release` -> `Release in Progress` -> `Completed`. (Testing runs orthogonally as a separate single-select field flipped by `request_testing!`, not as a Status value.)
|
|
664
661
|
|
|
665
662
|
Display names can be overridden in the consuming app via `config.pipeline_statuses`; the constants above remain the internal identifiers.
|
|
666
663
|
|
|
@@ -668,36 +665,36 @@ Display names can be overridden in the consuming app via `config.pipeline_status
|
|
|
668
665
|
|
|
669
666
|
```ruby
|
|
670
667
|
# Add an issue to the pipeline board
|
|
671
|
-
item = PMS::
|
|
668
|
+
item = PMS::ProjectItem.create!(issue, project_number: PMS::Pipeline.resolve_pipeline_project_number!)
|
|
672
669
|
|
|
673
670
|
# Forward transitions
|
|
674
|
-
PMS::Pipeline.take!(item)
|
|
675
|
-
PMS::Pipeline.mark_in_review!(item)
|
|
676
|
-
PMS::Pipeline.request_testing!(item)
|
|
671
|
+
PMS::Pipeline.take!(item) # Started
|
|
672
|
+
PMS::Pipeline.mark_in_review!(item) # In Review
|
|
673
|
+
PMS::Pipeline.request_testing!(item) # flips Testing single-select to active
|
|
677
674
|
PMS::Pipeline.mark_ready_for_release!(item) # Ready for Release
|
|
678
675
|
|
|
679
676
|
# Deployment-driven transitions
|
|
680
|
-
PMS::Pipeline.start_deployment!(commit_sha: 'abc123…') # every Ready item whose issue is in the commit
|
|
677
|
+
PMS::Pipeline.start_deployment!(commit_sha: 'abc123…') # every Ready item whose issue is in the commit -> Release in Progress
|
|
681
678
|
PMS::Pipeline.complete_deployment!(item, deployment_id: 42) # Completed (when issue.metadata.auto_complete)
|
|
682
679
|
|
|
683
680
|
# Remove from pipeline (deletes the project item)
|
|
684
681
|
PMS::Pipeline.remove!(item)
|
|
685
682
|
```
|
|
686
683
|
|
|
687
|
-
Forward transitions call `Pipeline.guard_approvals!(issue)`, which raises `PMS::
|
|
684
|
+
Forward transitions call `Pipeline.guard_approvals!(issue)`, which raises `PMS::PendingApprovalsError` if the linked issue has un-approved required approvers. Batch/automated transitions (`start_deployment!`, `complete_deployment!`, `remove!`) skip the guard on purpose.
|
|
688
685
|
|
|
689
686
|
### Webhook-driven automation
|
|
690
687
|
|
|
691
688
|
The engine mounts two webhook endpoints when the pipeline is enabled:
|
|
692
689
|
|
|
693
|
-
- `POST /webhooks/github` — takes GitHub `pull_request` events. On `closed` against a tracked branch, the gem calls `IssueLinker` to extract `#123` references from the PR body and commit messages, then transitions each linked issue (e.g. merged to main
|
|
690
|
+
- `POST /webhooks/github` — takes GitHub `pull_request` events. On `closed` against a tracked branch, the gem calls `IssueLinker` to extract `#123` references from the PR body and commit messages, then transitions each linked issue (e.g. merged to main -> `start_deployment!`).
|
|
694
691
|
- `POST /webhooks/aws` — takes CodeDeploy/SNS lifecycle events. On a successful deployment, the gem flips matching items to `Completed`.
|
|
695
692
|
|
|
696
693
|
See `designs/release_cycle/plan.md` for the full design, including the `projects_v2_item` path that fires `Pipeline.take!` when a human drags an item to Started on github.com.
|
|
697
694
|
|
|
698
695
|
### "Take" button
|
|
699
696
|
|
|
700
|
-
The mounted UI exposes a **Take** button on pipeline issues that calls `Pipeline.take!` and assigns the current user. Your app's user-id
|
|
697
|
+
The mounted UI exposes a **Take** button on pipeline issues that calls `Pipeline.take!` and assigns the current user. Your app's user-id -> GitHub login mapping is provided by `config.github_login_for` (a hash keyed by user id).
|
|
701
698
|
|
|
702
699
|
## Verify setup
|
|
703
700
|
|
|
@@ -716,7 +713,7 @@ The gem provides test helpers for consuming apps:
|
|
|
716
713
|
require 'plan_my_stuff/test_helpers'
|
|
717
714
|
|
|
718
715
|
RSpec.configure do |config|
|
|
719
|
-
config.include
|
|
716
|
+
config.include PMS::TestHelpers
|
|
720
717
|
end
|
|
721
718
|
```
|
|
722
719
|
|
|
@@ -747,7 +744,7 @@ All routes are under the engine's mount point. Each group can be disabled via `c
|
|
|
747
744
|
| `POST /issues/:issue_id/closure` / `DELETE …/closure` | close / reopen |
|
|
748
745
|
| `POST /issues/:issue_id/waiting` / `DELETE …/waiting` | mark / clear waiting-on-user |
|
|
749
746
|
| `POST /issues/:issue_id/viewers` / `DELETE …/viewers/:id` | add / remove viewer |
|
|
750
|
-
| `POST /issues/:issue_id/take` (pipeline) | Take button
|
|
747
|
+
| `POST /issues/:issue_id/take` (pipeline) | Take button -> `Pipeline.take!` |
|
|
751
748
|
| `POST /issues/:issue_id/comments`, `GET …/comments/:id/edit`, `PATCH …/comments/:id` | create / edit / update comment |
|
|
752
749
|
| `POST /issues/:issue_id/labels` / `DELETE …/labels/:id` | add / remove label |
|
|
753
750
|
| `POST /issues/:issue_id/links` / `DELETE …/links/:id` | link / unlink related issue |
|
|
@@ -786,7 +783,7 @@ Every mounted route resolves its controller through `config.controller_for(key)`
|
|
|
786
783
|
|
|
787
784
|
```ruby
|
|
788
785
|
# app/controllers/my_app/issues_controller.rb
|
|
789
|
-
class MyApp::IssuesController <
|
|
786
|
+
class MyApp::IssuesController < PMS::IssuesController
|
|
790
787
|
before_action :authenticate_user!
|
|
791
788
|
before_action :authorize_ticket_access
|
|
792
789
|
end
|
|
@@ -799,9 +796,9 @@ PlanMyStuff.configure do |config|
|
|
|
799
796
|
end
|
|
800
797
|
```
|
|
801
798
|
|
|
802
|
-
Overridable keys (see `
|
|
799
|
+
Overridable keys (see `PMS::Configuration::DEFAULT_CONTROLLERS`):
|
|
803
800
|
|
|
804
|
-
```
|
|
801
|
+
```text
|
|
805
802
|
issues issues/closures
|
|
806
803
|
comments issues/viewers
|
|
807
804
|
labels issues/takes
|
|
@@ -6,6 +6,7 @@ module PlanMyStuff
|
|
|
6
6
|
helper Rails.application.routes.url_helpers
|
|
7
7
|
|
|
8
8
|
before_action :authenticate_pms_user!
|
|
9
|
+
before_action :set_support_user
|
|
9
10
|
|
|
10
11
|
private
|
|
11
12
|
|
|
@@ -23,8 +24,12 @@ module PlanMyStuff
|
|
|
23
24
|
instance_exec(&pms_auth) if pms_auth
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
#
|
|
27
|
-
|
|
27
|
+
# @return [void]
|
|
28
|
+
def set_support_user
|
|
29
|
+
@support_user = support_user?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the current user for PMS visibility checks. Delegates to the consuming app's current_user method.
|
|
28
33
|
#
|
|
29
34
|
# @return [Object, nil]
|
|
30
35
|
#
|
|
@@ -34,7 +39,7 @@ module PlanMyStuff
|
|
|
34
39
|
|
|
35
40
|
# @return [Boolean]
|
|
36
41
|
def support_user?
|
|
37
|
-
pms_current_user.present? &&
|
|
42
|
+
pms_current_user.present? && PlanMyStuff::UserResolver.support?(pms_current_user)
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
# Redirects non-support users back with an error.
|
|
@@ -49,6 +54,20 @@ module PlanMyStuff
|
|
|
49
54
|
redirect_to(path)
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
# Logs +error+ and invokes the consuming app's +PlanMyStuff.configuration.controller_rescue+ hook so monitoring
|
|
58
|
+
# can fire even when the rescue swallows the error and redirects to a flash. Wired into every user-facing
|
|
59
|
+
# controller +rescue+.
|
|
60
|
+
#
|
|
61
|
+
# @param error [StandardError]
|
|
62
|
+
#
|
|
63
|
+
# @return [void]
|
|
64
|
+
#
|
|
65
|
+
def pms_handle_rescue(error)
|
|
66
|
+
Rails.logger.error("[PlanMyStuff] #{error.class}: #{error.message}")
|
|
67
|
+
Rails.logger.error(error.backtrace.join("\n")) if error.backtrace
|
|
68
|
+
PlanMyStuff.configuration.controller_rescue&.call(error)
|
|
69
|
+
end
|
|
70
|
+
|
|
52
71
|
# Splits a comma-separated labels string into an array.
|
|
53
72
|
#
|
|
54
73
|
# @param labels_string [String, nil]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
class CommentsController < ApplicationController
|
|
4
|
+
class CommentsController < PlanMyStuff::ApplicationController
|
|
5
5
|
# POST /issues/:issue_id/comments
|
|
6
6
|
def create
|
|
7
|
-
@issue =
|
|
7
|
+
@issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
PlanMyStuff::Comment.create!(
|
|
10
10
|
issue: @issue,
|
|
11
11
|
body: comment_params[:body],
|
|
12
12
|
user: pms_current_user,
|
|
@@ -16,7 +16,8 @@ module PlanMyStuff
|
|
|
16
16
|
|
|
17
17
|
flash[:success] = 'Comment was successfully created.'
|
|
18
18
|
redirect_to(plan_my_stuff.issue_path(@issue.number, repo: @issue.repo.full_name))
|
|
19
|
-
rescue
|
|
19
|
+
rescue PlanMyStuff::LockedIssueError => e
|
|
20
|
+
pms_handle_rescue(e)
|
|
20
21
|
flash[:error] = 'This issue is locked; no new comments can be posted.'
|
|
21
22
|
redirect_to(plan_my_stuff.issue_path(@issue.number, repo: @issue.repo.full_name))
|
|
22
23
|
end
|
|
@@ -26,8 +27,6 @@ module PlanMyStuff
|
|
|
26
27
|
load_comment
|
|
27
28
|
return unless @comment
|
|
28
29
|
return redirect_to_issue if issue_body_comment?
|
|
29
|
-
|
|
30
|
-
@support_user = support_user?
|
|
31
30
|
return if can_edit?(@comment)
|
|
32
31
|
|
|
33
32
|
redirect_to_unauthorized(plan_my_stuff.issue_path(@issue.number, repo: @issue.repo.full_name))
|
|
@@ -39,7 +38,6 @@ module PlanMyStuff
|
|
|
39
38
|
return unless @comment
|
|
40
39
|
return redirect_to_issue if issue_body_comment?
|
|
41
40
|
|
|
42
|
-
@support_user = support_user?
|
|
43
41
|
unless can_edit?(@comment)
|
|
44
42
|
redirect_to_unauthorized(plan_my_stuff.issue_path(@issue.number, repo: @issue.repo.full_name))
|
|
45
43
|
|
|
@@ -49,13 +47,14 @@ module PlanMyStuff
|
|
|
49
47
|
update_attrs = { body: comment_params[:body] }
|
|
50
48
|
update_attrs[:visibility] = comment_params[:visibility].to_sym if @support_user && comment_params[:visibility]
|
|
51
49
|
|
|
52
|
-
@comment.update!(**update_attrs)
|
|
50
|
+
@comment.update!(**update_attrs, user: pms_current_user)
|
|
53
51
|
|
|
54
52
|
flash[:success] = 'Comment was successfully updated.'
|
|
55
53
|
redirect_to(plan_my_stuff.issue_path(@issue.number, repo: @issue.repo.full_name))
|
|
56
|
-
rescue
|
|
54
|
+
rescue PlanMyStuff::StaleObjectError => e
|
|
55
|
+
pms_handle_rescue(e)
|
|
57
56
|
flash.now[:error] = 'Comment was modified by someone else. Please review the latest changes and try again.'
|
|
58
|
-
render(:edit, status:
|
|
57
|
+
render(:edit, status: PlanMyStuff.unprocessable_status)
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
private
|
|
@@ -70,13 +69,12 @@ module PlanMyStuff
|
|
|
70
69
|
# @return [void]
|
|
71
70
|
#
|
|
72
71
|
def load_comment
|
|
73
|
-
@issue =
|
|
74
|
-
@comment =
|
|
72
|
+
@issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
73
|
+
@comment = PlanMyStuff::Comment.find(params[:id].to_i, issue: @issue)
|
|
75
74
|
end
|
|
76
75
|
|
|
77
|
-
# Returns true if the current user can edit the given comment.
|
|
78
|
-
#
|
|
79
|
-
# their own comments.
|
|
76
|
+
# Returns true if the current user can edit the given comment. Support users can edit any comment. Regular users
|
|
77
|
+
# can only edit their own comments.
|
|
80
78
|
#
|
|
81
79
|
# @param comment [PlanMyStuff::Comment]
|
|
82
80
|
#
|
|
@@ -88,7 +86,7 @@ module PlanMyStuff
|
|
|
88
86
|
user = pms_current_user
|
|
89
87
|
return false if user.blank?
|
|
90
88
|
|
|
91
|
-
comment.metadata.created_by ==
|
|
89
|
+
comment.metadata.created_by == PlanMyStuff::UserResolver.user_id(user)
|
|
92
90
|
end
|
|
93
91
|
|
|
94
92
|
# @return [Boolean]
|