plan_my_stuff 0.7.0 → 0.9.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -1
  3. data/CONFIGURATION.md +351 -0
  4. data/README.md +100 -103
  5. data/app/controllers/plan_my_stuff/application_controller.rb +22 -3
  6. data/app/controllers/plan_my_stuff/comments_controller.rb +14 -16
  7. data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +23 -13
  8. data/app/controllers/plan_my_stuff/issues/closures_controller.rb +7 -5
  9. data/app/controllers/plan_my_stuff/issues/links_controller.rb +14 -18
  10. data/app/controllers/plan_my_stuff/issues/takes_controller.rb +99 -28
  11. data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +13 -5
  12. data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +7 -5
  13. data/app/controllers/plan_my_stuff/issues_controller.rb +24 -28
  14. data/app/controllers/plan_my_stuff/labels_controller.rb +21 -5
  15. data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +13 -6
  16. data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +5 -4
  17. data/app/controllers/plan_my_stuff/project_items_controller.rb +30 -5
  18. data/app/controllers/plan_my_stuff/projects_controller.rb +16 -16
  19. data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +21 -11
  20. data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +9 -4
  21. data/app/controllers/plan_my_stuff/testing_projects_controller.rb +30 -14
  22. data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +50 -17
  23. data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +32 -49
  24. data/app/jobs/plan_my_stuff/application_job.rb +2 -3
  25. data/app/jobs/plan_my_stuff/reminders_sweep_job.rb +15 -22
  26. data/app/views/plan_my_stuff/comments/edit.html.erb +1 -3
  27. data/app/views/plan_my_stuff/comments/partials/_form.html.erb +1 -0
  28. data/app/views/plan_my_stuff/issues/edit.html.erb +2 -4
  29. data/app/views/plan_my_stuff/issues/index.html.erb +2 -2
  30. data/app/views/plan_my_stuff/issues/new.html.erb +2 -4
  31. data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +23 -2
  32. data/app/views/plan_my_stuff/issues/partials/_form.html.erb +1 -0
  33. data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +2 -1
  34. data/app/views/plan_my_stuff/issues/partials/_links.html.erb +50 -7
  35. data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +2 -1
  36. data/app/views/plan_my_stuff/issues/show.html.erb +5 -2
  37. data/app/views/plan_my_stuff/partials/_flash.html.erb +3 -0
  38. data/app/views/plan_my_stuff/projects/edit.html.erb +1 -3
  39. data/app/views/plan_my_stuff/projects/index.html.erb +1 -1
  40. data/app/views/plan_my_stuff/projects/new.html.erb +1 -3
  41. data/app/views/plan_my_stuff/projects/partials/_form.html.erb +1 -0
  42. data/app/views/plan_my_stuff/projects/show.html.erb +13 -3
  43. data/app/views/plan_my_stuff/testing_project_items/new.html.erb +1 -3
  44. data/app/views/plan_my_stuff/testing_project_items/results/new.html.erb +1 -3
  45. data/app/views/plan_my_stuff/testing_projects/edit.html.erb +1 -3
  46. data/app/views/plan_my_stuff/testing_projects/new.html.erb +1 -3
  47. data/app/views/plan_my_stuff/testing_projects/partials/_form.html.erb +4 -3
  48. data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +1 -0
  49. data/app/views/plan_my_stuff/testing_projects/partials/items/_form.html.erb +1 -0
  50. data/app/views/plan_my_stuff/testing_projects/show.html.erb +2 -2
  51. data/config/routes.rb +2 -2
  52. data/lib/generators/plan_my_stuff/install/templates/initializer.rb +52 -14
  53. data/lib/plan_my_stuff/approval.rb +12 -4
  54. data/lib/plan_my_stuff/aws_sns_simulator.rb +12 -6
  55. data/lib/plan_my_stuff/base_metadata.rb +4 -15
  56. data/lib/plan_my_stuff/base_project.rb +68 -55
  57. data/lib/plan_my_stuff/base_project_item.rb +62 -57
  58. data/lib/plan_my_stuff/base_project_metadata.rb +1 -1
  59. data/lib/plan_my_stuff/client.rb +136 -48
  60. data/lib/plan_my_stuff/comment.rb +59 -57
  61. data/lib/plan_my_stuff/comment_metadata.rb +1 -1
  62. data/lib/plan_my_stuff/configuration.rb +93 -93
  63. data/lib/plan_my_stuff/errors.rb +10 -10
  64. data/lib/plan_my_stuff/graphql/queries.rb +1 -1
  65. data/lib/plan_my_stuff/issue.rb +471 -333
  66. data/lib/plan_my_stuff/issue_metadata.rb +10 -10
  67. data/lib/plan_my_stuff/label.rb +34 -18
  68. data/lib/plan_my_stuff/link.rb +15 -15
  69. data/lib/plan_my_stuff/markdown.rb +12 -6
  70. data/lib/plan_my_stuff/metadata_parser.rb +3 -1
  71. data/lib/plan_my_stuff/notifications.rb +1 -1
  72. data/lib/plan_my_stuff/pipeline/completed_sweep.rb +2 -2
  73. data/lib/plan_my_stuff/pipeline/issue_linker.rb +1 -1
  74. data/lib/plan_my_stuff/pipeline.rb +61 -83
  75. data/lib/plan_my_stuff/project.rb +4 -4
  76. data/lib/plan_my_stuff/project_item_metadata.rb +1 -1
  77. data/lib/plan_my_stuff/project_metadata.rb +1 -1
  78. data/lib/plan_my_stuff/reminders/closer.rb +1 -1
  79. data/lib/plan_my_stuff/reminders/fire.rb +3 -3
  80. data/lib/plan_my_stuff/reminders/sweep.rb +4 -4
  81. data/lib/plan_my_stuff/repo.rb +12 -6
  82. data/lib/plan_my_stuff/test_helpers.rb +11 -11
  83. data/lib/plan_my_stuff/testing_project.rb +12 -11
  84. data/lib/plan_my_stuff/testing_project_item.rb +11 -9
  85. data/lib/plan_my_stuff/testing_project_metadata.rb +2 -2
  86. data/lib/plan_my_stuff/version.rb +1 -1
  87. data/lib/plan_my_stuff/webhook_replayer.rb +14 -2
  88. data/lib/plan_my_stuff.rb +26 -2
  89. data/lib/tasks/plan_my_stuff.rake +33 -20
  90. metadata +4 -2
@@ -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
- # Consuming apps enqueue it once; the job self-requeues after each
6
- # perform so the schedule persists without requiring a cron/whenever
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
- # retry up to 25 times on error and our +around_perform+ +ensure+
14
- # re-enqueues a follow-up run on every attempt, causing geometric
15
- # duplicate pile-up. With +attempts: 1+, exactly one perform per
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
- # current time is before 6:30am ET). Apps wanting a different
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
- # self-requeue and the +plan_my_stuff:reminders:sweep+ rake task so
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
- # +ensure+ so a perform error still schedules the next one. Skips
64
- # requeue when perform never captured a repo arg (deserialization
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,8 +1,6 @@
1
1
  <h1>Edit Comment</h1>
2
2
 
3
- <% if flash[:error].present? %>
4
- <p style="color: red;"><%= flash[:error] %></p>
5
- <% end %>
3
+ <%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
6
4
 
7
5
  <%=
8
6
  render({
@@ -1,3 +1,4 @@
1
+ <%# locals: (issue:, comment:, support_user:) %>
1
2
  <%
2
3
  persisted = comment.persisted?
3
4
  url =
@@ -1,10 +1,8 @@
1
1
  <h1>Edit Issue #<%= @issue.number %></h1>
2
2
 
3
- <% if flash[:error].present? %>
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, support_user: @support_user } }) %>
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.any? %>
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 "New Issue", plan_my_stuff.new_issue_path(repo: @repo) %>
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
- <% if flash[:error].present? %>
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, support_user: @support_user } }) %>
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><strong><%= pending_count %> of <%= approvers.size %> approval(s) pending</strong></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.approved? && (approval.user_id == current_user_id || support_user) %>
64
+ <% if !approval.pending? && (approval.user_id == current_user_id || support_user) %>
44
65
  <%=
45
66
  button_to(
46
67
  'Revoke',
@@ -1,3 +1,4 @@
1
+ <%# locals: (issue:) %>
1
2
  <%
2
3
  persisted = issue.persisted?
3
4
  url =
@@ -1,4 +1,5 @@
1
- <% if issue.labels.any? %>
1
+ <%# locals: (issue:) %>
2
+ <% if issue.labels.present? %>
2
3
  <% issue.labels.each do |label| %>
3
4
  <span>
4
5
  <%= label %>
@@ -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
- { type: 'parent', label: 'Parent', single: true, add_form: true, removable: true, targets: [issue.parent].compact },
7
- { type: 'sub_ticket', label: 'Sub-tickets', single: false, add_form: true, removable: true, targets: issue.sub_tickets },
8
- { type: 'blocked_by', label: 'Blocked by', single: false, add_form: true, removable: true, targets: issue.blocked_by },
9
- { type: 'blocking', label: 'Blocking', single: false, add_form: false, removable: false, targets: issue.blocking },
10
- { type: 'related', label: 'Related', single: false, add_form: true, removable: true, targets: issue.related },
11
- { type: 'duplicate_of', label: 'Duplicate of', single: true, add_form: false, removable: false, targets: [issue.duplicate_of].compact },
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].any? %>
64
+ <% if section[:targets].present? %>
22
65
  <ul>
23
66
  <% section[:targets].each do |target| %>
24
67
  <li>
@@ -1,7 +1,8 @@
1
+ <%# locals: (issue:) %>
1
2
  <h2>Manage Viewers</h2>
2
3
 
3
4
  <% viewers = issue.metadata.visibility_allowlist %>
4
- <% if viewers.any? %>
5
+ <% if viewers.present? %>
5
6
  <ul>
6
7
  <% viewers.each do |viewer_id| %>
7
8
  <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('Unassign', 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.any? %>
73
+ <% if @comments.present? %>
71
74
  <% @comments.each do |comment| %>
72
75
  <div>
73
76
  <%= PlanMyStuff::Markdown.render(comment.body || '').html_safe %>
@@ -0,0 +1,3 @@
1
+ <% if flash[:error].present? %>
2
+ <p role="alert" style="color: red;"><%= flash[:error] %></p>
3
+ <% end %>
@@ -1,7 +1,5 @@
1
1
  <h1>Edit Project #<%= @project.number %></h1>
2
2
 
3
- <% if flash[:error].present? %>
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 } }) %>
@@ -6,7 +6,7 @@
6
6
  <%= link_to('Testing', plan_my_stuff.projects_path(filter: 'testing')) %>
7
7
  </p>
8
8
 
9
- <% if @projects.any? %>
9
+ <% if @projects.present? %>
10
10
  <ul>
11
11
  <% @projects.each do |project| %>
12
12
  <li>
@@ -1,7 +1,5 @@
1
1
  <h1>New Project</h1>
2
2
 
3
- <% if flash[:error].present? %>
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,3 +1,4 @@
1
+ <%# locals: (project:) %>
1
2
  <%
2
3
  persisted = project.persisted?
3
4
  url =
@@ -7,7 +7,7 @@
7
7
  <% end %>
8
8
  </p>
9
9
 
10
- <% if @statuses.any? %>
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.any? %>
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('×', title: "Unassign #{username}") %>
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>Add Item to <%= @project.title %></h1>
2
2
 
3
- <% if flash[:error].present? %>
4
- <p style="color: red;"><%= flash[:error] %></p>
5
- <% end %>
3
+ <%= render({ partial: 'plan_my_stuff/partials/flash' }) %>
6
4
 
7
5
  <%=
8
6
  render({
@@ -1,8 +1,6 @@
1
1
  <h1>Fail Item</h1>
2
2
 
3
- <% if flash[:error].present? %>
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
- <% if flash[:error].present? %>
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
- <% if flash[:error].present? %>
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.subject_urls.join("\n")) %>
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.due_date) %>
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.deadline_miss_reason) %>
34
+ <%= form.text_field(:deadline_miss_reason, value: project.metadata&.deadline_miss_reason) %>
34
35
  </div>
35
36
 
36
37
  <div>
@@ -1,3 +1,4 @@
1
+ <%# locals: (item:, project:, statuses:) %>
1
2
  <div style="border: 1px solid black; margin: 1em; padding: 0.5em">
2
3
  <% if item.draft? %>
3
4
  <strong><%= item.title %></strong>
@@ -1,3 +1,4 @@
1
+ <%# locals: (project:) %>
1
2
  <%= form_with(url: plan_my_stuff.testing_project_items_path(project.number), method: :post, local: true) do |form| %>
2
3
  <div>
3
4
  <%= form.label(:title, 'Title') %>
@@ -7,7 +7,7 @@
7
7
  <% end %>
8
8
  </p>
9
9
 
10
- <% if @support_user && @project.metadata.subject_urls.any? %>
10
+ <% if @support_user && @project.metadata.subject_urls.present? %>
11
11
  <p>
12
12
  <strong>Subject URLs:</strong>
13
13
  <% @project.metadata.subject_urls.each do |url| %>
@@ -20,7 +20,7 @@
20
20
  <p><strong>Due:</strong> <%= @project.metadata.due_date %></p>
21
21
  <% end %>
22
22
 
23
- <% if @statuses.any? %>
23
+ <% if @statuses.present? %>
24
24
  <table>
25
25
  <thead>
26
26
  <tr>
data/config/routes.rb CHANGED
@@ -12,7 +12,7 @@ PlanMyStuff::Engine.routes.draw do
12
12
  if config.pipeline_enabled
13
13
  resource(
14
14
  :take,
15
- only: :create,
15
+ only: %i[create destroy],
16
16
  controller: config.controller_for(:'issues/takes'),
17
17
  )
18
18
  end
@@ -32,7 +32,7 @@ PlanMyStuff::Engine.routes.draw do
32
32
 
33
33
  if mount_groups.fetch(:projects, true)
34
34
  resources :projects, except: %i[destroy], controller: config.controller_for(:projects) do
35
- resources :items, only: %i[create], controller: config.controller_for(:project_items) do
35
+ resources :items, only: %i[create destroy], controller: config.controller_for(:project_items) do
36
36
  resource :status, only: %i[update], controller: config.controller_for(:'project_items/statuses')
37
37
  resource :assignment, only: %i[update destroy], controller: config.controller_for(:'project_items/assignments')
38
38
  end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- PMS.configure do |config|
3
+ PlanMyStuff.configure do |config|
4
4
  # --------------------------------------------------------------------------
5
5
  # Authentication (required)
6
6
  # --------------------------------------------------------------------------
7
7
  # GitHub PAT with `repo` and `project` scopes. All API calls use this token.
8
8
  config.access_token = Rails.application.credentials.dig(:plan_my_stuff, :github_token)
9
9
 
10
+ # Classic PAT (requires `repo` scope) for the Issues Import API (golden-comet-preview).
11
+ # Fine-grained tokens are not supported by that endpoint.
12
+ # config.import_access_token = Rails.application.credentials.dig(:plan_my_stuff, :github_import_token)
13
+
10
14
  # --------------------------------------------------------------------------
11
15
  # Organization (required)
12
16
  # --------------------------------------------------------------------------
@@ -97,27 +101,29 @@ PMS.configure do |config|
97
101
  # number is appended to form `Issue#user_link`, which the gem also
98
102
  # writes as the visible body of the GitHub issue.
99
103
  # Recommended default:
104
+ # url_options = Rails.application.config.action_mailer.default_url_options
100
105
  # config.issues_url_prefix =
101
- # "#{Rails.application.config.action_mailer.default_url_options[:host]}/issues"
106
+ # "#{url_options[:protocol] || 'http'}://#{url_options[:host]}/issues"
102
107
 
103
108
  # --------------------------------------------------------------------------
104
- # Request gateway
109
+ # Notification actor
105
110
  # --------------------------------------------------------------------------
106
- # Proc returning boolean, or nil (always send). When it returns false the
107
- # request is deferred to a background job instead of hitting GitHub.
108
- # config.should_send_request = -> { !MaintenanceMode.active? }
109
-
110
- # Map of action type to job class name for deferred requests.
111
- # config.job_classes = {
112
- # create_ticket: 'PmsCreateTicketJob',
113
- # post_comment: 'PmsPostCommentJob',
114
- # update_status: 'PmsUpdateStatusJob'
115
- # }
116
-
117
111
  # Fallback actor for notification events (plan_my_stuff.*) when a caller
118
112
  # does not pass an explicit user: kwarg. Proc/lambda called at event time.
119
113
  # config.current_user = -> { Current.user }
120
114
 
115
+ # --------------------------------------------------------------------------
116
+ # Controller rescue hook
117
+ # --------------------------------------------------------------------------
118
+ # Invoked from every gem controller rescue block, after the gem logs the
119
+ # error class/message and stack trace via Rails.logger.error and just
120
+ # before the user-facing flash + redirect/render. Forwards the rescued
121
+ # exception so consuming apps can fire monitoring even though the rescue
122
+ # swallows the error. Receives the rescued exception; return value is
123
+ # ignored. Defaults to nil (no-op).
124
+ #
125
+ # config.controller_rescue = ->(error) { MonitoringService.notice_error(error) }
126
+
121
127
  # --------------------------------------------------------------------------
122
128
  # Custom fields
123
129
  # --------------------------------------------------------------------------
@@ -146,6 +152,22 @@ PMS.configure do |config|
146
152
  # Testing-project-only fields (merged on top of shared, context wins on conflicts):
147
153
  # config.testing_custom_fields = {}
148
154
 
155
+ # --------------------------------------------------------------------------
156
+ # Issue types (org-side renames)
157
+ # --------------------------------------------------------------------------
158
+ # The gem speaks in canonical type names: 'Bug', 'Feature',
159
+ # 'IT Issue / Hardware', 'Other', 'Performance', 'Question', 'Task'.
160
+ # Symbol shortcuts (`:bug`, `:feature`, `:it_issue`, `:other`, `:performance`,
161
+ # `:question`, `:task`) resolve to those canonical names automatically.
162
+ # If your GitHub org uses different names for any of them, map the
163
+ # canonical name to your org's name here and PMS will translate on
164
+ # write. Missing keys pass through unchanged.
165
+ #
166
+ # config.issue_types = {
167
+ # 'Bug' => 'User Bug',
168
+ # 'Feature' => 'Enhancement',
169
+ # }
170
+
149
171
  # --------------------------------------------------------------------------
150
172
  # Release pipeline
151
173
  # --------------------------------------------------------------------------
@@ -161,6 +183,22 @@ PMS.configure do |config|
161
183
  # 'Completed' => 'Done',
162
184
  # }
163
185
 
186
+ # Display name for the Testing single-select field on the pipeline project.
187
+ # config.pipeline_testing_field_name = 'Testing'
188
+
189
+ # Display labels for the canonical Testing field option keys.
190
+ # config.pipeline_testing_values = {
191
+ # active: 'Testing',
192
+ # inactive: 'Not testing',
193
+ # }
194
+
195
+ # Whether the pipeline sweep removes aged-out Completed items.
196
+ # config.pipeline_completion_purge_enabled = true
197
+
198
+ # Hours after a project item's last update at which the sweep removes it
199
+ # from the pipeline if its status is Completed.
200
+ # config.pipeline_completion_ttl_hours = 24
201
+
164
202
  # config.main_branch = 'main'
165
203
  # config.production_branch = 'production'
166
204