plan_my_stuff 0.26.0 → 0.28.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 +21 -0
- data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +13 -4
- data/lib/plan_my_stuff/comment.rb +1 -24
- data/lib/plan_my_stuff/issue.rb +80 -2
- data/lib/plan_my_stuff/pipeline.rb +4 -1
- data/lib/plan_my_stuff/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9aac5ec777cf9b53a459843e9bdbf1177178fe1623457a60d266e993db3d2d93
|
|
4
|
+
data.tar.gz: 1b7089b760fa0552e3b861732472e43cab09667699e1528703809a2ce753254f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 84f1cc4e7dd87ed3e33f2095d3c0f57c9ad61972ffaa5ed1d9379dfb6bb0e7bc1b07491c76c6c4f4618c39b977d7240689de5552a8c9f60fe1b474c29dde2637
|
|
7
|
+
data.tar.gz: 1d355a051c6d6f0903c80558019790ab0e1d84adb8885b28b0c18b57802ed16c338ab7793ca24f535dc01980296fcc33b19eacc3acd204d08586db25d63138d4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `Issue.list_page_info(...)` returns a `PlanMyStuff::Issue::PageInfo` value object exposing `.issues`, `.page`,
|
|
8
|
+
`.per_page`, `.has_next?`, and `.has_prev?`. `Issue.list` now delegates to it; return type unchanged
|
|
9
|
+
(`Array<Issue>`). Closes #63.
|
|
10
|
+
|
|
11
|
+
## 0.27.0
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `PlanMyStuff::Issue#mark_responded!(user)` - stamps `metadata.responded_at` on the first support-user engagement of
|
|
16
|
+
any kind. No-ops when the user is blank, not a support user, the issue isn't a PMS issue, or `responded_at` is
|
|
17
|
+
already set.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `Pipeline.take!` and the `issues.assigned` webhook now stamp `metadata.responded_at` on first support engagement
|
|
22
|
+
(was previously only set by the first support comment) (closes #60).
|
|
23
|
+
|
|
3
24
|
## 0.26.0
|
|
4
25
|
|
|
5
26
|
### Added
|
|
@@ -91,6 +91,10 @@ module PlanMyStuff
|
|
|
91
91
|
# GitHub already records the issue assignment (that's what triggered this webhook), so the gem does not
|
|
92
92
|
# call +assign!+ on the project item -- that would clobber co-assignees on the underlying issue.
|
|
93
93
|
#
|
|
94
|
+
# Regardless of pipeline state, +mark_responded!+ stamps +metadata.responded_at+ when a support user is the
|
|
95
|
+
# assignee -- including self-assigns to issues already in the pipeline. The method's own guards no-op for
|
|
96
|
+
# non-support assignees and already-responded issues.
|
|
97
|
+
#
|
|
94
98
|
# @return [void]
|
|
95
99
|
#
|
|
96
100
|
def handle_issue_assigned
|
|
@@ -101,6 +105,14 @@ module PlanMyStuff
|
|
|
101
105
|
|
|
102
106
|
return if assignee_login.blank?
|
|
103
107
|
|
|
108
|
+
repo = payload_params.dig(:repository, :full_name)
|
|
109
|
+
issue = PlanMyStuff::Issue.find(issue_number, repo: repo)
|
|
110
|
+
assignee_user = PlanMyStuff::UserResolver.from_github_login(assignee_login)
|
|
111
|
+
|
|
112
|
+
# Stamp before the already-in-pipeline early-return so a support user self-assigning to an
|
|
113
|
+
# already-taken issue still records a first response.
|
|
114
|
+
issue.mark_responded!(assignee_user)
|
|
115
|
+
|
|
104
116
|
existing = PlanMyStuff::Pipeline::IssueLinker.find_project_item(issue_number)
|
|
105
117
|
if existing.present?
|
|
106
118
|
Rails.logger.info("[PlanMyStuff] Issue ##{issue_number} already in pipeline project, skipping")
|
|
@@ -108,9 +120,6 @@ module PlanMyStuff
|
|
|
108
120
|
return
|
|
109
121
|
end
|
|
110
122
|
|
|
111
|
-
repo = payload_params.dig(:repository, :full_name)
|
|
112
|
-
issue = PlanMyStuff::Issue.find(issue_number, repo: repo)
|
|
113
|
-
|
|
114
123
|
if issue.approvals_required? && !issue.fully_approved?
|
|
115
124
|
Rails.logger.info("[PlanMyStuff] Issue ##{issue_number} has pending approvals, skipping")
|
|
116
125
|
|
|
@@ -119,7 +128,7 @@ module PlanMyStuff
|
|
|
119
128
|
|
|
120
129
|
number = PlanMyStuff::Pipeline.resolve_pipeline_project_number!
|
|
121
130
|
project_item = PlanMyStuff::ProjectItem.create!(issue, project_number: number)
|
|
122
|
-
PlanMyStuff::Pipeline.take!(project_item, user:
|
|
131
|
+
PlanMyStuff::Pipeline.take!(project_item, user: assignee_user)
|
|
123
132
|
end
|
|
124
133
|
|
|
125
134
|
# Removes the issue from the pipeline project when the LAST assignee is removed. If any assignees remain,
|
|
@@ -91,7 +91,7 @@ module PlanMyStuff
|
|
|
91
91
|
cache_writer: :write_comment,
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
issue.mark_responded!(resolved_user) unless skip_responded
|
|
95
95
|
|
|
96
96
|
comment = build(result, issue: issue)
|
|
97
97
|
PlanMyStuff::Notifications.instrument('comment_created', comment, user: resolved_user)
|
|
@@ -218,29 +218,6 @@ module PlanMyStuff
|
|
|
218
218
|
:internal
|
|
219
219
|
end
|
|
220
220
|
|
|
221
|
-
# Sets responded_at on the issue metadata if this is the first support comment and the issue hasn't been
|
|
222
|
-
# responded to yet.
|
|
223
|
-
#
|
|
224
|
-
# @param issue [PlanMyStuff::Issue] parent issue
|
|
225
|
-
# @param user [Object, nil] resolved user object
|
|
226
|
-
#
|
|
227
|
-
# @return [void]
|
|
228
|
-
#
|
|
229
|
-
def mark_issue_responded_if_first_support_comment!(issue, user)
|
|
230
|
-
return if user.nil?
|
|
231
|
-
|
|
232
|
-
return unless PlanMyStuff::UserResolver.support?(user)
|
|
233
|
-
return unless issue.pms_issue?
|
|
234
|
-
|
|
235
|
-
return if issue.metadata.responded?
|
|
236
|
-
|
|
237
|
-
PlanMyStuff::Issue.update!(
|
|
238
|
-
number: issue.number,
|
|
239
|
-
repo: issue.repo,
|
|
240
|
-
metadata: { responded_at: PlanMyStuff.format_time(Time.now.utc) },
|
|
241
|
-
)
|
|
242
|
-
end
|
|
243
|
-
|
|
244
221
|
# Mutates issue waiting state based on the comment's author. Support users with +waiting_on_reply: true+ enter
|
|
245
222
|
# the issue into waiting-on-user state. Non-support users clear any active waiting-on-user state and
|
|
246
223
|
# auto-reopen issues that were closed by the inactivity sweep.
|
data/lib/plan_my_stuff/issue.rb
CHANGED
|
@@ -9,6 +9,21 @@ module PlanMyStuff
|
|
|
9
9
|
# - `Issue.create!` / `Issue.find` / `Issue.list` return persisted instances
|
|
10
10
|
# - `issue.save!` / `issue.update!` / `issue.reload` for persistence
|
|
11
11
|
class Issue < PlanMyStuff::ApplicationRecord
|
|
12
|
+
# Value object returned by +Issue.list_page_info+: the fetched issues plus pagination metadata read from the
|
|
13
|
+
# +Link+ header of the +list_issues+ response. +:total_pages+ is intentionally absent -- GitHub's issues endpoint
|
|
14
|
+
# is cursor-paginated and never advertises +rel="last"+.
|
|
15
|
+
#
|
|
16
|
+
# @!attribute [r] issues
|
|
17
|
+
# @return [Array<PlanMyStuff::Issue>]
|
|
18
|
+
# @!attribute [r] page
|
|
19
|
+
# @return [Integer] echo of the requested page
|
|
20
|
+
# @!attribute [r] per_page
|
|
21
|
+
# @return [Integer] echo of the requested per_page
|
|
22
|
+
PageInfo = Data.define(:issues, :page, :per_page, :has_next, :has_prev) do
|
|
23
|
+
alias_method :has_next?, :has_next
|
|
24
|
+
alias_method :has_prev?, :has_prev
|
|
25
|
+
end
|
|
26
|
+
|
|
12
27
|
include PlanMyStuff::IssueExtractions::Approvals
|
|
13
28
|
include PlanMyStuff::IssueExtractions::Links
|
|
14
29
|
include PlanMyStuff::IssueExtractions::Viewers
|
|
@@ -332,7 +347,38 @@ module PlanMyStuff
|
|
|
332
347
|
#
|
|
333
348
|
# @return [Array<PlanMyStuff::Issue>]
|
|
334
349
|
#
|
|
335
|
-
def list(
|
|
350
|
+
def list(**)
|
|
351
|
+
list_page_info(**).issues
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Lists GitHub issues like +.list+, but returns a +PageInfo+ value object carrying the issues plus pagination
|
|
355
|
+
# metadata read from the response's +Link+ header in the same request. Use this over +.list+ when a caller needs
|
|
356
|
+
# to know whether more pages exist (e.g. to render "Next"/"Prev" controls) without an optimistic +page + 1+
|
|
357
|
+
# probe.
|
|
358
|
+
#
|
|
359
|
+
# Shares the entire parameter surface, filtering, and PR-rejection behavior of +.list+; see it for semantics.
|
|
360
|
+
#
|
|
361
|
+
# Note the PR-filter wart: +per_page+ caps GitHub's raw item count (issues + PRs), but PRs are stripped
|
|
362
|
+
# client-side afterward, so +page_info.issues.length+ may be smaller than +per_page+. +has_next?+ comes straight
|
|
363
|
+
# from the +Link+ header, so it reflects raw items too -- a page can report +has_next? == true+ while showing
|
|
364
|
+
# fewer than +per_page+ issues.
|
|
365
|
+
#
|
|
366
|
+
# @raise [ArgumentError] when +priority_list: false+ is passed, or when +issue_type:+ is an Array
|
|
367
|
+
# @raise [PlanMyStuff::IssueFieldsNotEnabledError] when +issue_fields:+ is passed and
|
|
368
|
+
# +config.issue_fields_enabled+ is +false+
|
|
369
|
+
#
|
|
370
|
+
# @param repo [Symbol, String, nil] defaults to config.default_repo
|
|
371
|
+
# @param state [Symbol] :open, :closed, or :all
|
|
372
|
+
# @param labels [Array<String>]
|
|
373
|
+
# @param issue_type [String, Symbol, nil] a single GitHub issue type name
|
|
374
|
+
# @param issue_fields [Hash{String,Symbol => Object,Range,nil}, nil] GitHub Issue Field equality / range filters
|
|
375
|
+
# @param priority_list [Boolean, nil] when +true+, restricts to +Priority List+ +Yes+ issues
|
|
376
|
+
# @param page [Integer]
|
|
377
|
+
# @param per_page [Integer]
|
|
378
|
+
#
|
|
379
|
+
# @return [PlanMyStuff::Issue::PageInfo]
|
|
380
|
+
#
|
|
381
|
+
def list_page_info(
|
|
336
382
|
repo: nil,
|
|
337
383
|
state: :open,
|
|
338
384
|
labels: [],
|
|
@@ -366,8 +412,16 @@ module PlanMyStuff
|
|
|
366
412
|
params[:issue_field_values] = field_pairs.join(',') if field_pairs.present?
|
|
367
413
|
|
|
368
414
|
github_issues = client.rest(:list_issues, resolved_repo, **params)
|
|
415
|
+
rels = client.last_response&.rels || {}
|
|
369
416
|
filtered = github_issues.reject { |gi| gi.respond_to?(:pull_request) && gi.pull_request }
|
|
370
|
-
|
|
417
|
+
|
|
418
|
+
PageInfo.new(
|
|
419
|
+
issues: filtered.map { |gi| build(gi, repo: resolved_repo) },
|
|
420
|
+
page: page,
|
|
421
|
+
per_page: per_page,
|
|
422
|
+
has_next: rels[:next].present?,
|
|
423
|
+
has_prev: rels[:prev].present?,
|
|
424
|
+
)
|
|
371
425
|
end
|
|
372
426
|
|
|
373
427
|
# Convenience shortcut for +list(priority_list: true, ...)+. See +.list+ for parameter semantics.
|
|
@@ -886,6 +940,30 @@ module PlanMyStuff
|
|
|
886
940
|
save!(user: user, skip_notification: skip_notification)
|
|
887
941
|
end
|
|
888
942
|
|
|
943
|
+
# Stamps +metadata.responded_at+ on the first support-user engagement with this issue. Centralizes the guards so
|
|
944
|
+
# every engagement path (first support comment, +Pipeline.take!+, self-assign webhook) can funnel through one
|
|
945
|
+
# method. No-ops unless +user+ resolves to a support user on a PMS issue that hasn't been responded to yet.
|
|
946
|
+
#
|
|
947
|
+
# @param user [Object, nil] actor engaging with the issue (resolved via +PlanMyStuff::UserResolver+)
|
|
948
|
+
#
|
|
949
|
+
# @return [void]
|
|
950
|
+
#
|
|
951
|
+
def mark_responded!(user)
|
|
952
|
+
resolved = PlanMyStuff::UserResolver.resolve(user)
|
|
953
|
+
return if resolved.blank?
|
|
954
|
+
|
|
955
|
+
return unless PlanMyStuff::UserResolver.support?(resolved)
|
|
956
|
+
return unless pms_issue?
|
|
957
|
+
|
|
958
|
+
return if metadata.responded?
|
|
959
|
+
|
|
960
|
+
self.class.update!(
|
|
961
|
+
number: number,
|
|
962
|
+
repo: repo,
|
|
963
|
+
metadata: { responded_at: PlanMyStuff.format_time(Time.now.utc) },
|
|
964
|
+
)
|
|
965
|
+
end
|
|
966
|
+
|
|
889
967
|
# Re-fetches this issue from GitHub and updates all local attributes.
|
|
890
968
|
#
|
|
891
969
|
# @return [self]
|
|
@@ -150,7 +150,8 @@ module PlanMyStuff
|
|
|
150
150
|
locked_statuses.include?(project_item.status)
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
-
# Moves a project item to "Started".
|
|
153
|
+
# Moves a project item to "Started". When +user+ is a support user, also stamps +metadata.responded_at+ on the
|
|
154
|
+
# issue via +Issue#mark_responded!+ (a no-op if it's already responded to / not a PMS issue).
|
|
154
155
|
#
|
|
155
156
|
# @param project_item [PlanMyStuff::ProjectItem]
|
|
156
157
|
# @param user [Object, nil] actor forwarded to the +pipeline_started.plan_my_stuff+ payload
|
|
@@ -162,6 +163,8 @@ module PlanMyStuff
|
|
|
162
163
|
status = resolve_status_name(PlanMyStuff::Pipeline::Status::STARTED)
|
|
163
164
|
result = project_item.move_to!(status)
|
|
164
165
|
|
|
166
|
+
project_item.issue.mark_responded!(user) if user.present?
|
|
167
|
+
|
|
165
168
|
instrument(PlanMyStuff::Pipeline::Status::STARTED, project_item, user: user)
|
|
166
169
|
result
|
|
167
170
|
end
|