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.
- checksums.yaml +7 -0
- data/LICENSE +28 -0
- data/README.md +284 -0
- data/app/controllers/plan_my_stuff/application_controller.rb +76 -0
- data/app/controllers/plan_my_stuff/comments_controller.rb +82 -0
- data/app/controllers/plan_my_stuff/issues_controller.rb +145 -0
- data/app/controllers/plan_my_stuff/labels_controller.rb +30 -0
- data/app/controllers/plan_my_stuff/project_items_controller.rb +93 -0
- data/app/controllers/plan_my_stuff/projects_controller.rb +17 -0
- data/app/views/plan_my_stuff/comments/edit.html.erb +16 -0
- data/app/views/plan_my_stuff/comments/partials/_form.html.erb +32 -0
- data/app/views/plan_my_stuff/issues/edit.html.erb +12 -0
- data/app/views/plan_my_stuff/issues/index.html.erb +37 -0
- data/app/views/plan_my_stuff/issues/new.html.erb +7 -0
- data/app/views/plan_my_stuff/issues/partials/_form.html.erb +41 -0
- data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +23 -0
- data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +32 -0
- data/app/views/plan_my_stuff/issues/show.html.erb +58 -0
- data/app/views/plan_my_stuff/projects/index.html.erb +13 -0
- data/app/views/plan_my_stuff/projects/show.html.erb +101 -0
- data/config/routes.rb +25 -0
- data/lib/generators/plan_my_stuff/install/install_generator.rb +38 -0
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +106 -0
- data/lib/generators/plan_my_stuff/views/views_generator.rb +22 -0
- data/lib/plan_my_stuff/application_record.rb +39 -0
- data/lib/plan_my_stuff/base_metadata.rb +136 -0
- data/lib/plan_my_stuff/client.rb +143 -0
- data/lib/plan_my_stuff/comment.rb +360 -0
- data/lib/plan_my_stuff/comment_metadata.rb +56 -0
- data/lib/plan_my_stuff/configuration.rb +139 -0
- data/lib/plan_my_stuff/custom_fields.rb +65 -0
- data/lib/plan_my_stuff/engine.rb +11 -0
- data/lib/plan_my_stuff/errors.rb +87 -0
- data/lib/plan_my_stuff/issue.rb +486 -0
- data/lib/plan_my_stuff/issue_metadata.rb +111 -0
- data/lib/plan_my_stuff/label.rb +59 -0
- data/lib/plan_my_stuff/markdown.rb +83 -0
- data/lib/plan_my_stuff/metadata_parser.rb +53 -0
- data/lib/plan_my_stuff/project.rb +504 -0
- data/lib/plan_my_stuff/project_item.rb +414 -0
- data/lib/plan_my_stuff/test_helpers.rb +501 -0
- data/lib/plan_my_stuff/user_resolver.rb +61 -0
- data/lib/plan_my_stuff/verifier.rb +102 -0
- data/lib/plan_my_stuff/version.rb +19 -0
- data/lib/plan_my_stuff.rb +69 -0
- data/lib/tasks/plan_my_stuff.rake +23 -0
- 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,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
|