plan_my_stuff 0.9.0 → 0.10.1

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.
@@ -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
@@ -3,8 +3,8 @@
3
3
  module PlanMyStuff
4
4
  module VERSION
5
5
  MAJOR = 0
6
- MINOR = 9
7
- TINY = 0
6
+ MINOR = 10
7
+ TINY = 1
8
8
 
9
9
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
10
10
  PRE = nil
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plan_my_stuff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brands Insurance
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-05-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -44,7 +43,6 @@ dependencies:
44
43
  - - "~>"
45
44
  - !ruby/object:Gem::Version
46
45
  version: 10.0.0
47
- description:
48
46
  email:
49
47
  - documents@brandsinsurance.com
50
48
  executables: []
@@ -113,6 +111,7 @@ files:
113
111
  - lib/plan_my_stuff/aws_sns_simulator.rb
114
112
  - lib/plan_my_stuff/base_metadata.rb
115
113
  - lib/plan_my_stuff/base_project.rb
114
+ - lib/plan_my_stuff/base_project_extractions/graphql_hydration.rb
116
115
  - lib/plan_my_stuff/base_project_item.rb
117
116
  - lib/plan_my_stuff/base_project_metadata.rb
118
117
  - lib/plan_my_stuff/cache.rb
@@ -125,6 +124,10 @@ files:
125
124
  - lib/plan_my_stuff/errors.rb
126
125
  - lib/plan_my_stuff/graphql/queries.rb
127
126
  - lib/plan_my_stuff/issue.rb
127
+ - lib/plan_my_stuff/issue_extractions/approvals.rb
128
+ - lib/plan_my_stuff/issue_extractions/links.rb
129
+ - lib/plan_my_stuff/issue_extractions/viewers.rb
130
+ - lib/plan_my_stuff/issue_extractions/waiting.rb
128
131
  - lib/plan_my_stuff/issue_metadata.rb
129
132
  - lib/plan_my_stuff/label.rb
130
133
  - lib/plan_my_stuff/link.rb
@@ -203,8 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
203
206
  - !ruby/object:Gem::Version
204
207
  version: '0'
205
208
  requirements: []
206
- rubygems_version: 3.5.11
207
- signing_key:
209
+ rubygems_version: 4.0.6
208
210
  specification_version: 4
209
211
  summary: Shared PMS
210
212
  test_files: []