plan_my_stuff 0.7.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 +31 -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 +51 -2
- 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 +467 -333
- 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
|
@@ -5,7 +5,7 @@ require 'openssl'
|
|
|
5
5
|
module PlanMyStuff
|
|
6
6
|
module Webhooks
|
|
7
7
|
class GithubController < ActionController::API
|
|
8
|
-
before_action :verify_signature
|
|
8
|
+
before_action :verify_signature
|
|
9
9
|
|
|
10
10
|
# POST /webhooks/github
|
|
11
11
|
def create
|
|
@@ -29,7 +29,7 @@ module PlanMyStuff
|
|
|
29
29
|
#
|
|
30
30
|
# @return [void]
|
|
31
31
|
#
|
|
32
|
-
def verify_signature
|
|
32
|
+
def verify_signature
|
|
33
33
|
body = request.body.read
|
|
34
34
|
request.body.rewind
|
|
35
35
|
|
|
@@ -81,21 +81,15 @@ module PlanMyStuff
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
# Adds the issue to the pipeline project at "Started" the first
|
|
85
|
-
#
|
|
86
|
-
# ignored -- if the issue is already in the pipeline we do not
|
|
87
|
-
# touch its status.
|
|
84
|
+
# Adds the issue to the pipeline project at "Started" the first time it is assigned. Re-assigns and
|
|
85
|
+
# additional assignees are ignored -- if the issue is already in the pipeline we do not touch its status.
|
|
88
86
|
#
|
|
89
87
|
# Skipped paths (no project item is created):
|
|
90
|
-
# - Issue is closed (assignment changes on a closed issue
|
|
91
|
-
#
|
|
92
|
-
# - Issue has pending approvals (creating + +take!+ would
|
|
93
|
-
# either orphan an item or 500 on the approval guard)
|
|
88
|
+
# - Issue is closed (assignment changes on a closed issue must not alter the pipeline)
|
|
89
|
+
# - Issue has pending approvals (creating + +take!+ would either orphan an item or 500 on the approval guard)
|
|
94
90
|
#
|
|
95
|
-
# GitHub already records the issue assignment (that's what
|
|
96
|
-
#
|
|
97
|
-
# on the project item -- that would clobber co-assignees on
|
|
98
|
-
# the underlying issue.
|
|
91
|
+
# GitHub already records the issue assignment (that's what triggered this webhook), so the gem does not
|
|
92
|
+
# call +assign!+ on the project item -- that would clobber co-assignees on the underlying issue.
|
|
99
93
|
#
|
|
100
94
|
# @return [void]
|
|
101
95
|
#
|
|
@@ -123,21 +117,18 @@ module PlanMyStuff
|
|
|
123
117
|
return
|
|
124
118
|
end
|
|
125
119
|
|
|
126
|
-
number = PlanMyStuff::Pipeline.resolve_pipeline_project_number
|
|
120
|
+
number = PlanMyStuff::Pipeline.resolve_pipeline_project_number!
|
|
127
121
|
project_item = PlanMyStuff::ProjectItem.create!(issue, project_number: number)
|
|
128
122
|
PlanMyStuff::Pipeline.take!(project_item)
|
|
129
123
|
end
|
|
130
124
|
|
|
131
|
-
# Removes the issue from the pipeline project when the LAST
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
# the pipeline at all, also a no-op (logged at info).
|
|
125
|
+
# Removes the issue from the pipeline project when the LAST assignee is removed. If any assignees remain,
|
|
126
|
+
# the webhook is a no-op (only one of N was removed). If the issue isn't in the pipeline at all, also a
|
|
127
|
+
# no-op (logged at info).
|
|
135
128
|
#
|
|
136
|
-
# Closed issues are skipped: assignment changes on a closed
|
|
137
|
-
#
|
|
138
|
-
# release
|
|
139
|
-
# Completed) are also locked -- once on the release path an
|
|
140
|
-
# item should not come off via webhook.
|
|
129
|
+
# Closed issues are skipped: assignment changes on a closed issue must not alter the pipeline. Items
|
|
130
|
+
# already on the release cycle (Ready for Release, Release in Progress, Completed) are also locked --
|
|
131
|
+
# once on the release path an item should not come off via webhook.
|
|
141
132
|
#
|
|
142
133
|
# @return [void]
|
|
143
134
|
#
|
|
@@ -145,7 +136,7 @@ module PlanMyStuff
|
|
|
145
136
|
return if issue_params[:state] == 'closed'
|
|
146
137
|
|
|
147
138
|
remaining_assignees = Array.wrap(issue_params[:assignees])
|
|
148
|
-
return if remaining_assignees.
|
|
139
|
+
return if remaining_assignees.present?
|
|
149
140
|
|
|
150
141
|
issue_number = issue_params.fetch(:number)
|
|
151
142
|
project_item = PlanMyStuff::Pipeline::IssueLinker.find_project_item(issue_number)
|
|
@@ -169,16 +160,13 @@ module PlanMyStuff
|
|
|
169
160
|
PlanMyStuff::Pipeline.remove!(project_item)
|
|
170
161
|
end
|
|
171
162
|
|
|
172
|
-
# Handles GitHub's +projects_v2_item+ webhook event. Mirrors a dev
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
# +plan_my_stuff.pipeline.started+ event fires.
|
|
163
|
+
# Handles GitHub's +projects_v2_item+ webhook event. Mirrors a dev dragging an item from "Submitted" to
|
|
164
|
+
# "Started" on the project board into a +Pipeline.take!+ call so the +plan_my_stuff.pipeline.started+
|
|
165
|
+
# event fires.
|
|
176
166
|
#
|
|
177
|
-
# Only acts on +action: 'edited'+ where the Status single-select
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
# is already "Started" (loop guard from +move_to!+ triggering
|
|
181
|
-
# another webhook), or when the item cannot be located on the
|
|
167
|
+
# Only acts on +action: 'edited'+ where the Status single-select field changed on the pipeline project.
|
|
168
|
+
# No-ops when the new status is anything other than "Started", when the +from+ status is already "Started"
|
|
169
|
+
# (loop guard from +move_to!+ triggering another webhook), or when the item cannot be located on the
|
|
182
170
|
# pipeline project.
|
|
183
171
|
#
|
|
184
172
|
# @return [void]
|
|
@@ -189,7 +177,7 @@ module PlanMyStuff
|
|
|
189
177
|
item_project_node_id = payload_params.dig(:projects_v2_item, :project_node_id)
|
|
190
178
|
return if item_project_node_id.blank?
|
|
191
179
|
|
|
192
|
-
pipeline_number = PlanMyStuff::Pipeline.resolve_pipeline_project_number
|
|
180
|
+
pipeline_number = PlanMyStuff::Pipeline.resolve_pipeline_project_number!
|
|
193
181
|
project = PlanMyStuff::Project.find(pipeline_number)
|
|
194
182
|
return unless project.id == item_project_node_id
|
|
195
183
|
|
|
@@ -231,11 +219,9 @@ module PlanMyStuff
|
|
|
231
219
|
def handle_pull_request
|
|
232
220
|
action = payload_params.fetch(:action)
|
|
233
221
|
|
|
234
|
-
# Only PRs targeting +main+ drive pipeline transitions on
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
# back to "Started". The +closed+ branch handles its own
|
|
238
|
-
# base-ref routing.
|
|
222
|
+
# Only PRs targeting +main+ drive pipeline transitions on open/draft/reopen. PRs into +production+ are
|
|
223
|
+
# deploy PRs (often auto-created as drafts) and must not bump items back to "Started". The +closed+
|
|
224
|
+
# branch handles its own base-ref routing.
|
|
239
225
|
if %w[opened ready_for_review reopened converted_to_draft].include?(action)
|
|
240
226
|
base_ref = pull_request_params.dig(:base, :ref)
|
|
241
227
|
return unless base_ref == PlanMyStuff.configuration.main_branch
|
|
@@ -253,11 +239,9 @@ module PlanMyStuff
|
|
|
253
239
|
end
|
|
254
240
|
end
|
|
255
241
|
|
|
256
|
-
# Opening a PR as a draft is a soft "I've started working on this"
|
|
257
|
-
#
|
|
258
|
-
# pipeline
|
|
259
|
-
# assign the PR author. Already-in-pipeline items are NOT moved
|
|
260
|
-
# (a draft open is not a status change).
|
|
242
|
+
# Opening a PR as a draft is a soft "I've started working on this" signal. For each linked issue, when
|
|
243
|
+
# the issue is not yet in the pipeline, add it at "Started"; when the issue has no assignees, assign the
|
|
244
|
+
# PR author. Already-in-pipeline items are NOT moved (a draft open is not a status change).
|
|
261
245
|
#
|
|
262
246
|
# @return [void]
|
|
263
247
|
#
|
|
@@ -283,9 +267,8 @@ module PlanMyStuff
|
|
|
283
267
|
end
|
|
284
268
|
end
|
|
285
269
|
|
|
286
|
-
# Adds the issue to the pipeline at "Started". Skipped when the
|
|
287
|
-
#
|
|
288
|
-
# otherwise leave an orphan project item behind).
|
|
270
|
+
# Adds the issue to the pipeline at "Started". Skipped when the issue has pending approvals
|
|
271
|
+
# (Pipeline.take!'s guard would otherwise leave an orphan project item behind).
|
|
289
272
|
#
|
|
290
273
|
# @param issue [PlanMyStuff::Issue]
|
|
291
274
|
#
|
|
@@ -297,7 +280,7 @@ module PlanMyStuff
|
|
|
297
280
|
return
|
|
298
281
|
end
|
|
299
282
|
|
|
300
|
-
number = PlanMyStuff::Pipeline.resolve_pipeline_project_number
|
|
283
|
+
number = PlanMyStuff::Pipeline.resolve_pipeline_project_number!
|
|
301
284
|
project_item = PlanMyStuff::ProjectItem.create!(issue, project_number: number)
|
|
302
285
|
PlanMyStuff::Pipeline.take!(project_item)
|
|
303
286
|
end
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
# Base class for all PMS gem jobs. Subclasses +::ActiveJob::Base+ so
|
|
5
|
-
#
|
|
6
|
-
# +ApplicationJob+ constant.
|
|
4
|
+
# Base class for all PMS gem jobs. Subclasses +::ActiveJob::Base+ so the gem doesn't depend on the consuming app
|
|
5
|
+
# having an +ApplicationJob+ constant.
|
|
7
6
|
class ApplicationJob < ::ActiveJob::Base
|
|
8
7
|
end
|
|
9
8
|
end
|
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
# Daily-cadence sweep job for reminder dispatch + inactivity auto-close.
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# setup. Queue-adapter-agnostic: runs through ActiveJob's
|
|
8
|
-
# +set(wait_until:).perform_later+ so any backend works.
|
|
4
|
+
# Daily-cadence sweep job for reminder dispatch + inactivity auto-close. Consuming apps enqueue it once; the job
|
|
5
|
+
# self-requeues after each perform so the schedule persists without requiring a cron/whenever setup.
|
|
6
|
+
# Queue-adapter-agnostic: runs through ActiveJob's +set(wait_until:).perform_later+ so any backend works.
|
|
9
7
|
class RemindersSweepJob < PlanMyStuff::ApplicationJob
|
|
10
8
|
queue_as :default
|
|
11
9
|
|
|
12
|
-
# Only try once per enqueue. Without this, Delayed-style adapters
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# +perform_later+; if it fails, the follow-up scheduled in +ensure+
|
|
17
|
-
# takes over tomorrow.
|
|
10
|
+
# Only try once per enqueue. Without this, Delayed-style adapters retry up to 25 times on error and our
|
|
11
|
+
# +around_perform+ +ensure+ re-enqueues a follow-up run on every attempt, causing geometric duplicate
|
|
12
|
+
# pile-up. With +attempts: 1+, exactly one perform per +perform_later+; if it fails, the follow-up scheduled
|
|
13
|
+
# in +ensure+ takes over tomorrow.
|
|
18
14
|
retry_on StandardError, attempts: 1
|
|
19
15
|
|
|
20
16
|
around_perform :requeue_for_next_run
|
|
21
17
|
|
|
22
18
|
class << self
|
|
23
|
-
# Next sweep time. Default: 6:30am Eastern tomorrow (today if the
|
|
24
|
-
#
|
|
25
|
-
# cadence override this on a subclass.
|
|
19
|
+
# Next sweep time. Default: 6:30am Eastern tomorrow (today if the current time is before 6:30am ET). Apps
|
|
20
|
+
# wanting a different cadence override this on a subclass.
|
|
26
21
|
#
|
|
27
22
|
# @return [Time] UTC
|
|
28
23
|
#
|
|
@@ -33,9 +28,8 @@ module PlanMyStuff
|
|
|
33
28
|
target.utc
|
|
34
29
|
end
|
|
35
30
|
|
|
36
|
-
# Schedules a sweep for +next_run+. Used by the after-perform
|
|
37
|
-
#
|
|
38
|
-
# both kick off runs on the same cadence (instead of "now").
|
|
31
|
+
# Schedules a sweep for +next_run+. Used by the after-perform self-requeue and the
|
|
32
|
+
# +plan_my_stuff:reminders:sweep+ rake task so both kick off runs on the same cadence (instead of "now").
|
|
39
33
|
#
|
|
40
34
|
# @param repo [Symbol, String]
|
|
41
35
|
#
|
|
@@ -54,15 +48,14 @@ module PlanMyStuff
|
|
|
54
48
|
@repo_arg = repo
|
|
55
49
|
PlanMyStuff::Reminders::Sweep.new(repo: repo).call
|
|
56
50
|
PlanMyStuff::Archive::Sweep.new(repo: repo).call
|
|
57
|
-
PlanMyStuff::Pipeline::CompletedSweep.perform
|
|
51
|
+
PlanMyStuff::Pipeline::CompletedSweep.perform!
|
|
58
52
|
end
|
|
59
53
|
|
|
60
54
|
private
|
|
61
55
|
|
|
62
|
-
# Runs inside +around_perform+. Re-enqueues the next run in an
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# error or direct +.perform+ call with missing kwargs).
|
|
56
|
+
# Runs inside +around_perform+. Re-enqueues the next run in an +ensure+ so a perform error still schedules
|
|
57
|
+
# the next one. Skips requeue when perform never captured a repo arg (deserialization error or direct
|
|
58
|
+
# +.perform+ call with missing kwargs).
|
|
66
59
|
#
|
|
67
60
|
# @return [void]
|
|
68
61
|
#
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
<h1>Edit Issue #<%= @issue.number %></h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<p style="color: red;"><%= flash[:error] %></p>
|
|
5
|
-
<% end %>
|
|
3
|
+
<%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
|
|
6
4
|
|
|
7
|
-
<%= render({ partial: 'plan_my_stuff/issues/partials/form', locals: { issue: @issue
|
|
5
|
+
<%= render({ partial: 'plan_my_stuff/issues/partials/form', locals: { issue: @issue } }) %>
|
|
8
6
|
|
|
9
7
|
<% if @support_user %>
|
|
10
8
|
<hr>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<h1>Issues</h1>
|
|
2
2
|
|
|
3
|
-
<% if @issues.
|
|
3
|
+
<% if @issues.present? %>
|
|
4
4
|
<table>
|
|
5
5
|
<thead>
|
|
6
6
|
<tr>
|
|
@@ -34,4 +34,4 @@
|
|
|
34
34
|
<p>No issues found.</p>
|
|
35
35
|
<% end %>
|
|
36
36
|
<br>
|
|
37
|
-
<%= link_to
|
|
37
|
+
<%= link_to('New Issue', plan_my_stuff.new_issue_path(repo: @repo)) %>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<h1>New Issue</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<p style="color: red;"><%= flash[:error] %></p>
|
|
5
|
-
<% end %>
|
|
3
|
+
<%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
|
|
6
4
|
|
|
7
|
-
<%= render({ partial: 'plan_my_stuff/issues/partials/form', locals: { issue: @issue
|
|
5
|
+
<%= render({ partial: 'plan_my_stuff/issues/partials/form', locals: { issue: @issue } }) %>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
<%# locals: (issue:, support_user:, current_user_id_local:) %>
|
|
1
2
|
<%
|
|
2
3
|
approvers = issue.approvers
|
|
3
4
|
pending_count = issue.pending_approvals.size
|
|
5
|
+
rejected_count = issue.rejected_approvals.size
|
|
4
6
|
current_user_id = current_user_id_local
|
|
5
7
|
%>
|
|
6
8
|
|
|
@@ -12,7 +14,12 @@
|
|
|
12
14
|
<% if issue.fully_approved? %>
|
|
13
15
|
<p><strong>Fully approved</strong></p>
|
|
14
16
|
<% else %>
|
|
15
|
-
<p
|
|
17
|
+
<p>
|
|
18
|
+
<strong><%= pending_count %> of <%= approvers.size %> approval(s) pending</strong>
|
|
19
|
+
<% if rejected_count.positive? %>
|
|
20
|
+
(<%= rejected_count %> rejected)
|
|
21
|
+
<% end %>
|
|
22
|
+
</p>
|
|
16
23
|
<% end %>
|
|
17
24
|
|
|
18
25
|
<ul>
|
|
@@ -24,6 +31,11 @@
|
|
|
24
31
|
<% if approval.approved_at %>
|
|
25
32
|
at <%= approval.approved_at.iso8601 %>
|
|
26
33
|
<% end %>
|
|
34
|
+
<% elsif approval.rejected? %>
|
|
35
|
+
rejected
|
|
36
|
+
<% if approval.rejected_at %>
|
|
37
|
+
at <%= approval.rejected_at.iso8601 %>
|
|
38
|
+
<% end %>
|
|
27
39
|
<% else %>
|
|
28
40
|
pending
|
|
29
41
|
<% end %>
|
|
@@ -38,9 +50,18 @@
|
|
|
38
50
|
form: { style: 'display:inline' },
|
|
39
51
|
)
|
|
40
52
|
%>
|
|
53
|
+
<%=
|
|
54
|
+
button_to(
|
|
55
|
+
'Reject',
|
|
56
|
+
plan_my_stuff.issue_approval_path(issue.number, approval.user_id, repo: issue.repo.full_name),
|
|
57
|
+
method: :patch,
|
|
58
|
+
params: { approval: { status: 'rejected' } },
|
|
59
|
+
form: { style: 'display:inline' },
|
|
60
|
+
)
|
|
61
|
+
%>
|
|
41
62
|
<% end %>
|
|
42
63
|
|
|
43
|
-
<% if approval.
|
|
64
|
+
<% if !approval.pending? && (approval.user_id == current_user_id || support_user) %>
|
|
44
65
|
<%=
|
|
45
66
|
button_to(
|
|
46
67
|
'Revoke',
|
|
@@ -1,14 +1,57 @@
|
|
|
1
|
+
<%# locals: (issue:, support_user:) %>
|
|
1
2
|
<%
|
|
2
3
|
# read_only sections have no add form; remove_only sections allow X but
|
|
3
4
|
# no add (blocking is created from the blocked side; duplicate_of is
|
|
4
5
|
# created via mark_duplicate! and cleared by reopening on GitHub).
|
|
5
6
|
sections = [
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
{
|
|
8
|
+
type: 'parent',
|
|
9
|
+
label: 'Parent',
|
|
10
|
+
single: true,
|
|
11
|
+
add_form: true,
|
|
12
|
+
removable: true,
|
|
13
|
+
targets: [issue.parent].compact
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type: 'sub_ticket',
|
|
17
|
+
label: 'Sub-tickets',
|
|
18
|
+
single: false,
|
|
19
|
+
add_form: true,
|
|
20
|
+
removable: true,
|
|
21
|
+
targets: issue.sub_tickets
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'blocked_by',
|
|
25
|
+
label: 'Blocked by',
|
|
26
|
+
single: false,
|
|
27
|
+
add_form: true,
|
|
28
|
+
removable: true,
|
|
29
|
+
targets: issue.blocked_by
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'blocking',
|
|
33
|
+
label: 'Blocking',
|
|
34
|
+
single: false,
|
|
35
|
+
add_form: false,
|
|
36
|
+
removable: false,
|
|
37
|
+
targets: issue.blocking
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: 'related',
|
|
41
|
+
label: 'Related',
|
|
42
|
+
single: false,
|
|
43
|
+
add_form: true,
|
|
44
|
+
removable: true,
|
|
45
|
+
targets: issue.related
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'duplicate_of',
|
|
49
|
+
label: 'Duplicate of',
|
|
50
|
+
single: true,
|
|
51
|
+
add_form: false,
|
|
52
|
+
removable: false,
|
|
53
|
+
targets: [issue.duplicate_of].compact
|
|
54
|
+
},
|
|
12
55
|
]
|
|
13
56
|
visible_sections = support_user ? sections : sections.select { |s| s[:type] == 'related' }
|
|
14
57
|
%>
|
|
@@ -18,7 +61,7 @@
|
|
|
18
61
|
<% visible_sections.each do |section| %>
|
|
19
62
|
<div>
|
|
20
63
|
<h4><%= section[:label] %></h4>
|
|
21
|
-
<% if section[:targets].
|
|
64
|
+
<% if section[:targets].present? %>
|
|
22
65
|
<ul>
|
|
23
66
|
<% section[:targets].each do |target| %>
|
|
24
67
|
<li>
|
|
@@ -17,9 +17,12 @@
|
|
|
17
17
|
<%= button_to('Mark waiting', plan_my_stuff.issue_waiting_path(@issue.number, repo: @issue.repo.full_name), method: :post) %>
|
|
18
18
|
<% end %>
|
|
19
19
|
<% end %>
|
|
20
|
-
<% if @support_user && @pipeline_enabled && @pipeline_item.nil? %>
|
|
20
|
+
<% if @support_user && @pipeline_enabled && @pipeline_item.nil? && @issue.assignees.blank? %>
|
|
21
21
|
<%= button_to('Take', plan_my_stuff.issue_take_path(@issue.number, repo: @issue.repo.full_name), method: :post) %>
|
|
22
22
|
<% end %>
|
|
23
|
+
<% if @support_user && @pipeline_enabled && @current_user_login.present? && @issue.assignees.include?(@current_user_login) %>
|
|
24
|
+
<%= button_to('Release', plan_my_stuff.issue_take_path(@issue.number, repo: @issue.repo.full_name), method: :delete) %>
|
|
25
|
+
<% end %>
|
|
23
26
|
<% if @support_user %>
|
|
24
27
|
<%= link_to('Start Testing Project', plan_my_stuff.new_testing_project_path(subject_url: @issue.html_url)) %>
|
|
25
28
|
<% end %>
|
|
@@ -67,7 +70,7 @@
|
|
|
67
70
|
|
|
68
71
|
<h2>Comments (<%= @comments.size %>)</h2>
|
|
69
72
|
|
|
70
|
-
<% if @comments.
|
|
73
|
+
<% if @comments.present? %>
|
|
71
74
|
<% @comments.each do |comment| %>
|
|
72
75
|
<div>
|
|
73
76
|
<%= PlanMyStuff::Markdown.render(comment.body || '').html_safe %>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<h1>Edit Project #<%= @project.number %></h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<p style="color: red;"><%= flash[:error] %></p>
|
|
5
|
-
<% end %>
|
|
3
|
+
<%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
|
|
6
4
|
|
|
7
5
|
<%= render({ partial: 'plan_my_stuff/projects/partials/form', locals: { project: @project } }) %>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<h1>New Project</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<p style="color: red;"><%= flash[:error] %></p>
|
|
5
|
-
<% end %>
|
|
3
|
+
<%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
|
|
6
4
|
|
|
7
5
|
<%= render({ partial: 'plan_my_stuff/projects/partials/form', locals: { project: @project } }) %>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<% end %>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
<% if @statuses.
|
|
10
|
+
<% if @statuses.present? %>
|
|
11
11
|
<table>
|
|
12
12
|
<thead>
|
|
13
13
|
<tr>
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
<% end %>
|
|
44
44
|
|
|
45
45
|
<% assignees = item.field_values['Assignees'] || [] %>
|
|
46
|
-
<% if assignees.
|
|
46
|
+
<% if assignees.present? %>
|
|
47
47
|
<div>
|
|
48
48
|
<% assignees.each do |username| %>
|
|
49
49
|
<span>
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
) do |form|
|
|
58
58
|
%>
|
|
59
59
|
<%= form.hidden_field(:username, value: username) %>
|
|
60
|
-
<%= form.submit('
|
|
60
|
+
<%= form.submit('x', title: "Unassign #{username}") %>
|
|
61
61
|
<% end %>
|
|
62
62
|
</span>
|
|
63
63
|
<% end %>
|
|
@@ -74,6 +74,16 @@
|
|
|
74
74
|
<%= form.text_field(:assignee, placeholder: 'GitHub username') %>
|
|
75
75
|
<%= form.submit('Assign') %>
|
|
76
76
|
<% end %>
|
|
77
|
+
|
|
78
|
+
<%=
|
|
79
|
+
form_with(
|
|
80
|
+
url: plan_my_stuff.project_item_path(@project.number, item.id),
|
|
81
|
+
method: :delete,
|
|
82
|
+
local: true,
|
|
83
|
+
) do |form|
|
|
84
|
+
%>
|
|
85
|
+
<%= form.submit('Remove from project') %>
|
|
86
|
+
<% end %>
|
|
77
87
|
</div>
|
|
78
88
|
<% end %>
|
|
79
89
|
<% if items.empty? %>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
<h1>Fail Item</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<p style="color: red;"><%= flash[:error] %></p>
|
|
5
|
-
<% end %>
|
|
3
|
+
<%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
|
|
6
4
|
|
|
7
5
|
<%= form_with(
|
|
8
6
|
url: plan_my_stuff.testing_project_item_result_path(@project_number, @item_id),
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<h1>Edit Testing Project #<%= @project.number %></h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<p style="color: red;"><%= flash[:error] %></p>
|
|
5
|
-
<% end %>
|
|
3
|
+
<%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
|
|
6
4
|
|
|
7
5
|
<%= render({ partial: 'plan_my_stuff/testing_projects/partials/form', locals: { project: @project } }) %>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<h1>New Testing Project</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
<p style="color: red;"><%= flash[:error] %></p>
|
|
5
|
-
<% end %>
|
|
3
|
+
<%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
|
|
6
4
|
|
|
7
5
|
<%= render({ partial: 'plan_my_stuff/testing_projects/partials/form', locals: { project: @project } }) %>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<%# locals: (project:) %>
|
|
1
2
|
<%
|
|
2
3
|
persisted = project.persisted?
|
|
3
4
|
url =
|
|
@@ -20,17 +21,17 @@
|
|
|
20
21
|
|
|
21
22
|
<div>
|
|
22
23
|
<%= form.label(:subject_urls, 'Subject URLs (one per line)') %>
|
|
23
|
-
<%= form.text_area(:subject_urls, rows: 4, value: project.metadata
|
|
24
|
+
<%= form.text_area(:subject_urls, rows: 4, value: Array.wrap(project.metadata&.subject_urls).join("\n")) %>
|
|
24
25
|
</div>
|
|
25
26
|
|
|
26
27
|
<div>
|
|
27
28
|
<%= form.label(:due_date, 'Due Date') %>
|
|
28
|
-
<%= form.date_field(:due_date, value: project.metadata
|
|
29
|
+
<%= form.date_field(:due_date, value: project.metadata&.due_date&.strftime('%F')) %>
|
|
29
30
|
</div>
|
|
30
31
|
|
|
31
32
|
<div>
|
|
32
33
|
<%= form.label(:deadline_miss_reason, 'Deadline Miss Reason') %>
|
|
33
|
-
<%= form.text_field(:deadline_miss_reason, value: project.metadata
|
|
34
|
+
<%= form.text_field(:deadline_miss_reason, value: project.metadata&.deadline_miss_reason) %>
|
|
34
35
|
</div>
|
|
35
36
|
|
|
36
37
|
<div>
|