plan_my_stuff 0.8.0 → 0.10.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 +31 -0
- data/CONFIGURATION.md +351 -0
- data/app/views/plan_my_stuff/issues/show.html.erb +1 -1
- data/app/views/plan_my_stuff/partials/_flash.html.erb +0 -1
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +1 -12
- data/lib/plan_my_stuff/base_project.rb +5 -176
- data/lib/plan_my_stuff/base_project_extractions/graphql_hydration.rb +184 -0
- data/lib/plan_my_stuff/base_project_item.rb +1 -0
- data/lib/plan_my_stuff/comment.rb +5 -3
- data/lib/plan_my_stuff/configuration.rb +3 -16
- data/lib/plan_my_stuff/issue.rb +15 -1082
- data/lib/plan_my_stuff/issue_extractions/approvals.rb +370 -0
- data/lib/plan_my_stuff/issue_extractions/links.rb +525 -0
- data/lib/plan_my_stuff/issue_extractions/viewers.rb +75 -0
- data/lib/plan_my_stuff/issue_extractions/waiting.rb +148 -0
- data/lib/plan_my_stuff/label.rb +4 -4
- data/lib/plan_my_stuff/version.rb +1 -1
- data/lib/tasks/plan_my_stuff.rake +2 -2
- metadata +8 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PlanMyStuff
|
|
4
|
+
module IssueExtractions
|
|
5
|
+
module Waiting
|
|
6
|
+
# Marks the issue as waiting on an end-user reply. Sets +metadata.waiting_on_user_at+ to now, (re)computes
|
|
7
|
+
# +metadata.next_reminder_at+, and adds the configured +waiting_on_user_label+ to the issue. Called from
|
|
8
|
+
# +Comment.create!+ when a support user posts a comment with +waiting_on_reply: true+, and from the
|
|
9
|
+
# +Issues::WaitingsController+ toggle.
|
|
10
|
+
#
|
|
11
|
+
# @param user [Object, nil] actor for the label notification event
|
|
12
|
+
#
|
|
13
|
+
# @return [self]
|
|
14
|
+
#
|
|
15
|
+
def enter_waiting_on_user!(user: nil)
|
|
16
|
+
now = Time.now.utc
|
|
17
|
+
label = PlanMyStuff.configuration.waiting_on_user_label
|
|
18
|
+
|
|
19
|
+
PlanMyStuff::Label.ensure!(repo: repo, name: label)
|
|
20
|
+
PlanMyStuff::Label.add!(issue: self, labels: [label], user: user) if labels.exclude?(label)
|
|
21
|
+
|
|
22
|
+
self.class.update!(
|
|
23
|
+
number: number,
|
|
24
|
+
repo: repo,
|
|
25
|
+
metadata: {
|
|
26
|
+
waiting_on_user_at: PlanMyStuff.format_time(now),
|
|
27
|
+
next_reminder_at: format_next_reminder_at(from: now),
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
reload
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Clears the waiting-on-user state: removes the label, clears +metadata.waiting_on_user_at+, and clears
|
|
34
|
+
# +metadata.next_reminder_at+ unless a waiting-on-approval timer is still active. No-ops if the issue is not
|
|
35
|
+
# currently waiting on a user reply.
|
|
36
|
+
#
|
|
37
|
+
# @return [self]
|
|
38
|
+
#
|
|
39
|
+
def clear_waiting_on_user!
|
|
40
|
+
label = PlanMyStuff.configuration.waiting_on_user_label
|
|
41
|
+
return self if metadata.waiting_on_user_at.nil? && labels.exclude?(label)
|
|
42
|
+
|
|
43
|
+
PlanMyStuff::Label.remove!(issue: self, labels: [label]) if labels.include?(label)
|
|
44
|
+
|
|
45
|
+
self.class.update!(
|
|
46
|
+
number: number,
|
|
47
|
+
repo: repo,
|
|
48
|
+
metadata: {
|
|
49
|
+
waiting_on_user_at: nil,
|
|
50
|
+
next_reminder_at:
|
|
51
|
+
metadata.waiting_on_approval_at ? PlanMyStuff.format_time(metadata.next_reminder_at) : nil,
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
reload
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Reopens an issue that was auto-closed by the inactivity sweep, clears +metadata.closed_by_inactivity+, and
|
|
58
|
+
# emits +plan_my_stuff.issue.reopened_by_reply+ carrying the reopening comment. Does not emit the regular
|
|
59
|
+
# +issue.reopened+ event \- subscribers that specifically care about this flow subscribe to the dedicated event.
|
|
60
|
+
#
|
|
61
|
+
# @param comment [PlanMyStuff::Comment] the reopening comment
|
|
62
|
+
# @param user [Object, nil] actor for the notification event
|
|
63
|
+
#
|
|
64
|
+
# @return [self]
|
|
65
|
+
#
|
|
66
|
+
def reopen_by_reply!(comment:, user: nil)
|
|
67
|
+
inactive_label = PlanMyStuff.configuration.user_inactive_label
|
|
68
|
+
PlanMyStuff::Label.remove!(issue: self, labels: [inactive_label]) if labels.include?(inactive_label)
|
|
69
|
+
|
|
70
|
+
self.class.update!(
|
|
71
|
+
number: number,
|
|
72
|
+
repo: repo,
|
|
73
|
+
state: :open,
|
|
74
|
+
metadata: { closed_by_inactivity: false },
|
|
75
|
+
)
|
|
76
|
+
reload
|
|
77
|
+
|
|
78
|
+
PlanMyStuff::Notifications.instrument(
|
|
79
|
+
'issue.reopened_by_reply',
|
|
80
|
+
self,
|
|
81
|
+
user: user,
|
|
82
|
+
comment: comment,
|
|
83
|
+
)
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# Formats the next reminder time as an ISO 8601 UTC string, using per-issue +metadata.reminder_days+ when set
|
|
90
|
+
# or +config.reminder_days+ otherwise. Returns +nil+ when the effective schedule is empty.
|
|
91
|
+
#
|
|
92
|
+
# @param from [Time] baseline timestamp
|
|
93
|
+
#
|
|
94
|
+
# @return [String, nil]
|
|
95
|
+
#
|
|
96
|
+
def format_next_reminder_at(from:)
|
|
97
|
+
days = metadata.reminder_days.presence || PlanMyStuff.configuration.reminder_days
|
|
98
|
+
return if days.empty?
|
|
99
|
+
|
|
100
|
+
PlanMyStuff.format_time(from + days.first.days)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# When an issue is transitioning from open to closed, strips both waiting labels from the outgoing labels
|
|
104
|
+
# array and clears the waiting-related timestamps on +metadata+ so a single save writes both state change and
|
|
105
|
+
# cleanup. No-op for any other transition.
|
|
106
|
+
#
|
|
107
|
+
# @param attrs [Hash] the kwargs hash being assembled for +Issue.update!+; mutated in place
|
|
108
|
+
#
|
|
109
|
+
# @return [void]
|
|
110
|
+
#
|
|
111
|
+
def clear_waiting_state_on_close(attrs)
|
|
112
|
+
return unless state_changed?
|
|
113
|
+
return unless state_was == 'open'
|
|
114
|
+
return unless state == 'closed'
|
|
115
|
+
|
|
116
|
+
return if metadata.waiting_on_user_at.blank? && metadata.waiting_on_approval_at.blank?
|
|
117
|
+
|
|
118
|
+
waiting_labels = [
|
|
119
|
+
PlanMyStuff.configuration.waiting_on_user_label,
|
|
120
|
+
PlanMyStuff.configuration.waiting_on_approval_label,
|
|
121
|
+
]
|
|
122
|
+
attrs[:labels] = Array.wrap(attrs[:labels]) - waiting_labels
|
|
123
|
+
|
|
124
|
+
metadata.waiting_on_user_at = nil
|
|
125
|
+
metadata.waiting_on_approval_at = nil
|
|
126
|
+
metadata.next_reminder_at = nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# When an inactivity-closed issue is being reopened, strips the +user_inactive_label+ from the outgoing labels
|
|
130
|
+
# and clears +metadata.closed_by_inactivity+ so the save writes both. No-op for any other transition or for
|
|
131
|
+
# reopens of non-inactive closes.
|
|
132
|
+
#
|
|
133
|
+
# @param attrs [Hash] the kwargs hash being assembled for +Issue.update!+; mutated in place
|
|
134
|
+
#
|
|
135
|
+
# @return [void]
|
|
136
|
+
#
|
|
137
|
+
def clear_inactivity_state_on_reopen(attrs)
|
|
138
|
+
return unless state_changed?
|
|
139
|
+
return unless state_was == 'closed'
|
|
140
|
+
return unless state == 'open'
|
|
141
|
+
return unless metadata.closed_by_inactivity
|
|
142
|
+
|
|
143
|
+
attrs[:labels] = Array.wrap(attrs[:labels]) - [PlanMyStuff.configuration.user_inactive_label]
|
|
144
|
+
metadata.closed_by_inactivity = false
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
data/lib/plan_my_stuff/label.rb
CHANGED
|
@@ -48,7 +48,7 @@ module PlanMyStuff
|
|
|
48
48
|
client = PlanMyStuff.client
|
|
49
49
|
client.rest(:label, repo, name)
|
|
50
50
|
rescue PlanMyStuff::APIError => e
|
|
51
|
-
raise unless e.status == 404
|
|
51
|
+
raise(e) unless e.status == 404
|
|
52
52
|
|
|
53
53
|
create_label!(client, repo, name, color, description)
|
|
54
54
|
end
|
|
@@ -62,11 +62,11 @@ module PlanMyStuff
|
|
|
62
62
|
#
|
|
63
63
|
# @return [Boolean]
|
|
64
64
|
#
|
|
65
|
-
def exists?(repo:, name:)
|
|
65
|
+
def exists?(repo:, name:, do_raise: true)
|
|
66
66
|
PlanMyStuff.client.rest(:label, repo, name)
|
|
67
67
|
true
|
|
68
68
|
rescue PlanMyStuff::APIError => e
|
|
69
|
-
raise
|
|
69
|
+
raise(e) if do_raise && e.status != 404
|
|
70
70
|
|
|
71
71
|
false
|
|
72
72
|
end
|
|
@@ -115,7 +115,7 @@ module PlanMyStuff
|
|
|
115
115
|
|
|
116
116
|
client.rest(:add_label, repo, name, color, **options)
|
|
117
117
|
rescue PlanMyStuff::APIError => e
|
|
118
|
-
raise unless e.status == 422
|
|
118
|
+
raise(e) unless e.status == 422
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
# Hydrates a Label from a GitHub API response.
|
|
@@ -46,7 +46,7 @@ namespace :plan_my_stuff do
|
|
|
46
46
|
url = ENV.fetch('URL') { raise(ArgumentError, 'URL env var is required') }
|
|
47
47
|
repo = ENV.fetch('REPO') do
|
|
48
48
|
PlanMyStuff.client.resolve_repo!
|
|
49
|
-
rescue
|
|
49
|
+
rescue PlanMyStuff::ConfigurationError, ArgumentError
|
|
50
50
|
raise(ArgumentError, 'REPO env var is required or configured (e.g. BrandsInsurance/PlanMyStuff)')
|
|
51
51
|
end
|
|
52
52
|
default_events = %w[pull_request issues]
|
|
@@ -94,7 +94,7 @@ namespace :plan_my_stuff do
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
desc 'Continuously poll org + repo hooks and auto-replay new deliveries ' \
|
|
97
|
-
'(ENDPOINT_URL=... [ORG_WEBHOOK_URL=...] [REPO_WEBHOOK_URL=... REPO=owner/name] [INTERVAL=
|
|
97
|
+
'(ENDPOINT_URL=... [ORG_WEBHOOK_URL=...] [REPO_WEBHOOK_URL=... REPO=owner/name] [INTERVAL=30]). ' \
|
|
98
98
|
'At least one of ORG_WEBHOOK_URL or REPO_WEBHOOK_URL is required (raises ArgumentError if both absent).'
|
|
99
99
|
task listen: :environment do
|
|
100
100
|
require 'plan_my_stuff/webhook_replayer'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: plan_my_stuff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brands Insurance
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04
|
|
11
|
+
date: 2026-05-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -52,6 +52,7 @@ extensions: []
|
|
|
52
52
|
extra_rdoc_files: []
|
|
53
53
|
files:
|
|
54
54
|
- CHANGELOG.md
|
|
55
|
+
- CONFIGURATION.md
|
|
55
56
|
- LICENSE
|
|
56
57
|
- README.md
|
|
57
58
|
- app/controllers/plan_my_stuff/application_controller.rb
|
|
@@ -112,6 +113,7 @@ files:
|
|
|
112
113
|
- lib/plan_my_stuff/aws_sns_simulator.rb
|
|
113
114
|
- lib/plan_my_stuff/base_metadata.rb
|
|
114
115
|
- lib/plan_my_stuff/base_project.rb
|
|
116
|
+
- lib/plan_my_stuff/base_project_extractions/graphql_hydration.rb
|
|
115
117
|
- lib/plan_my_stuff/base_project_item.rb
|
|
116
118
|
- lib/plan_my_stuff/base_project_metadata.rb
|
|
117
119
|
- lib/plan_my_stuff/cache.rb
|
|
@@ -124,6 +126,10 @@ files:
|
|
|
124
126
|
- lib/plan_my_stuff/errors.rb
|
|
125
127
|
- lib/plan_my_stuff/graphql/queries.rb
|
|
126
128
|
- lib/plan_my_stuff/issue.rb
|
|
129
|
+
- lib/plan_my_stuff/issue_extractions/approvals.rb
|
|
130
|
+
- lib/plan_my_stuff/issue_extractions/links.rb
|
|
131
|
+
- lib/plan_my_stuff/issue_extractions/viewers.rb
|
|
132
|
+
- lib/plan_my_stuff/issue_extractions/waiting.rb
|
|
127
133
|
- lib/plan_my_stuff/issue_metadata.rb
|
|
128
134
|
- lib/plan_my_stuff/label.rb
|
|
129
135
|
- lib/plan_my_stuff/link.rb
|