plan_my_stuff 0.18.0 → 0.20.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bded03d75c7bd996c62c647b038a588a4286628e7f041e385d4464ea87955505
4
- data.tar.gz: 5b778d271d85a1ad5681d8617dec41875d5664d0c3ea861ab9421bef962de4d2
3
+ metadata.gz: 06744d7c7bc63206522ce6d7c9176eeb8e93fabb4de903d7df32b17cfae6948d
4
+ data.tar.gz: 2338f2f85989b2ee3b29a48c114a82d36c8105c13e4caf19b2601ff02cbf063c
5
5
  SHA512:
6
- metadata.gz: db446cfa7623c4091f67cecd89ab7dfe84207237e601fd53f4a73401fc02d2019072801c326d281febb54373817cb87d35c60e2929a2ec425f1aca3e7623ef49
7
- data.tar.gz: b7f4328f697271829202ee201f4c05a9e1553bc95d50b721cfc3132462c0401428b87e22bd79cdeb4962e249bea991b3f5d39cab9cb1a3c048ef13a647e31312
6
+ metadata.gz: dd143db048f77a46fe7875b1592b9ff1dc26e6ddac13af60c177ae977f799606d172a23ca861222a1a133d53fb91d16be19dd40d8f7027b680316276d99088f0
7
+ data.tar.gz: 607957cf66e887c31bb6ab3f9a93726da0cf853cb3d43e8d34857b0dd789981788de4f0c8c6e076ad5236141732867ed9641971dcc683df5e4683e6b726542f3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Breaking
6
+
7
+ - `priority_list` and `priority_list_priority` keys removed from `IssueMetadata` serialization. Existing issues
8
+ migrate to the GitHub Issue Field equivalents (`Priority List`, `Priority List Priority`) on their next save.
9
+
10
+ ### Added
11
+
12
+ - `Issue#priority_list?` and `Issue#priority_list_priority` read the corresponding issue field values directly.
13
+ - `Issue.priority_list(repo:, state:, labels:, page:, per_page:)` convenience method delegates to
14
+ `Issue.list(priority_list: true, ...)`.
15
+ - `Issue.list` and `Issue.count` accept `priority_list:` for server-side filtering via the GitHub issue fields
16
+ API (passes `priority_list: false` raises `ArgumentError` -- the API has no negation qualifier).
17
+
18
+ ### Deprecated
19
+
20
+ - `IssueMetadata#priority_list`, `#priority_list=`, `#priority_list?`, `#priority_list_priority`, and
21
+ `#priority_list_priority=` emit deprecation warnings on each read/write. Legacy writes forward to the issue
22
+ field on `Issue#save!` / `#update!` / `Issue.create!`. The accessors will be removed in 1.0.0.
23
+
24
+ ## 0.19.0
25
+
26
+ ### Added
27
+
28
+ - Every controller action now yields its primary object to a block when subclassed, so consumers can do
29
+ `super do |obj| ... end` to wedge in audit-log / instrumentation without rewriting the action. Webhook
30
+ controllers yield `(payload, result)`. See [CONFIGURATION.md](CONFIGURATION.md).
31
+
3
32
  ## 0.18.0
4
33
 
5
34
  ### Breaking
data/CONFIGURATION.md CHANGED
@@ -374,6 +374,54 @@ Controllable keys (with gem defaults):
374
374
  | `:'webhooks/github'` | `plan_my_stuff/webhooks/github` |
375
375
  | `:'webhooks/aws'` | `plan_my_stuff/webhooks/aws` |
376
376
 
377
+ ### Customizing per-action behavior
378
+
379
+ Every mounted route resolves its controller through `config.controller_for(key)`. Subclass a gem controller in your
380
+ own app and register it to wedge in `before_action`s, authentication, or response tweaks - no monkey patching:
381
+
382
+ ```ruby
383
+ # app/controllers/my_app/issues_controller.rb
384
+ class MyApp::IssuesController < PMS::IssuesController
385
+ before_action :authenticate_user!
386
+ before_action :authorize_ticket_access
387
+ end
388
+ ```
389
+
390
+ ```ruby
391
+ # config/initializers/plan_my_stuff.rb
392
+ PlanMyStuff.configure do |config|
393
+ config.controllers[:issues] = 'my_app/issues'
394
+ end
395
+ ```
396
+
397
+ For per-action side effects (audit log, metrics, notifications) without rewriting the action, yielding actions
398
+ pass their primary object to an optional block on the happy path. Call `super do |obj| ... end` from a subclass:
399
+
400
+ ```ruby
401
+ class MyApp::IssuesController < PMS::IssuesController
402
+ def create
403
+ super do |issue|
404
+ AuditLog.record(actor: current_user, action: :issue_created, target: issue)
405
+ end
406
+ end
407
+
408
+ def update
409
+ super do |issue|
410
+ AuditLog.record(actor: current_user, action: :issue_updated, target: issue)
411
+ end
412
+ end
413
+ end
414
+ ```
415
+
416
+ Contract:
417
+
418
+ - The yield fires on the happy path, after the model load / write succeeds and before the gem's default
419
+ `flash[:success]` + `redirect_to`. Error branches, `not_found`, and authorization redirects do not yield.
420
+ - Read actions (`index`, `show`, `new`, `edit`) yield before the implicit render; `index` yields the collection,
421
+ the rest yield the single object.
422
+ - If your block calls `render` or `redirect_to`, the gem's default response is skipped (the action checks
423
+ `performed?`), so you can fully replace the response from the block.
424
+
377
425
  ### Parent controller
378
426
 
379
427
  | Option | Type | Default | Description |
data/README.md CHANGED
@@ -787,34 +787,6 @@ All routes are under the engine's mount point. Each group can be disabled via `c
787
787
 
788
788
  ## Controller overrides
789
789
 
790
- Every mounted route resolves its controller through `config.controller_for(key)`, which looks up `config.controllers[key]` and falls back to the gem default. Subclass a gem controller in your own app and register it to wedge in before_actions, authentication, or response tweaks — no monkey patching.
791
-
792
- ```ruby
793
- # app/controllers/my_app/issues_controller.rb
794
- class MyApp::IssuesController < PMS::IssuesController
795
- before_action :authenticate_user!
796
- before_action :authorize_ticket_access
797
- end
798
- ```
799
-
800
- ```ruby
801
- # config/initializers/plan_my_stuff.rb
802
- PlanMyStuff.configure do |config|
803
- config.controllers['issues'] = 'my_app/issues'
804
- end
805
- ```
806
-
807
- Overridable keys (see `PMS::Configuration::DEFAULT_CONTROLLERS`):
808
-
809
- ```text
810
- issues issues/closures
811
- comments issues/viewers
812
- labels issues/takes
813
- projects issues/waitings
814
- project_items issues/links
815
- testing_projects issues/approvals
816
- testing_project_items project_items/statuses
817
- webhooks/github project_items/assignments
818
- webhooks/aws testing_project_items/results
819
- ```
790
+ See [CONFIGURATION.md](CONFIGURATION.md#controller-overrides) for the full walkthrough including subclassing,
791
+ per-route registration, and the per-action block hook.
820
792
 
@@ -6,7 +6,7 @@ module PlanMyStuff
6
6
  def create
7
7
  @issue = PlanMyStuff::Issue.find(params[:issue_id])
8
8
 
9
- PlanMyStuff::Comment.create!(
9
+ comment = PlanMyStuff::Comment.create!(
10
10
  issue: @issue,
11
11
  body: comment_params[:body],
12
12
  user: pms_current_user,
@@ -14,6 +14,9 @@ module PlanMyStuff
14
14
  waiting_on_reply: comment_params[:waiting_on_reply] == '1',
15
15
  )
16
16
 
17
+ yield(comment) if block_given?
18
+ return if performed?
19
+
17
20
  flash[:success] = 'Comment was successfully created.'
18
21
  redirect_to(plan_my_stuff.issue_path(@issue))
19
22
  rescue PlanMyStuff::LockedIssueError => e
@@ -27,9 +30,14 @@ module PlanMyStuff
27
30
  load_comment
28
31
  return unless @comment
29
32
  return redirect_to_issue if issue_body_comment?
30
- return if can_edit?(@comment)
31
33
 
32
- redirect_to_unauthorized(plan_my_stuff.issue_path(@issue))
34
+ unless can_edit?(@comment)
35
+ redirect_to_unauthorized(plan_my_stuff.issue_path(@issue))
36
+
37
+ return
38
+ end
39
+
40
+ yield(@comment) if block_given?
33
41
  end
34
42
 
35
43
  # PATCH/PUT /issues/:issue_id/comments/:id
@@ -49,6 +57,9 @@ module PlanMyStuff
49
57
 
50
58
  @comment.update!(**update_attrs, user: pms_current_user)
51
59
 
60
+ yield(@comment) if block_given?
61
+ return if performed?
62
+
52
63
  flash[:success] = 'Comment was successfully updated.'
53
64
  redirect_to(plan_my_stuff.issue_path(@issue))
54
65
  rescue PlanMyStuff::StaleObjectError => e
@@ -26,6 +26,9 @@ module PlanMyStuff
26
26
  issue = PlanMyStuff::Issue.find(params[:issue_id])
27
27
  issue.request_approvals!(user_ids: user_ids, user: pms_current_user)
28
28
 
29
+ yield(issue) if block_given?
30
+ return if performed?
31
+
29
32
  flash[:success] = 'Approvers were successfully added.'
30
33
  redirect_to(show_path)
31
34
  rescue PlanMyStuff::AuthorizationError, PlanMyStuff::ValidationError => e
@@ -71,6 +74,9 @@ module PlanMyStuff
71
74
  flash[:success] = 'Response revoked.'
72
75
  end
73
76
 
77
+ yield(issue) if block_given?
78
+ return if performed?
79
+
74
80
  redirect_to(show_path)
75
81
  rescue PlanMyStuff::AuthorizationError, PlanMyStuff::ValidationError => e
76
82
  pms_handle_rescue(e)
@@ -88,6 +94,9 @@ module PlanMyStuff
88
94
  issue = PlanMyStuff::Issue.find(params[:issue_id])
89
95
  issue.remove_approvers!(user_ids: [params[:id].to_i], user: pms_current_user)
90
96
 
97
+ yield(issue) if block_given?
98
+ return if performed?
99
+
91
100
  flash[:success] = 'Approver was successfully removed.'
92
101
  redirect_to(show_path)
93
102
  rescue PlanMyStuff::AuthorizationError, PlanMyStuff::ValidationError => e
@@ -14,6 +14,9 @@ module PlanMyStuff
14
14
  issue = PlanMyStuff::Issue.find(params[:issue_id])
15
15
  issue.update!(state: :closed)
16
16
 
17
+ yield(issue) if block_given?
18
+ return if performed?
19
+
17
20
  flash[:success] = 'Issue was successfully closed.'
18
21
  redirect_to(plan_my_stuff.issue_path(issue))
19
22
  rescue PlanMyStuff::Error, ArgumentError => e
@@ -27,6 +30,9 @@ module PlanMyStuff
27
30
  issue = PlanMyStuff::Issue.find(params[:issue_id])
28
31
  issue.update!(state: :open)
29
32
 
33
+ yield(issue) if block_given?
34
+ return if performed?
35
+
30
36
  flash[:success] = 'Issue was successfully reopened.'
31
37
  redirect_to(plan_my_stuff.issue_path(issue))
32
38
  rescue PlanMyStuff::Error, ArgumentError => e
@@ -28,6 +28,9 @@ module PlanMyStuff
28
28
  issue = PlanMyStuff::Issue.find(params[:issue_id])
29
29
  link = add_link(issue, type)
30
30
 
31
+ yield(issue) if block_given?
32
+ return if performed?
33
+
31
34
  flash[:success] = "Linked #{link}"
32
35
  redirect_to(plan_my_stuff.issue_path(issue))
33
36
  rescue PlanMyStuff::ValidationError, ActiveModel::ValidationError, ArgumentError => e
@@ -47,6 +50,9 @@ module PlanMyStuff
47
50
  issue = PlanMyStuff::Issue.find(params[:issue_id])
48
51
  remove_link(issue, type, repo: repo, number: number)
49
52
 
53
+ yield(issue) if block_given?
54
+ return if performed?
55
+
50
56
  flash[:success] = "Unlinked #{repo}##{number}"
51
57
  redirect_to(plan_my_stuff.issue_path(issue))
52
58
  rescue PlanMyStuff::ValidationError, ActiveModel::ValidationError, ArgumentError => e
@@ -19,6 +19,10 @@ module PlanMyStuff
19
19
 
20
20
  PlanMyStuff::Pipeline.take!(project_item)
21
21
  assign_current_user(project_item)
22
+
23
+ yield(project_item) if block_given?
24
+ return if performed?
25
+
22
26
  flash[:success] ||= "Issue ##{issue.number} taken."
23
27
 
24
28
  redirect_to(plan_my_stuff.issue_path(issue))
@@ -48,6 +52,10 @@ module PlanMyStuff
48
52
  issue.update!(assignees: remaining)
49
53
  end
50
54
 
55
+ yielded = project_item.presence || issue
56
+ yield(yielded) if block_given?
57
+ return if performed?
58
+
51
59
  flash[:success] = "Issue ##{issue.number} released."
52
60
  redirect_to(plan_my_stuff.issue_path(issue))
53
61
  rescue ArgumentError, PlanMyStuff::Error => e
@@ -26,6 +26,9 @@ module PlanMyStuff
26
26
  issue = PlanMyStuff::Issue.find(params[:issue_id])
27
27
  issue.add_viewers!(user_ids: viewer_ids, user: pms_current_user)
28
28
 
29
+ yield(issue) if block_given?
30
+ return if performed?
31
+
29
32
  flash[:success] = 'Viewers were successfully added.'
30
33
  redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]))
31
34
  rescue PlanMyStuff::Error, Octokit::Error => e
@@ -44,6 +47,9 @@ module PlanMyStuff
44
47
  issue = PlanMyStuff::Issue.find(params[:issue_id])
45
48
  issue.remove_viewers!(user_ids: [params[:id].to_i], user: pms_current_user)
46
49
 
50
+ yield(issue) if block_given?
51
+ return if performed?
52
+
47
53
  flash[:success] = 'Viewer was successfully removed.'
48
54
  redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]))
49
55
  rescue PlanMyStuff::Error, Octokit::Error => e
@@ -19,6 +19,9 @@ module PlanMyStuff
19
19
  issue = PlanMyStuff::Issue.find(params[:issue_id])
20
20
  issue.enter_waiting_on_user!(user: pms_current_user)
21
21
 
22
+ yield(issue) if block_given?
23
+ return if performed?
24
+
22
25
  flash[:success] = 'Issue marked as waiting on user reply.'
23
26
  redirect_to(plan_my_stuff.issue_path(issue))
24
27
  rescue PlanMyStuff::Error, ArgumentError => e
@@ -37,6 +40,9 @@ module PlanMyStuff
37
40
  issue = PlanMyStuff::Issue.find(params[:issue_id])
38
41
  issue.clear_waiting_on_user!
39
42
 
43
+ yield(issue) if block_given?
44
+ return if performed?
45
+
40
46
  flash[:success] = 'Waiting-on-user state cleared.'
41
47
  redirect_to(plan_my_stuff.issue_path(issue))
42
48
  rescue PlanMyStuff::Error, ArgumentError => e
@@ -17,11 +17,15 @@ module PlanMyStuff
17
17
  page: @page,
18
18
  per_page: @per_page,
19
19
  )
20
+
21
+ yield(@issues) if block_given?
20
22
  end
21
23
 
22
24
  # GET /issues/new
23
25
  def new
24
26
  @issue = PlanMyStuff::Issue.new
27
+
28
+ yield(@issue) if block_given?
25
29
  end
26
30
 
27
31
  # POST /issues
@@ -33,6 +37,9 @@ module PlanMyStuff
33
37
  user: pms_current_user,
34
38
  )
35
39
 
40
+ yield(@issue) if block_given?
41
+ return if performed?
42
+
36
43
  flash[:success] = 'Issue was successfully created.'
37
44
  redirect_to(plan_my_stuff.issue_path(@issue))
38
45
  rescue PlanMyStuff::ValidationError => e
@@ -50,11 +57,15 @@ module PlanMyStuff
50
57
  @current_user_login = PlanMyStuff.configuration.github_login_for[@current_user_id]
51
58
  @pipeline_enabled = PlanMyStuff.configuration.pipeline_enabled
52
59
  @pipeline_item = load_pipeline_item(@issue.number) if @pipeline_enabled
60
+
61
+ yield(@issue) if block_given?
53
62
  end
54
63
 
55
64
  # GET /issues/:id/edit
56
65
  def edit
57
66
  @issue = PlanMyStuff::Issue.find(params[:id])
67
+
68
+ yield(@issue) if block_given?
58
69
  end
59
70
 
60
71
  # PATCH/PUT /issues/:id
@@ -67,6 +78,9 @@ module PlanMyStuff
67
78
  labels: parse_labels(issue_params[:labels]),
68
79
  )
69
80
 
81
+ yield(@issue) if block_given?
82
+ return if performed?
83
+
70
84
  flash[:success] = 'Issue was successfully updated.'
71
85
  redirect_to(plan_my_stuff.issue_path(@issue))
72
86
  rescue PlanMyStuff::StaleObjectError => e
@@ -22,6 +22,9 @@ module PlanMyStuff
22
22
 
23
23
  PlanMyStuff::Label.add!(issue: issue, labels: labels)
24
24
 
25
+ yield(issue) if block_given?
26
+ return if performed?
27
+
25
28
  flash[:success] = 'Label was successfully added.'
26
29
  redirect_to(plan_my_stuff.issue_path(issue))
27
30
  rescue PlanMyStuff::Error, Octokit::Error => e
@@ -35,6 +38,9 @@ module PlanMyStuff
35
38
  issue = PlanMyStuff::Issue.find(params[:issue_id])
36
39
  PlanMyStuff::Label.remove!(issue: issue, labels: [params[:id]])
37
40
 
41
+ yield(issue) if block_given?
42
+ return if performed?
43
+
38
44
  flash[:success] = 'Label was successfully removed.'
39
45
  redirect_to(plan_my_stuff.issue_path(issue))
40
46
  rescue PlanMyStuff::Error, Octokit::Error => e
@@ -16,6 +16,9 @@ module PlanMyStuff
16
16
 
17
17
  item.assign!(assignees)
18
18
 
19
+ yield(item) if block_given?
20
+ return if performed?
21
+
19
22
  flash[:success] =
20
23
  if assignees.present?
21
24
  "Item assigned to #{assignees.join(', ')}."
@@ -43,6 +46,9 @@ module PlanMyStuff
43
46
 
44
47
  item.assign!(remaining)
45
48
 
49
+ yield(item) if block_given?
50
+ return if performed?
51
+
46
52
  flash[:success] = "#{params[:username]} unassigned."
47
53
  redirect_to(plan_my_stuff.project_path(params[:project_id]))
48
54
  rescue ArgumentError, PlanMyStuff::Error => e
@@ -14,6 +14,9 @@ module PlanMyStuff
14
14
 
15
15
  item.move_to!(params[:status])
16
16
 
17
+ yield(item) if block_given?
18
+ return if performed?
19
+
17
20
  flash[:success] = "Item moved to #{params[:status]}."
18
21
  redirect_to(plan_my_stuff.project_path(params[:project_id]))
19
22
  rescue ArgumentError, PlanMyStuff::Error => e
@@ -7,19 +7,23 @@ module PlanMyStuff
7
7
  project_number = params[:project_id].to_i
8
8
 
9
9
  if params[:draft] == '1'
10
- PlanMyStuff::ProjectItem.create!(
10
+ item = PlanMyStuff::ProjectItem.create!(
11
11
  params[:title],
12
12
  draft: true,
13
13
  body: params[:body],
14
14
  project_number: project_number,
15
15
  )
16
- flash[:success] = 'Draft item created.'
16
+ flash_message = 'Draft item created.'
17
17
  else
18
18
  issue = PlanMyStuff::Issue.find(params[:issue_number].to_i)
19
- PlanMyStuff::ProjectItem.create!(issue, project_number: project_number)
20
- flash[:success] = "Issue ##{issue.number} added to project."
19
+ item = PlanMyStuff::ProjectItem.create!(issue, project_number: project_number)
20
+ flash_message = "Issue ##{issue.number} added to project."
21
21
  end
22
22
 
23
+ yield(item) if block_given?
24
+ return if performed?
25
+
26
+ flash[:success] = flash_message
23
27
  redirect_to(plan_my_stuff.project_path(project_number))
24
28
  rescue ArgumentError, PlanMyStuff::Error, Octokit::Error => e
25
29
  pms_handle_rescue(e)
@@ -38,6 +42,9 @@ module PlanMyStuff
38
42
 
39
43
  item.destroy!(user: pms_current_user)
40
44
 
45
+ yield(item) if block_given?
46
+ return if performed?
47
+
41
48
  flash[:success] = 'Item removed from project.'
42
49
  redirect_to(plan_my_stuff.project_path(project_number))
43
50
  rescue ArgumentError, PlanMyStuff::Error, Octokit::Error => e
@@ -12,11 +12,15 @@ module PlanMyStuff
12
12
  when 'regular' then all_projects.reject { |p| p.is_a?(PlanMyStuff::TestingProject) }
13
13
  else all_projects
14
14
  end
15
+
16
+ yield(@projects) if block_given?
15
17
  end
16
18
 
17
19
  # GET /projects/new
18
20
  def new
19
21
  @project = PlanMyStuff::Project.new
22
+
23
+ yield(@project) if block_given?
20
24
  end
21
25
 
22
26
  # POST /projects
@@ -28,6 +32,9 @@ module PlanMyStuff
28
32
  user: pms_current_user,
29
33
  )
30
34
 
35
+ yield(@project) if block_given?
36
+ return if performed?
37
+
31
38
  flash[:success] = 'Project was successfully created.'
32
39
  redirect_to(plan_my_stuff.project_path(@project.number))
33
40
  rescue PlanMyStuff::ValidationError => e
@@ -46,11 +53,15 @@ module PlanMyStuff
46
53
  @project = PlanMyStuff::Project.find(params[:id].to_i)
47
54
  @statuses = @project.statuses.pluck(:name)
48
55
  @items_by_status = @project.items.group_by(&:status)
56
+
57
+ yield(@project) if block_given?
49
58
  end
50
59
 
51
60
  # GET /projects/:id/edit
52
61
  def edit
53
62
  @project = PlanMyStuff::Project.find(params[:id].to_i)
63
+
64
+ yield(@project) if block_given?
54
65
  end
55
66
 
56
67
  # PATCH/PUT /projects/:id
@@ -63,6 +74,9 @@ module PlanMyStuff
63
74
  description: project_params[:description],
64
75
  )
65
76
 
77
+ yield(@project) if block_given?
78
+ return if performed?
79
+
66
80
  flash[:success] = 'Project was successfully updated.'
67
81
  redirect_to(plan_my_stuff.project_path(@project.number))
68
82
  rescue PlanMyStuff::StaleObjectError => e
@@ -30,6 +30,9 @@ module PlanMyStuff
30
30
  raise(ArgumentError, "Invalid result: #{params[:result].inspect}")
31
31
  end
32
32
 
33
+ yield(item) if block_given?
34
+ return if performed?
35
+
33
36
  redirect_to(plan_my_stuff.testing_project_path(project_number))
34
37
  rescue PlanMyStuff::ValidationError => e
35
38
  pms_handle_rescue(e)
@@ -5,6 +5,8 @@ module PlanMyStuff
5
5
  # GET /testing_projects/:testing_project_id/items/new
6
6
  def new
7
7
  @project = PlanMyStuff::TestingProject.find(params[:testing_project_id].to_i)
8
+
9
+ yield(@project) if block_given?
8
10
  rescue PlanMyStuff::Error, Octokit::Error => e
9
11
  pms_handle_rescue(e)
10
12
  flash[:error] = e.message
@@ -26,6 +28,9 @@ module PlanMyStuff
26
28
  item.update_due_date!(Date.parse(item_params[:due_date])) if item_params[:due_date].present?
27
29
  item.update_pass_mode!(item_params[:pass_mode]) if item_params[:pass_mode].present?
28
30
 
31
+ yield(item) if block_given?
32
+ return if performed?
33
+
29
34
  flash[:success] = 'Item added.'
30
35
  redirect_to(plan_my_stuff.testing_project_path(project_number))
31
36
  rescue ArgumentError, PlanMyStuff::Error, Octokit::Error => e
@@ -6,6 +6,8 @@ module PlanMyStuff
6
6
  def new
7
7
  @project = PlanMyStuff::TestingProject.new
8
8
  @project.metadata.subject_urls = [params[:subject_url]] if params[:subject_url].present?
9
+
10
+ yield(@project) if block_given?
9
11
  end
10
12
 
11
13
  # POST /testing_projects
@@ -19,6 +21,9 @@ module PlanMyStuff
19
21
  user: pms_current_user,
20
22
  )
21
23
 
24
+ yield(@project) if block_given?
25
+ return if performed?
26
+
22
27
  flash[:success] = 'Testing project was successfully created.'
23
28
  redirect_to(plan_my_stuff.testing_project_path(@project.number))
24
29
  rescue PlanMyStuff::ValidationError => e
@@ -39,11 +44,15 @@ module PlanMyStuff
39
44
  @project = PlanMyStuff::TestingProject.find(params[:id].to_i)
40
45
  @statuses = @project.statuses.pluck(:name)
41
46
  @items_by_status = @project.items.group_by(&:status)
47
+
48
+ yield(@project) if block_given?
42
49
  end
43
50
 
44
51
  # GET /testing_projects/:id/edit
45
52
  def edit
46
53
  @project = PlanMyStuff::TestingProject.find(params[:id].to_i)
54
+
55
+ yield(@project) if block_given?
47
56
  end
48
57
 
49
58
  # PATCH/PUT /testing_projects/:id
@@ -60,6 +69,9 @@ module PlanMyStuff
60
69
  },
61
70
  )
62
71
 
72
+ yield(@project) if block_given?
73
+ return if performed?
74
+
63
75
  flash[:success] = 'Testing project was successfully updated.'
64
76
  redirect_to(plan_my_stuff.testing_project_path(@project.number))
65
77
  rescue PlanMyStuff::StaleObjectError => e
@@ -296,26 +296,46 @@ module PlanMyStuff
296
296
 
297
297
  # Lists GitHub issues with optional filters and pagination.
298
298
  #
299
+ # @raise [ArgumentError] when +priority_list: false+ is passed
300
+ #
299
301
  # @param repo [Symbol, String, nil] defaults to config.default_repo
300
302
  # @param state [Symbol] :open, :closed, or :all
301
303
  # @param labels [Array<String>]
304
+ # @param priority_list [Boolean, nil] when +true+, restricts to issues whose +Priority List+ issue field is
305
+ # +Yes+ (server-side filter via the +issue_field_values+ query param). +false+ raises +ArgumentError+ -- GitHub
306
+ # has no negation qualifier. Silently dropped when +config.issue_fields_enabled+ is +false+.
302
307
  # @param page [Integer]
303
308
  # @param per_page [Integer]
304
309
  #
305
310
  # @return [Array<PlanMyStuff::Issue>]
306
311
  #
307
- def list(repo: nil, state: :open, labels: [], page: 1, per_page: 25)
312
+ def list(repo: nil, state: :open, labels: [], priority_list: nil, page: 1, per_page: 25)
313
+ if priority_list == false
314
+ raise(ArgumentError, 'priority_list: false is not supported (no GitHub negation qualifier)')
315
+ end
316
+
308
317
  client = PlanMyStuff.client
309
318
  resolved_repo = client.resolve_repo!(repo)
310
319
 
311
320
  params = { state: state.to_s, page: page, per_page: per_page }
312
321
  params[:labels] = labels.sort.join(',') if labels.present?
322
+ if priority_list && PlanMyStuff.configuration.issue_fields_enabled
323
+ params[:issue_field_values] = 'priority-list:Yes'
324
+ end
313
325
 
314
326
  github_issues = client.rest(:list_issues, resolved_repo, **params)
315
327
  filtered = github_issues.reject { |gi| gi.respond_to?(:pull_request) && gi.pull_request }
316
328
  filtered.map { |gi| build(gi, repo: resolved_repo) }
317
329
  end
318
330
 
331
+ # Convenience shortcut for +list(priority_list: true, ...)+. See +.list+ for parameter semantics.
332
+ #
333
+ # @return [Array<PlanMyStuff::Issue>]
334
+ #
335
+ def priority_list(**)
336
+ list(**, priority_list: true)
337
+ end
338
+
319
339
  # Counts GitHub issues matching the given filters without paginating full payloads.
320
340
  #
321
341
  # Uses GitHub's Search API (+search/issues+), which returns +total_count+ in a single
@@ -327,13 +347,23 @@ module PlanMyStuff
327
347
  # - The Search API has its own rate limit (30 req/min authenticated) separate from
328
348
  # the core REST API.
329
349
  #
350
+ # @raise [ArgumentError] when +priority_list: false+ is passed
351
+ #
330
352
  # @param repo [Symbol, String, nil] defaults to config.default_repo
331
353
  # @param state [Symbol] :open, :closed, or :all
332
354
  # @param labels [Array<String>]
355
+ # @param priority_list [Boolean, nil] when +true+, restricts to issues whose +Priority List+ issue field is
356
+ # +Yes+ (server-side filter via the +field.priority-list:Yes+ Search qualifier). +false+ raises
357
+ # +ArgumentError+ -- GitHub has no negation qualifier. Silently dropped when
358
+ # +config.issue_fields_enabled+ is +false+.
333
359
  #
334
360
  # @return [Integer]
335
361
  #
336
- def count(repo: nil, state: :open, labels: [])
362
+ def count(repo: nil, state: :open, labels: [], priority_list: nil)
363
+ if priority_list == false
364
+ raise(ArgumentError, 'priority_list: false is not supported (no GitHub negation qualifier)')
365
+ end
366
+
337
367
  client = PlanMyStuff.client
338
368
  resolved_repo = client.resolve_repo!(repo)
339
369
 
@@ -344,7 +374,12 @@ module PlanMyStuff
344
374
  qualifiers += labels_to_use.map do |label|
345
375
  "label:\"#{label}\""
346
376
  end
347
- client.rest(:search_issues, qualifiers.join(' '), per_page: 1).total_count
377
+ search_options = { per_page: 1 }
378
+ if priority_list && PlanMyStuff.configuration.issue_fields_enabled
379
+ qualifiers << 'field.priority-list:Yes'
380
+ search_options[:advanced_search] = true
381
+ end
382
+ client.rest(:search_issues, qualifiers.join(' '), **search_options).total_count
348
383
  end
349
384
 
350
385
  # Submits one or more pre-built payloads to GitHub's "Import Issues" preview endpoint
@@ -663,6 +698,8 @@ module PlanMyStuff
663
698
  # @return [self]
664
699
  #
665
700
  def save!(user: nil, skip_notification: false)
701
+ forward_legacy_priority_list_metadata!
702
+
666
703
  if new_record?
667
704
  created = self.class.create!(
668
705
  title: title,
@@ -795,6 +832,16 @@ module PlanMyStuff
795
832
  @issue_fields ||= load_issue_fields!
796
833
  end
797
834
 
835
+ # @return [Boolean]
836
+ def priority_list?
837
+ issue_fields['Priority List'] == 'Yes'
838
+ end
839
+
840
+ # @return [Integer, nil]
841
+ def priority_list_priority
842
+ issue_fields['Priority List Priority']
843
+ end
844
+
798
845
  # Bulk-updates GitHub Issue Field values in a single +setIssueFieldValue+ mutation. Each key is the field display
799
846
  # name; values are coerced to the right input fragment based on the field's type. Passing +nil+ as a value clears
800
847
  # that field.
@@ -934,6 +981,28 @@ module PlanMyStuff
934
981
  reload
935
982
  end
936
983
 
984
+ # Forwards a pending legacy +metadata.priority_list+ /
985
+ # +#priority_list_priority+ write into +@pending_issue_fields+ so the next
986
+ # +save!+ persists the values into the +Priority List+ /
987
+ # +Priority List Priority+ GitHub Issue Fields. Caller-supplied
988
+ # +issue_fields:+ entries win on key collision. Silently skipped when
989
+ # +config.issue_fields_enabled+ is +false+.
990
+ #
991
+ # @return [void]
992
+ #
993
+ def forward_legacy_priority_list_metadata!
994
+ return unless PlanMyStuff.configuration.issue_fields_enabled
995
+ return unless metadata.instance_variable_get(:@legacy_priority_list_pending)
996
+
997
+ legacy_pl = metadata.instance_variable_get(:@priority_list)
998
+ legacy_plp = metadata.instance_variable_get(:@priority_list_priority)
999
+
1000
+ legacy_fields = { 'Priority List' => legacy_pl ? 'Yes' : nil }
1001
+ legacy_fields['Priority List Priority'] = legacy_plp unless legacy_plp == -1
1002
+
1003
+ @pending_issue_fields = legacy_fields.merge(@pending_issue_fields || {})
1004
+ end
1005
+
937
1006
  # Applies in-memory updates from an +update!+ kwargs hash. Top-level scalars go through their setters so
938
1007
  # +@body_dirty+ and friends stay in sync; +metadata:+ is merged into +@metadata+ (top-level attrs assigned
939
1008
  # directly, custom_fields merged key-by-key).
@@ -2,14 +2,16 @@
2
2
 
3
3
  module PlanMyStuff
4
4
  class IssueMetadata < PlanMyStuff::BaseMetadata
5
+ PRIORITY_LIST_METADATA_DEPRECATION =
6
+ 'PlanMyStuff: IssueMetadata#priority_list / #priority_list_priority are deprecated. priority_list moved to ' \
7
+ "GitHub Issue Fields ('Priority List' single_select, 'Priority List Priority' number) in 0.20.0. Reads " \
8
+ 'now come from issue_fields; legacy writes forward to set_issue_fields! on save. The metadata accessors ' \
9
+ 'will be removed in 1.0.0.'
10
+
5
11
  # @return [Time, nil] first support action timestamp, nil until set
6
12
  attr_accessor :responded_at
7
13
  # @return [String, nil] user-facing URL in the consuming app
8
14
  attr_accessor :issues_url
9
- # @return [Boolean] whether this issue appears on the priority dashboard
10
- attr_accessor :priority_list
11
- # @return [Integer] sort order on priority dashboard (-1 = unranked)
12
- attr_accessor :priority_list_priority
13
15
  # @return [Array<Integer>] user IDs of non-support users allowed to view internal comments
14
16
  attr_accessor :visibility_allowlist
15
17
  # @return [String, nil] merged PR commit SHA for release tracking
@@ -50,8 +52,14 @@ module PlanMyStuff
50
52
 
51
53
  metadata.responded_at = parse_time(hash[:responded_at])
52
54
  metadata.issues_url = hash[:issues_url]
53
- metadata.priority_list = hash.fetch(:priority_list, false)
54
- metadata.priority_list_priority = hash.fetch(:priority_list_priority, -1)
55
+ if hash.key?(:priority_list) || hash.key?(:priority_list_priority)
56
+ PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
57
+ metadata.instance_variable_set(:@priority_list, hash[:priority_list]) if hash.key?(:priority_list)
58
+ if hash.key?(:priority_list_priority)
59
+ metadata.instance_variable_set(:@priority_list_priority, hash[:priority_list_priority])
60
+ end
61
+ metadata.instance_variable_set(:@legacy_priority_list_pending, true)
62
+ end
55
63
  metadata.visibility_allowlist = Array.wrap(hash[:visibility_allowlist])
56
64
  metadata.commit_sha = hash[:commit_sha]
57
65
  metadata.auto_complete = hash.fetch(:auto_complete, true)
@@ -86,8 +94,6 @@ module PlanMyStuff
86
94
 
87
95
  metadata.responded_at = nil
88
96
  metadata.issues_url = build_issues_url(PlanMyStuff.configuration)
89
- metadata.priority_list = false
90
- metadata.priority_list_priority = -1
91
97
  metadata.visibility_allowlist = []
92
98
  metadata.commit_sha = nil
93
99
  metadata.auto_complete = true
@@ -169,8 +175,6 @@ module PlanMyStuff
169
175
 
170
176
  def initialize
171
177
  super
172
- @priority_list = false
173
- @priority_list_priority = -1
174
178
  @visibility_allowlist = []
175
179
  @auto_complete = true
176
180
  @links = []
@@ -188,9 +192,55 @@ module PlanMyStuff
188
192
  !!auto_complete
189
193
  end
190
194
 
195
+ # @deprecated Use +Issue#priority_list?+. Removed in 1.0.0.
196
+ #
197
+ # @return [Object, nil]
198
+ #
199
+ def priority_list
200
+ PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
201
+ @priority_list
202
+ end
203
+
204
+ # @deprecated Use +Issue#update!(issue_fields: { 'Priority List' => 'Yes' })+. Removed in 1.0.0.
205
+ #
206
+ # @param value [Object]
207
+ #
208
+ # @return [Object]
209
+ #
210
+ def priority_list=(value)
211
+ PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
212
+ @legacy_priority_list_pending = true
213
+ @priority_list = value
214
+ end
215
+
216
+ # @deprecated Use +Issue#priority_list?+. Removed in 1.0.0.
217
+ #
191
218
  # @return [Boolean]
219
+ #
192
220
  def priority_list?
193
- !!priority_list
221
+ PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
222
+ !!@priority_list
223
+ end
224
+
225
+ # @deprecated Use +Issue#priority_list_priority+. Removed in 1.0.0.
226
+ #
227
+ # @return [Object, nil]
228
+ #
229
+ def priority_list_priority
230
+ PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
231
+ @priority_list_priority
232
+ end
233
+
234
+ # @deprecated Use +Issue#update!(issue_fields: { 'Priority List Priority' => N })+. Removed in 1.0.0.
235
+ #
236
+ # @param value [Object]
237
+ #
238
+ # @return [Object]
239
+ #
240
+ def priority_list_priority=(value)
241
+ PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
242
+ @legacy_priority_list_pending = true
243
+ @priority_list_priority = value
194
244
  end
195
245
 
196
246
  # @return [Boolean]
@@ -220,8 +270,6 @@ module PlanMyStuff
220
270
  super.merge(
221
271
  responded_at: PlanMyStuff.format_time(responded_at),
222
272
  issues_url: issues_url,
223
- priority_list: priority_list,
224
- priority_list_priority: priority_list_priority,
225
273
  visibility_allowlist: visibility_allowlist,
226
274
  commit_sha: commit_sha,
227
275
  auto_complete: auto_complete,
@@ -3,7 +3,7 @@
3
3
  module PlanMyStuff
4
4
  module VERSION
5
5
  MAJOR = 0
6
- MINOR = 18
6
+ MINOR = 20
7
7
  TINY = 0
8
8
 
9
9
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
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.18.0
4
+ version: 0.20.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-05-19 00:00:00.000000000 Z
11
+ date: 2026-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails