plan_my_stuff 0.1.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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +28 -0
  3. data/README.md +284 -0
  4. data/app/controllers/plan_my_stuff/application_controller.rb +76 -0
  5. data/app/controllers/plan_my_stuff/comments_controller.rb +82 -0
  6. data/app/controllers/plan_my_stuff/issues_controller.rb +145 -0
  7. data/app/controllers/plan_my_stuff/labels_controller.rb +30 -0
  8. data/app/controllers/plan_my_stuff/project_items_controller.rb +93 -0
  9. data/app/controllers/plan_my_stuff/projects_controller.rb +17 -0
  10. data/app/views/plan_my_stuff/comments/edit.html.erb +16 -0
  11. data/app/views/plan_my_stuff/comments/partials/_form.html.erb +32 -0
  12. data/app/views/plan_my_stuff/issues/edit.html.erb +12 -0
  13. data/app/views/plan_my_stuff/issues/index.html.erb +37 -0
  14. data/app/views/plan_my_stuff/issues/new.html.erb +7 -0
  15. data/app/views/plan_my_stuff/issues/partials/_form.html.erb +41 -0
  16. data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +23 -0
  17. data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +32 -0
  18. data/app/views/plan_my_stuff/issues/show.html.erb +58 -0
  19. data/app/views/plan_my_stuff/projects/index.html.erb +13 -0
  20. data/app/views/plan_my_stuff/projects/show.html.erb +101 -0
  21. data/config/routes.rb +25 -0
  22. data/lib/generators/plan_my_stuff/install/install_generator.rb +38 -0
  23. data/lib/generators/plan_my_stuff/install/templates/initializer.rb +106 -0
  24. data/lib/generators/plan_my_stuff/views/views_generator.rb +22 -0
  25. data/lib/plan_my_stuff/application_record.rb +39 -0
  26. data/lib/plan_my_stuff/base_metadata.rb +136 -0
  27. data/lib/plan_my_stuff/client.rb +143 -0
  28. data/lib/plan_my_stuff/comment.rb +360 -0
  29. data/lib/plan_my_stuff/comment_metadata.rb +56 -0
  30. data/lib/plan_my_stuff/configuration.rb +139 -0
  31. data/lib/plan_my_stuff/custom_fields.rb +65 -0
  32. data/lib/plan_my_stuff/engine.rb +11 -0
  33. data/lib/plan_my_stuff/errors.rb +87 -0
  34. data/lib/plan_my_stuff/issue.rb +486 -0
  35. data/lib/plan_my_stuff/issue_metadata.rb +111 -0
  36. data/lib/plan_my_stuff/label.rb +59 -0
  37. data/lib/plan_my_stuff/markdown.rb +83 -0
  38. data/lib/plan_my_stuff/metadata_parser.rb +53 -0
  39. data/lib/plan_my_stuff/project.rb +504 -0
  40. data/lib/plan_my_stuff/project_item.rb +414 -0
  41. data/lib/plan_my_stuff/test_helpers.rb +501 -0
  42. data/lib/plan_my_stuff/user_resolver.rb +61 -0
  43. data/lib/plan_my_stuff/verifier.rb +102 -0
  44. data/lib/plan_my_stuff/version.rb +19 -0
  45. data/lib/plan_my_stuff.rb +69 -0
  46. data/lib/tasks/plan_my_stuff.rake +23 -0
  47. metadata +126 -0
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlanMyStuff
4
+ class ProjectItemsController < ApplicationController
5
+ # POST /projects/:project_id/items
6
+ def create
7
+ project_number = params[:project_id].to_i
8
+
9
+ if params[:draft] == '1'
10
+ PMS::ProjectItem.create!(params[:title], draft: true, body: params[:body], project_number: project_number)
11
+ flash[:success] = 'Draft item created.'
12
+ else
13
+ issue = PMS::Issue.find(params[:issue_number].to_i)
14
+ PMS::ProjectItem.create!(issue, project_number: project_number)
15
+ flash[:success] = "Issue ##{issue.number} added to project."
16
+ end
17
+
18
+ redirect_to(plan_my_stuff.project_path(project_number))
19
+ rescue ArgumentError, PMS::Error, Octokit::Error => e
20
+ flash[:error] = e.message
21
+ redirect_to(plan_my_stuff.project_path(project_number))
22
+ end
23
+
24
+ # PATCH /projects/:project_id/items/:id/move
25
+ def move
26
+ item = find_project_item
27
+
28
+ item.move_to!(params[:status])
29
+
30
+ flash[:success] = "Item moved to #{params[:status]}."
31
+ redirect_to(plan_my_stuff.project_path(params[:project_id]))
32
+ rescue ArgumentError, PMS::Error => e
33
+ flash[:error] = e.message
34
+ redirect_to(plan_my_stuff.project_path(params[:project_id]))
35
+ end
36
+
37
+ # PATCH /projects/:project_id/items/:id/assign
38
+ def assign
39
+ item = find_project_item
40
+ assignees = parse_assignees(params[:assignee])
41
+
42
+ item.assign!(assignees)
43
+
44
+ flash[:success] = "Item assigned to #{assignees.join(', ')}."
45
+ redirect_to(plan_my_stuff.project_path(params[:project_id]))
46
+ rescue ArgumentError, PMS::Error => e
47
+ flash[:error] = e.message
48
+ redirect_to(plan_my_stuff.project_path(params[:project_id]))
49
+ end
50
+
51
+ # PATCH /projects/:project_id/items/:id/unassign
52
+ def unassign
53
+ item = find_project_item
54
+ current_assignees = item.field_values['Assignees'] || []
55
+ remaining = current_assignees - [params[:username]]
56
+
57
+ item.assign!(remaining)
58
+
59
+ flash[:success] = "#{params[:username]} unassigned."
60
+ redirect_to(plan_my_stuff.project_path(params[:project_id]))
61
+ rescue ArgumentError, PMS::Error => e
62
+ flash[:error] = e.message
63
+ redirect_to(plan_my_stuff.project_path(params[:project_id]))
64
+ end
65
+
66
+ private
67
+
68
+ # Finds the project and the item within it.
69
+ #
70
+ # @return [PlanMyStuff::ProjectItem]
71
+ #
72
+ def find_project_item
73
+ project = PMS::Project.find(params[:project_id].to_i)
74
+ item = project.items.find { |i| i.id == params[:id] }
75
+
76
+ raise(PMS::APIError, "Item not found: #{params[:id]}") unless item
77
+
78
+ item
79
+ end
80
+
81
+ # Splits a comma-separated assignees string into an array.
82
+ #
83
+ # @param assignees_string [String, nil]
84
+ #
85
+ # @return [Array<String>]
86
+ #
87
+ def parse_assignees(assignees_string)
88
+ return [] if assignees_string.blank?
89
+
90
+ assignees_string.split(',').filter_map { |a| a.strip.presence }
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlanMyStuff
4
+ class ProjectsController < ApplicationController
5
+ # GET /projects
6
+ def index
7
+ @projects = PMS::Project.list.reject(&:closed)
8
+ end
9
+
10
+ # GET /projects/:id
11
+ def show
12
+ @project = PMS::Project.find(params[:id].to_i)
13
+ @statuses = @project.statuses.pluck(:name)
14
+ @items_by_status = @project.items.group_by(&:status)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ <h1>Edit Comment</h1>
2
+
3
+ <% if flash[:error].present? %>
4
+ <p style="color: red;"><%= flash[:error] %></p>
5
+ <% end %>
6
+
7
+ <%=
8
+ render({
9
+ partial: 'plan_my_stuff/comments/partials/form',
10
+ locals: {
11
+ issue: @issue,
12
+ comment: @comment,
13
+ support_user: @support_user,
14
+ },
15
+ })
16
+ %>
@@ -0,0 +1,32 @@
1
+ <%
2
+ persisted = comment.persisted?
3
+ url =
4
+ if persisted
5
+ plan_my_stuff.issue_comment_path(issue.number, comment.id)
6
+ else
7
+ plan_my_stuff.issue_comments_path(issue.number)
8
+ end
9
+ %>
10
+ <%= form_with(url: url, method: persisted ? :patch : :post, scope: :comment) do |form| %>
11
+ <div>
12
+ <%= form.label(:body, persisted ? 'Edit comment' : 'Add a comment') %>
13
+ <%= form.text_area(:body, rows: 4, value: persisted ? comment.body_without_header : nil) %>
14
+ </div>
15
+
16
+ <% if support_user %>
17
+ <div>
18
+ <%= form.label(:visibility, 'Visibility') %>
19
+ <%=
20
+ form.select(
21
+ :visibility,
22
+ [%w[Public public], %w[Internal internal]],
23
+ selected: comment.visibility&.to_s || 'public',
24
+ )
25
+ %>
26
+ </div>
27
+ <% end %>
28
+
29
+ <div>
30
+ <%= form.submit(persisted ? 'Update Comment' : 'Post Comment') %>
31
+ </div>
32
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <h1>Edit Issue #<%= @issue.number %></h1>
2
+
3
+ <% if flash[:error].present? %>
4
+ <p style="color: red;"><%= flash[:error] %></p>
5
+ <% end %>
6
+
7
+ <%= render({ partial: 'plan_my_stuff/issues/partials/form', locals: { issue: @issue, support_user: @support_user } }) %>
8
+
9
+ <% if @support_user %>
10
+ <hr>
11
+ <%= render({ partial: 'plan_my_stuff/issues/partials/viewers', locals: { issue: @issue } }) %>
12
+ <% end %>
@@ -0,0 +1,37 @@
1
+ <h1>Issues</h1>
2
+
3
+ <% if @issues.any? %>
4
+ <table>
5
+ <thead>
6
+ <tr>
7
+ <th>#</th>
8
+ <th>Title</th>
9
+ <th>State</th>
10
+ <th>Labels</th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <% @issues.each do |issue| %>
15
+ <tr>
16
+ <td><%= issue.number %></td>
17
+ <td><%= link_to(issue.title, plan_my_stuff.issue_path(issue.number)) %></td>
18
+ <td><%= issue.state %></td>
19
+ <td><%= issue.labels.join(', ') %></td>
20
+ </tr>
21
+ <% end %>
22
+ </tbody>
23
+ </table>
24
+
25
+ <nav>
26
+ <% if @page > 1 %>
27
+ <%= link_to('Previous', plan_my_stuff.issues_path(page: @page - 1, state: @state, labels: @labels)) %>
28
+ <% end %>
29
+ <% if @issues.size == @per_page %>
30
+ <%= link_to('Next', plan_my_stuff.issues_path(page: @page + 1, state: @state, labels: @labels)) %>
31
+ <% end %>
32
+ </nav>
33
+ <% else %>
34
+ <p>No issues found.</p>
35
+ <% end %>
36
+ <br>
37
+ <%= link_to "New Issue", plan_my_stuff.new_issue_path %>
@@ -0,0 +1,7 @@
1
+ <h1>New Issue</h1>
2
+
3
+ <% if flash[:error].present? %>
4
+ <p style="color: red;"><%= flash[:error] %></p>
5
+ <% end %>
6
+
7
+ <%= render({ partial: 'plan_my_stuff/issues/partials/form', locals: { issue: @issue, support_user: @support_user } }) %>
@@ -0,0 +1,41 @@
1
+ <% persisted = issue.persisted? %>
2
+ <%= form_with(
3
+ url: persisted ? plan_my_stuff.issue_path(issue.number) : plan_my_stuff.issues_path,
4
+ method: persisted ? :patch : :post,
5
+ scope: :issue,
6
+ ) do |form| %>
7
+ <div>
8
+ <%= form.label(:title, 'Title') %>
9
+ <%= form.text_field(:title, value: issue.title) %>
10
+ </div>
11
+
12
+ <div>
13
+ <%= form.label(:body, 'Description') %>
14
+ <%= form.text_area(:body, rows: 8, value: issue.body) %>
15
+ </div>
16
+
17
+ <div>
18
+ <%= form.label(:labels, 'Labels (comma-separated)') %>
19
+ <%= form.text_field(:labels, value: Array.wrap(issue.labels).join(', ')) %>
20
+ </div>
21
+
22
+ <%# Advanced fields - uncomment to enable in your consuming app. %>
23
+ <%# <div> %>
24
+ <%#= form.label(:repo, 'Repository') %>
25
+ <%#= form.select(:repo, PlanMyStuff.configuration.repos.keys.map { |k| [k.to_s.titleize, k] }) %>
26
+ <%# </div> %>
27
+
28
+ <%# <div> %>
29
+ <%#= form.check_box(:add_to_project) %>
30
+ <%#= form.label(:add_to_project, 'Add to project') %>
31
+ <%# </div> %>
32
+
33
+ <%# <div> %>
34
+ <%#= form.label(:visibility_allowlist, 'Visibility allowlist (comma-separated user IDs)') %>
35
+ <%#= form.text_field(:visibility_allowlist) %>
36
+ <%# </div> %>
37
+
38
+ <div>
39
+ <%= form.submit(persisted ? 'Update Issue' : 'Create Issue') %>
40
+ </div>
41
+ <% end %>
@@ -0,0 +1,23 @@
1
+ <% if issue.labels.any? %>
2
+ <% issue.labels.each do |label| %>
3
+ <span>
4
+ <%= label %>
5
+ <%=
6
+ button_to(
7
+ 'x',
8
+ plan_my_stuff.issue_remove_label_path(issue.number, label),
9
+ method: :delete,
10
+ form: { style: 'display:inline' }
11
+ )
12
+ %>
13
+ </span>
14
+ <% end %>
15
+ <% else %>
16
+ <em>No labels</em>
17
+ <% end %>
18
+
19
+ <%= form_with(url: plan_my_stuff.issue_add_label_path(issue.number), method: :post, local: true) do |form| %>
20
+ <%= form.label(:label_name, 'Add label') %>
21
+ <%= form.text_field(:label_name) %>
22
+ <%= form.submit('Add') %>
23
+ <% end %>
@@ -0,0 +1,32 @@
1
+ <h2>Manage Viewers</h2>
2
+
3
+ <% viewers = issue.metadata.visibility_allowlist %>
4
+ <% if viewers.any? %>
5
+ <ul>
6
+ <% viewers.each do |viewer_id| %>
7
+ <li>
8
+ User #<%= viewer_id %>
9
+ <%=
10
+ button_to(
11
+ 'Remove',
12
+ plan_my_stuff.remove_viewer_issue_path(issue.number, viewer_id: viewer_id),
13
+ method: :delete
14
+ )
15
+ %>
16
+ </li>
17
+ <% end %>
18
+ </ul>
19
+ <% else %>
20
+ <p>No viewers added.</p>
21
+ <% end %>
22
+
23
+ <%= form_with(url: plan_my_stuff.add_viewers_issue_path(issue.number), method: :post) do |form| %>
24
+ <div>
25
+ <%= form.label(:viewer_ids, 'Add viewer IDs (comma-separated)') %>
26
+ <%= form.text_field(:viewer_ids) %>
27
+ </div>
28
+
29
+ <div>
30
+ <%= form.submit('Add Viewers') %>
31
+ </div>
32
+ <% end %>
@@ -0,0 +1,58 @@
1
+ <h1><%= @issue.title %> <small>#<%= @issue.number %></small></h1>
2
+
3
+ <p>
4
+ <%= link_to('Edit', plan_my_stuff.edit_issue_path(@issue.number)) %>
5
+ <% if @issue.state == 'open' %>
6
+ <%= button_to('Close Issue', plan_my_stuff.close_issue_path(@issue.number), method: :patch) %>
7
+ <% else %>
8
+ <%= button_to('Reopen Issue', plan_my_stuff.reopen_issue_path(@issue.number), method: :patch) %>
9
+ <% end %>
10
+ </p>
11
+
12
+ <p><strong>State:</strong> <%= @issue.state %></p>
13
+
14
+ <div>
15
+ <strong>Labels:</strong>
16
+ <%=
17
+ render({
18
+ partial: 'plan_my_stuff/issues/partials/labels',
19
+ locals: { issue: @issue },
20
+ })
21
+ %>
22
+ </div>
23
+
24
+ <div>
25
+ <%= PlanMyStuff::Markdown.render(@issue.body || '').html_safe %>
26
+ </div>
27
+
28
+ <hr>
29
+
30
+ <h2>Comments (<%= @comments.size %>)</h2>
31
+
32
+ <% if @comments.any? %>
33
+ <% @comments.each do |comment| %>
34
+ <div>
35
+ <%= PlanMyStuff::Markdown.render(comment.body || '').html_safe %>
36
+ <% if comment.pms_comment? && (@support_user || comment.metadata.created_by == @current_user_id) %>
37
+ <p>
38
+ <%= link_to('Edit', plan_my_stuff.edit_issue_comment_path(@issue.number, comment.id)) %>
39
+ </p>
40
+ <% end %>
41
+ </div>
42
+ <% end %>
43
+ <% else %>
44
+ <p>No comments yet.</p>
45
+ <% end %>
46
+
47
+ <hr>
48
+
49
+ <%=
50
+ render({
51
+ partial: 'plan_my_stuff/comments/partials/form',
52
+ locals: {
53
+ issue: @issue,
54
+ comment: PlanMyStuff::Comment.new,
55
+ support_user: @support_user,
56
+ },
57
+ })
58
+ %>
@@ -0,0 +1,13 @@
1
+ <h1>Projects</h1>
2
+
3
+ <% if @projects.any? %>
4
+ <ul>
5
+ <% @projects.each do |project| %>
6
+ <li>
7
+ <%= link_to(project.title, plan_my_stuff.project_path(project.number)) %>
8
+ </li>
9
+ <% end %>
10
+ </ul>
11
+ <% else %>
12
+ <p>No projects found.</p>
13
+ <% end %>
@@ -0,0 +1,101 @@
1
+ <h1><%= @project.title %></h1>
2
+
3
+ <% if @statuses.any? %>
4
+ <table>
5
+ <thead>
6
+ <tr>
7
+ <% @statuses.each do |status| %>
8
+ <th><%= status %></th>
9
+ <% end %>
10
+ </tr>
11
+ </thead>
12
+ <tbody>
13
+ <tr>
14
+ <% @statuses.each do |status| %>
15
+ <td>
16
+ <% items = @items_by_status[status] || [] %>
17
+ <% items.each do |item| %>
18
+ <div style="border: 1px solid black; margin: 1em">
19
+ <% if item.number.present? %>
20
+ <strong><%= link_to(item.title, plan_my_stuff.issue_path(item.number)) %></strong>
21
+ <% else %>
22
+ <strong><%= item.title %></strong>
23
+ <% end %>
24
+ <% if item.number.present? %>
25
+ <small>#<%= item.number %></small>
26
+ <% end %>
27
+
28
+ <%=
29
+ form_with(
30
+ url: plan_my_stuff.move_project_item_path(@project.number, item.id),
31
+ method: :patch,
32
+ local: true,
33
+ ) do |form|
34
+ %>
35
+ <%= form.select(:status, @statuses, { selected: status }, { onchange: 'this.form.requestSubmit();' }) %>
36
+ <% end %>
37
+
38
+ <% assignees = item.field_values['Assignees'] || [] %>
39
+ <% if assignees.any? %>
40
+ <div>
41
+ <% assignees.each do |username| %>
42
+ <span>
43
+ <%= username %>
44
+ <%=
45
+ form_with(
46
+ url: plan_my_stuff.unassign_project_item_path(@project.number, item.id),
47
+ method: :patch,
48
+ local: true,
49
+ style: 'display: inline',
50
+ ) do |form|
51
+ %>
52
+ <%= form.hidden_field(:username, value: username) %>
53
+ <%= form.submit('×', title: "Unassign #{username}") %>
54
+ <% end %>
55
+ </span>
56
+ <% end %>
57
+ </div>
58
+ <% end %>
59
+
60
+ <%=
61
+ form_with(
62
+ url: plan_my_stuff.assign_project_item_path(@project.number, item.id),
63
+ method: :patch,
64
+ local: true,
65
+ ) do |form|
66
+ %>
67
+ <%= form.text_field(:assignee, placeholder: 'GitHub username') %>
68
+ <%= form.submit('Assign') %>
69
+ <% end %>
70
+ </div>
71
+ <% end %>
72
+ <% if items.empty? %>
73
+ <em>No items</em>
74
+ <% end %>
75
+ </td>
76
+ <% end %>
77
+ </tr>
78
+ </tbody>
79
+ </table>
80
+ <% else %>
81
+ <p>No statuses configured for this project.</p>
82
+ <% end %>
83
+
84
+ <h2>Add Item</h2>
85
+
86
+ <h3>Add Existing Issue</h3>
87
+ <%= form_with(url: plan_my_stuff.project_items_path(@project.number), method: :post, local: true) do |form| %>
88
+ <%= form.label(:issue_number, 'Issue number') %>
89
+ <%= form.number_field(:issue_number, min: 1, required: true) %>
90
+ <%= form.submit('Add Issue') %>
91
+ <% end %>
92
+
93
+ <h3>Create Draft Item</h3>
94
+ <%= form_with(url: plan_my_stuff.project_items_path(@project.number), method: :post, local: true) do |form| %>
95
+ <%= form.hidden_field(:draft, value: '1') %>
96
+ <%= form.label(:title, 'Title') %>
97
+ <%= form.text_field(:title, required: true) %>
98
+ <%= form.label(:body, 'Body') %>
99
+ <%= form.text_area(:body) %>
100
+ <%= form.submit('Create Draft') %>
101
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ PlanMyStuff::Engine.routes.draw do
4
+ resources :issues, except: %i[destroy] do
5
+ member do
6
+ patch :close
7
+ patch :reopen
8
+ post :add_viewers
9
+ delete :remove_viewer
10
+ end
11
+ resources :comments, only: %i[create edit update]
12
+ post 'labels', to: 'labels#add_to_issue', as: :add_label
13
+ delete 'labels/:name', to: 'labels#remove_from_issue', as: :remove_label
14
+ end
15
+
16
+ resources :projects, only: %i[index show] do
17
+ resources :items, only: %i[create], controller: 'project_items' do
18
+ member do
19
+ patch :move
20
+ patch :assign
21
+ patch :unassign
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlanMyStuff
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ # @return [void]
9
+ def copy_initializer
10
+ template('initializer.rb', 'config/initializers/plan_my_stuff.rb')
11
+ end
12
+
13
+ # @return [void]
14
+ def add_route
15
+ mount_line = " mount PlanMyStuff::Engine, at: '/tickets'\n"
16
+ route_file = 'config/routes.rb'
17
+
18
+ if File.exist?(route_file) && File.read(route_file).include?('PlanMyStuff::Engine')
19
+ say('Route already mounted, skipping.', :yellow)
20
+ return
21
+ end
22
+
23
+ inject_into_file(route_file, mount_line, after: "Rails.application.routes.draw do\n")
24
+ end
25
+
26
+ # @return [void]
27
+ def show_next_steps
28
+ say('')
29
+ say('PlanMyStuff installed.', :green)
30
+ say('')
31
+ say('Next steps:', :yellow)
32
+ say(' 1. Add your GitHub PAT to credentials: rails credentials:edit')
33
+ say(' 2. Run `rails g plan_my_stuff:views` to copy view templates (optional)')
34
+ say('')
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ PMS.configure do |config|
4
+ # --------------------------------------------------------------------------
5
+ # Authentication (required)
6
+ # --------------------------------------------------------------------------
7
+ # GitHub PAT with `repo` and `project` scopes. All API calls use this token.
8
+ config.access_token = Rails.application.credentials.dig(:plan_my_stuff, :github_token)
9
+
10
+ # --------------------------------------------------------------------------
11
+ # Organization (required)
12
+ # --------------------------------------------------------------------------
13
+ config.organization = 'YourOrganization'
14
+
15
+ # --------------------------------------------------------------------------
16
+ # Repositories
17
+ # --------------------------------------------------------------------------
18
+ # Register named repos for shorthand access throughout the gem.
19
+ # config.repos[:element] = 'YourOrganization/Element'
20
+ # config.repos[:underwriter] = 'YourOrganization/Underwriter'
21
+ # config.default_repo = :element
22
+
23
+ # --------------------------------------------------------------------------
24
+ # Projects
25
+ # --------------------------------------------------------------------------
26
+ # Default GitHub Projects V2 number for add_to_project calls.
27
+ # config.default_project_number = 14
28
+
29
+ # --------------------------------------------------------------------------
30
+ # App identity
31
+ # --------------------------------------------------------------------------
32
+ # Name of this application, stored in issue/comment metadata.
33
+ # config.app_name = 'MyApp'
34
+
35
+ # --------------------------------------------------------------------------
36
+ # User integration
37
+ # --------------------------------------------------------------------------
38
+ # Your app's user model class name (constantized for lookups).
39
+ # config.user_class = 'User'
40
+
41
+ # Method called on user object to get display name for comment headers.
42
+ # config.display_name_method = :full_name
43
+
44
+ # Method called on user object to extract the app-side user ID.
45
+ # config.user_id_method = :id
46
+
47
+ # Determines if a user is support staff. Symbol (method name called on user)
48
+ # or Proc that receives the user object and returns a boolean.
49
+ # config.support_method = :support?
50
+ # config.support_method = ->(user) { user.role.in?(%w[support admin]) }
51
+
52
+ # --------------------------------------------------------------------------
53
+ # Engine authentication
54
+ # --------------------------------------------------------------------------
55
+ # Block executed as a before_action on all PlanMyStuff engine controllers.
56
+ # Use this to require authentication before accessing the mounted engine.
57
+ #
58
+ # config.authenticate_with do
59
+ # redirect_to main_app.login_path unless current_user
60
+ # end
61
+
62
+ # --------------------------------------------------------------------------
63
+ # Markdown rendering
64
+ # --------------------------------------------------------------------------
65
+ # :commonmarker (default) or :redcarpet -- the chosen gem must be in your Gemfile.
66
+ # config.markdown_renderer = :commonmarker
67
+
68
+ # --------------------------------------------------------------------------
69
+ # URL prefix
70
+ # --------------------------------------------------------------------------
71
+ # Prefix for building user-facing ticket URLs in your app.
72
+ # config.issues_url_prefix = 'https://myapp.example.com/tickets/'
73
+
74
+ # --------------------------------------------------------------------------
75
+ # Request gateway
76
+ # --------------------------------------------------------------------------
77
+ # Proc returning boolean, or nil (always send). When it returns false the
78
+ # request is deferred to a background job instead of hitting GitHub.
79
+ # config.should_send_request = -> { !MaintenanceMode.active? }
80
+
81
+ # Map of action type to job class name for deferred requests.
82
+ # config.job_classes = {
83
+ # create_ticket: 'PmsCreateTicketJob',
84
+ # post_comment: 'PmsPostCommentJob',
85
+ # update_status: 'PmsUpdateStatusJob'
86
+ # }
87
+
88
+ # Custom notifier for deferred requests (proc/class), or nil to use
89
+ # the built-in DeferredMailer.
90
+ # config.deferred_notifier = nil
91
+ # config.deferred_email_from = 'noreply@example.com'
92
+ # config.deferred_email_to = 'it-support@example.com'
93
+
94
+ # --------------------------------------------------------------------------
95
+ # Custom fields
96
+ # --------------------------------------------------------------------------
97
+ # App-defined fields stored in issue/comment metadata. Keys are field names,
98
+ # values are hashes with :type and :required.
99
+ #
100
+ # Supported types: :string, :integer, :boolean, :array, :hash
101
+ # required: true means the key must be present (value can be nil, [], etc.)
102
+ #
103
+ config.custom_fields = {
104
+ notification_recipients: { type: :array, required: true },
105
+ }
106
+ end