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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -1
- data/CONFIGURATION.md +351 -0
- data/README.md +100 -103
- data/app/controllers/plan_my_stuff/application_controller.rb +22 -3
- data/app/controllers/plan_my_stuff/comments_controller.rb +14 -16
- data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +23 -13
- data/app/controllers/plan_my_stuff/issues/closures_controller.rb +7 -5
- data/app/controllers/plan_my_stuff/issues/links_controller.rb +14 -18
- data/app/controllers/plan_my_stuff/issues/takes_controller.rb +99 -28
- data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +13 -5
- data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +7 -5
- data/app/controllers/plan_my_stuff/issues_controller.rb +24 -28
- data/app/controllers/plan_my_stuff/labels_controller.rb +21 -5
- data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +13 -6
- data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +5 -4
- data/app/controllers/plan_my_stuff/project_items_controller.rb +30 -5
- data/app/controllers/plan_my_stuff/projects_controller.rb +16 -16
- data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +21 -11
- data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +9 -4
- data/app/controllers/plan_my_stuff/testing_projects_controller.rb +30 -14
- data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +50 -17
- data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +32 -49
- data/app/jobs/plan_my_stuff/application_job.rb +2 -3
- data/app/jobs/plan_my_stuff/reminders_sweep_job.rb +15 -22
- data/app/views/plan_my_stuff/comments/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/comments/partials/_form.html.erb +1 -0
- data/app/views/plan_my_stuff/issues/edit.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/index.html.erb +2 -2
- data/app/views/plan_my_stuff/issues/new.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +23 -2
- data/app/views/plan_my_stuff/issues/partials/_form.html.erb +1 -0
- data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +2 -1
- data/app/views/plan_my_stuff/issues/partials/_links.html.erb +50 -7
- data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +2 -1
- data/app/views/plan_my_stuff/issues/show.html.erb +5 -2
- data/app/views/plan_my_stuff/partials/_flash.html.erb +3 -0
- data/app/views/plan_my_stuff/projects/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/projects/index.html.erb +1 -1
- data/app/views/plan_my_stuff/projects/new.html.erb +1 -3
- data/app/views/plan_my_stuff/projects/partials/_form.html.erb +1 -0
- data/app/views/plan_my_stuff/projects/show.html.erb +13 -3
- data/app/views/plan_my_stuff/testing_project_items/new.html.erb +1 -3
- data/app/views/plan_my_stuff/testing_project_items/results/new.html.erb +1 -3
- data/app/views/plan_my_stuff/testing_projects/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/testing_projects/new.html.erb +1 -3
- data/app/views/plan_my_stuff/testing_projects/partials/_form.html.erb +4 -3
- data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +1 -0
- data/app/views/plan_my_stuff/testing_projects/partials/items/_form.html.erb +1 -0
- data/app/views/plan_my_stuff/testing_projects/show.html.erb +2 -2
- data/config/routes.rb +2 -2
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +52 -14
- data/lib/plan_my_stuff/approval.rb +12 -4
- data/lib/plan_my_stuff/aws_sns_simulator.rb +12 -6
- data/lib/plan_my_stuff/base_metadata.rb +4 -15
- data/lib/plan_my_stuff/base_project.rb +68 -55
- data/lib/plan_my_stuff/base_project_item.rb +62 -57
- data/lib/plan_my_stuff/base_project_metadata.rb +1 -1
- data/lib/plan_my_stuff/client.rb +136 -48
- data/lib/plan_my_stuff/comment.rb +59 -57
- data/lib/plan_my_stuff/comment_metadata.rb +1 -1
- data/lib/plan_my_stuff/configuration.rb +93 -93
- data/lib/plan_my_stuff/errors.rb +10 -10
- data/lib/plan_my_stuff/graphql/queries.rb +1 -1
- data/lib/plan_my_stuff/issue.rb +471 -333
- data/lib/plan_my_stuff/issue_metadata.rb +10 -10
- data/lib/plan_my_stuff/label.rb +34 -18
- data/lib/plan_my_stuff/link.rb +15 -15
- data/lib/plan_my_stuff/markdown.rb +12 -6
- data/lib/plan_my_stuff/metadata_parser.rb +3 -1
- data/lib/plan_my_stuff/notifications.rb +1 -1
- data/lib/plan_my_stuff/pipeline/completed_sweep.rb +2 -2
- data/lib/plan_my_stuff/pipeline/issue_linker.rb +1 -1
- data/lib/plan_my_stuff/pipeline.rb +61 -83
- data/lib/plan_my_stuff/project.rb +4 -4
- data/lib/plan_my_stuff/project_item_metadata.rb +1 -1
- data/lib/plan_my_stuff/project_metadata.rb +1 -1
- data/lib/plan_my_stuff/reminders/closer.rb +1 -1
- data/lib/plan_my_stuff/reminders/fire.rb +3 -3
- data/lib/plan_my_stuff/reminders/sweep.rb +4 -4
- data/lib/plan_my_stuff/repo.rb +12 -6
- data/lib/plan_my_stuff/test_helpers.rb +11 -11
- data/lib/plan_my_stuff/testing_project.rb +12 -11
- data/lib/plan_my_stuff/testing_project_item.rb +11 -9
- data/lib/plan_my_stuff/testing_project_metadata.rb +2 -2
- data/lib/plan_my_stuff/version.rb +1 -1
- data/lib/plan_my_stuff/webhook_replayer.rb +14 -2
- data/lib/plan_my_stuff.rb +26 -2
- data/lib/tasks/plan_my_stuff.rake +33 -20
- metadata +4 -2
|
@@ -8,27 +8,29 @@ module PlanMyStuff
|
|
|
8
8
|
# POST /issues/:issue_id/closure -> create (closes)
|
|
9
9
|
# DELETE /issues/:issue_id/closure -> destroy (reopens)
|
|
10
10
|
#
|
|
11
|
-
class ClosuresController < ApplicationController
|
|
11
|
+
class ClosuresController < PlanMyStuff::ApplicationController
|
|
12
12
|
# POST /issues/:issue_id/closure
|
|
13
13
|
def create
|
|
14
|
-
issue =
|
|
14
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
15
15
|
issue.update!(state: :closed)
|
|
16
16
|
|
|
17
17
|
flash[:success] = 'Issue was successfully closed.'
|
|
18
18
|
redirect_to(plan_my_stuff.issue_path(issue.number, repo: issue.repo.full_name))
|
|
19
|
-
rescue
|
|
19
|
+
rescue PlanMyStuff::Error, ArgumentError => e
|
|
20
|
+
pms_handle_rescue(e)
|
|
20
21
|
flash[:error] = e.message
|
|
21
22
|
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
# DELETE /issues/:issue_id/closure
|
|
25
26
|
def destroy
|
|
26
|
-
issue =
|
|
27
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
27
28
|
issue.update!(state: :open)
|
|
28
29
|
|
|
29
30
|
flash[:success] = 'Issue was successfully reopened.'
|
|
30
31
|
redirect_to(plan_my_stuff.issue_path(issue.number, repo: issue.repo.full_name))
|
|
31
|
-
rescue
|
|
32
|
+
rescue PlanMyStuff::Error, ArgumentError => e
|
|
33
|
+
pms_handle_rescue(e)
|
|
32
34
|
flash[:error] = e.message
|
|
33
35
|
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
34
36
|
end
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
4
|
module Issues
|
|
5
|
-
# CRUD for ticket relationships: +:related+ (metadata-backed) and
|
|
6
|
-
# +:
|
|
7
|
-
# (native GitHub APIs). Backs the links panel on the issue show view.
|
|
5
|
+
# CRUD for ticket relationships: +:related+ (metadata-backed) and +:blocked_by+ / +:parent+ / +:sub_ticket+ /
|
|
6
|
+
# +:duplicate_of+ (native GitHub APIs). Backs the links panel on the issue show view.
|
|
8
7
|
#
|
|
9
8
|
# POST /issues/:issue_id/links -> create (adds a link)
|
|
10
9
|
# DELETE /issues/:issue_id/links/:id -> destroy (removes a link)
|
|
11
10
|
#
|
|
12
|
-
class LinksController < ApplicationController
|
|
11
|
+
class LinksController < PlanMyStuff::ApplicationController
|
|
13
12
|
NATIVE_DISPATCH = {
|
|
14
13
|
'related' => { add: :add_related!, remove: :remove_related! },
|
|
15
14
|
'blocked_by' => { add: :add_blocker!, remove: :remove_blocker! },
|
|
@@ -26,12 +25,13 @@ module PlanMyStuff
|
|
|
26
25
|
return
|
|
27
26
|
end
|
|
28
27
|
|
|
29
|
-
issue =
|
|
28
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
30
29
|
link = add_link(issue, type)
|
|
31
30
|
|
|
32
31
|
flash[:success] = "Linked #{link}"
|
|
33
32
|
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
34
|
-
rescue
|
|
33
|
+
rescue PlanMyStuff::ValidationError, ActiveModel::ValidationError, ArgumentError => e
|
|
34
|
+
pms_handle_rescue(e)
|
|
35
35
|
flash[:error] = e.message
|
|
36
36
|
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
37
37
|
end
|
|
@@ -44,12 +44,13 @@ module PlanMyStuff
|
|
|
44
44
|
return
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
issue =
|
|
47
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
48
48
|
remove_link(issue, type, repo: repo, number: number)
|
|
49
49
|
|
|
50
50
|
flash[:success] = "Unlinked #{repo}##{number}"
|
|
51
51
|
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
52
|
-
rescue
|
|
52
|
+
rescue PlanMyStuff::ValidationError, ActiveModel::ValidationError, ArgumentError => e
|
|
53
|
+
pms_handle_rescue(e)
|
|
53
54
|
flash[:error] = e.message
|
|
54
55
|
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
55
56
|
end
|
|
@@ -61,21 +62,18 @@ module PlanMyStuff
|
|
|
61
62
|
params.require(:link).permit(:type, :issue_number, :repo)
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
#
|
|
65
|
-
# is not defined for +:duplicate_of+ (reopen via GitHub).
|
|
65
|
+
# All link mutations require a support user. +destroy+ is not defined for +:duplicate_of+ (reopen via GitHub).
|
|
66
66
|
#
|
|
67
67
|
# @return [Boolean]
|
|
68
68
|
#
|
|
69
69
|
def dispatch_allowed?(type, action)
|
|
70
70
|
entry = NATIVE_DISPATCH[type]
|
|
71
71
|
return false if entry.nil? || entry[action].nil?
|
|
72
|
-
# FIXME: should non-support users be able to alter related links?
|
|
73
|
-
return true if type == 'related'
|
|
74
72
|
|
|
75
73
|
support_user?
|
|
76
74
|
end
|
|
77
75
|
|
|
78
|
-
# @return [
|
|
76
|
+
# @return [PlanMyStuff::Link]
|
|
79
77
|
def add_link(issue, type)
|
|
80
78
|
method = NATIVE_DISPATCH.dig(type, :add)
|
|
81
79
|
target = {
|
|
@@ -105,11 +103,9 @@ module PlanMyStuff
|
|
|
105
103
|
end
|
|
106
104
|
end
|
|
107
105
|
|
|
108
|
-
# Parses +"{type}:{owner/repo}:{number}"+ out of +params[:id]+.
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
# path params (anti-path-traversal), so the repo portion can
|
|
112
|
-
# still contain +%2F+. Decode before splitting.
|
|
106
|
+
# Parses +"{type}:{owner/repo}:{number}"+ out of +params[:id]+. The route helper URL-encodes outgoing segments,
|
|
107
|
+
# but Rack's default path decoder intentionally leaves +%2F+ escaped in path params (anti-path-traversal), so
|
|
108
|
+
# the repo portion can still contain +%2F+. Decode before splitting.
|
|
113
109
|
#
|
|
114
110
|
# @param id [String]
|
|
115
111
|
#
|
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
4
|
module Issues
|
|
5
|
-
# Moves an issue's pipeline ProjectItem to "Started" via
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# picking up work on an issue they've been assigned to.
|
|
5
|
+
# Moves an issue's pipeline ProjectItem to "Started" via +PlanMyStuff::Pipeline.take!+. Backs the "Take" button on
|
|
6
|
+
# the mounted issue show view (T-RC-017). The primary UI path for a dev picking up work on an issue they've been
|
|
7
|
+
# assigned to.
|
|
9
8
|
#
|
|
10
|
-
class TakesController < ApplicationController
|
|
11
|
-
before_action :require_support_user
|
|
9
|
+
class TakesController < PlanMyStuff::ApplicationController
|
|
10
|
+
before_action :require_support_user
|
|
12
11
|
|
|
13
12
|
# POST /issues/:issue_id/take
|
|
14
13
|
def create
|
|
15
14
|
issue_number = params[:issue_id].to_i
|
|
16
15
|
repo = params[:repo]
|
|
17
16
|
|
|
17
|
+
issue = PlanMyStuff::Issue.find(issue_number, repo: repo)
|
|
18
|
+
guard_already_taken!(issue)
|
|
19
|
+
|
|
18
20
|
project_item = PlanMyStuff::Pipeline::IssueLinker.find_project_item(issue_number)
|
|
19
|
-
project_item ||= add_to_pipeline(
|
|
21
|
+
project_item ||= add_to_pipeline(issue)
|
|
20
22
|
|
|
21
23
|
PlanMyStuff::Pipeline.take!(project_item)
|
|
22
24
|
assign_current_user(project_item)
|
|
@@ -24,18 +26,49 @@ module PlanMyStuff
|
|
|
24
26
|
|
|
25
27
|
redirect_to(plan_my_stuff.issue_path(issue_number, repo: repo))
|
|
26
28
|
rescue ArgumentError, PlanMyStuff::Error => e
|
|
29
|
+
pms_handle_rescue(e)
|
|
30
|
+
flash[:error] = e.message
|
|
31
|
+
redirect_to(plan_my_stuff.issue_path(issue_number, repo: repo))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# DELETE /issues/:issue_id/take
|
|
35
|
+
def destroy
|
|
36
|
+
issue_number = params[:issue_id].to_i
|
|
37
|
+
repo = params[:repo]
|
|
38
|
+
|
|
39
|
+
issue = PlanMyStuff::Issue.find(issue_number, repo: repo)
|
|
40
|
+
login = current_user_login
|
|
41
|
+
guard_release!(issue, login)
|
|
42
|
+
|
|
43
|
+
remaining = issue.assignees - [login]
|
|
44
|
+
project_item = PlanMyStuff::Pipeline::IssueLinker.find_project_item(issue_number)
|
|
45
|
+
|
|
46
|
+
if project_item.present?
|
|
47
|
+
if remaining.empty?
|
|
48
|
+
issue.update!(assignees: [])
|
|
49
|
+
project_item.destroy!
|
|
50
|
+
else
|
|
51
|
+
project_item.assign!(remaining)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
issue.update!(assignees: remaining)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
flash[:success] = "Issue ##{issue_number} released."
|
|
58
|
+
redirect_to(plan_my_stuff.issue_path(issue_number, repo: repo))
|
|
59
|
+
rescue ArgumentError, PlanMyStuff::Error => e
|
|
60
|
+
pms_handle_rescue(e)
|
|
27
61
|
flash[:error] = e.message
|
|
28
62
|
redirect_to(plan_my_stuff.issue_path(issue_number, repo: repo))
|
|
29
63
|
end
|
|
30
64
|
|
|
31
65
|
private
|
|
32
66
|
|
|
33
|
-
# Redirects non-support users back to the issue page. Mirrors
|
|
34
|
-
# +Issues::ViewersController+'s authorization check.
|
|
67
|
+
# Redirects non-support users back to the issue page. Mirrors +Issues::ViewersController+'s authorization check.
|
|
35
68
|
#
|
|
36
69
|
# @return [void]
|
|
37
70
|
#
|
|
38
|
-
def require_support_user
|
|
71
|
+
def require_support_user
|
|
39
72
|
return if support_user?
|
|
40
73
|
|
|
41
74
|
redirect_to_unauthorized(
|
|
@@ -43,46 +76,84 @@ module PlanMyStuff
|
|
|
43
76
|
)
|
|
44
77
|
end
|
|
45
78
|
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
79
|
+
# Best-effort race guard for two users clicking Take on the same issue. GitHub allows multiple assignees so a
|
|
80
|
+
# naive second take would silently pile on; refuse instead and let the second user see who already has it.
|
|
81
|
+
#
|
|
82
|
+
# @raise [PlanMyStuff::Error] if the issue already has assignees
|
|
49
83
|
#
|
|
50
|
-
# @param
|
|
51
|
-
#
|
|
84
|
+
# @param issue [PlanMyStuff::Issue]
|
|
85
|
+
#
|
|
86
|
+
# @return [void]
|
|
87
|
+
#
|
|
88
|
+
def guard_already_taken!(issue)
|
|
89
|
+
return if issue.assignees.blank?
|
|
90
|
+
|
|
91
|
+
raise(
|
|
92
|
+
PlanMyStuff::Error,
|
|
93
|
+
"Issue ##{issue.number} is already taken by @#{issue.assignees.join(', @')}.",
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Adds an issue to the pipeline project so +Pipeline.take!+ has something to operate on when the user clicks
|
|
98
|
+
# "Take" on an issue that hasn't been submitted to the pipeline yet.
|
|
99
|
+
#
|
|
100
|
+
# @param issue [PlanMyStuff::Issue]
|
|
52
101
|
#
|
|
53
102
|
# @return [PlanMyStuff::ProjectItem]
|
|
54
103
|
#
|
|
55
|
-
def add_to_pipeline(
|
|
56
|
-
|
|
57
|
-
pipeline_number = PlanMyStuff::Pipeline.resolve_pipeline_project_number
|
|
104
|
+
def add_to_pipeline(issue)
|
|
105
|
+
pipeline_number = PlanMyStuff::Pipeline.resolve_pipeline_project_number!
|
|
58
106
|
PlanMyStuff::ProjectItem.create!(issue, project_number: pipeline_number)
|
|
59
107
|
end
|
|
60
108
|
|
|
61
|
-
# Looks up the current user's GitHub login in
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
# logged and surfaced as a flash warning but do not revert the
|
|
65
|
-
# status move.
|
|
109
|
+
# Looks up the current user's GitHub login in +config.github_login_for+ (a +{user_id => login}+ hash) and
|
|
110
|
+
# assigns them on the project item. Assignment failures are logged and surfaced as a flash warning but do not
|
|
111
|
+
# revert the status move.
|
|
66
112
|
#
|
|
67
113
|
# @param project_item [PlanMyStuff::ProjectItem]
|
|
68
114
|
#
|
|
69
115
|
# @return [void]
|
|
70
116
|
#
|
|
71
117
|
def assign_current_user(project_item)
|
|
72
|
-
|
|
73
|
-
user_id = user.present? ? PMS::UserResolver.user_id(user) : nil
|
|
74
|
-
login = PlanMyStuff.configuration.github_login_for[user_id]
|
|
118
|
+
login = current_user_login
|
|
75
119
|
|
|
76
120
|
if login.blank?
|
|
77
|
-
Rails.logger.info(
|
|
121
|
+
Rails.logger.info('[PlanMyStuff] No github_login_for mapping for current user; skipping Take assignment')
|
|
78
122
|
return
|
|
79
123
|
end
|
|
80
124
|
|
|
81
125
|
project_item.assign!(login)
|
|
82
126
|
rescue PlanMyStuff::Error => e
|
|
83
|
-
Rails.logger.warn("[
|
|
127
|
+
Rails.logger.warn("[PlanMyStuff] Take assignment failed: #{e.message}")
|
|
84
128
|
flash[:warning] = "Issue taken but assignment failed: #{e.message}"
|
|
85
129
|
end
|
|
130
|
+
|
|
131
|
+
# GitHub login for the current user via +config.github_login_for+ (a +{user_id => login}+ hash), or +nil+ if the
|
|
132
|
+
# user is unmapped or unauthenticated.
|
|
133
|
+
#
|
|
134
|
+
# @return [String, nil]
|
|
135
|
+
#
|
|
136
|
+
def current_user_login
|
|
137
|
+
user = pms_current_user
|
|
138
|
+
user_id = user.present? ? PlanMyStuff::UserResolver.user_id(user) : nil
|
|
139
|
+
PlanMyStuff.configuration.github_login_for[user_id]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Refuses the release when the current user has no login mapping or is not in the issue's assignees. Mirrors the
|
|
143
|
+
# +guard_already_taken!+ check on +#create+.
|
|
144
|
+
#
|
|
145
|
+
# @raise [PlanMyStuff::Error] if the user cannot release the issue
|
|
146
|
+
#
|
|
147
|
+
# @param issue [PlanMyStuff::Issue]
|
|
148
|
+
# @param login [String, nil]
|
|
149
|
+
#
|
|
150
|
+
# @return [void]
|
|
151
|
+
#
|
|
152
|
+
def guard_release!(issue, login)
|
|
153
|
+
raise(PlanMyStuff::Error, 'No GitHub login mapping for current user; cannot release.') if login.blank?
|
|
154
|
+
|
|
155
|
+
raise(PlanMyStuff::Error, "Issue ##{issue.number} is not assigned to you.") if issue.assignees.exclude?(login)
|
|
156
|
+
end
|
|
86
157
|
end
|
|
87
158
|
end
|
|
88
159
|
end
|
|
@@ -8,7 +8,7 @@ module PlanMyStuff
|
|
|
8
8
|
# POST /issues/:issue_id/viewers -> create (adds viewer(s))
|
|
9
9
|
# DELETE /issues/:issue_id/viewers/:id -> destroy (removes a viewer)
|
|
10
10
|
#
|
|
11
|
-
class ViewersController < ApplicationController
|
|
11
|
+
class ViewersController < PlanMyStuff::ApplicationController
|
|
12
12
|
# POST /issues/:issue_id/viewers
|
|
13
13
|
def create
|
|
14
14
|
unless support_user?
|
|
@@ -23,11 +23,15 @@ module PlanMyStuff
|
|
|
23
23
|
return
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
issue =
|
|
27
|
-
issue.add_viewers(user_ids: viewer_ids, user: pms_current_user)
|
|
26
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
27
|
+
issue.add_viewers!(user_ids: viewer_ids, user: pms_current_user)
|
|
28
28
|
|
|
29
29
|
flash[:success] = 'Viewers were successfully added.'
|
|
30
30
|
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id], repo: params[:repo]))
|
|
31
|
+
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
32
|
+
pms_handle_rescue(e)
|
|
33
|
+
flash[:error] = e.message
|
|
34
|
+
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id], repo: params[:repo]))
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
# DELETE /issues/:issue_id/viewers/:id
|
|
@@ -37,11 +41,15 @@ module PlanMyStuff
|
|
|
37
41
|
return
|
|
38
42
|
end
|
|
39
43
|
|
|
40
|
-
issue =
|
|
41
|
-
issue.remove_viewers(user_ids: [params[:id].to_i], user: pms_current_user)
|
|
44
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
45
|
+
issue.remove_viewers!(user_ids: [params[:id].to_i], user: pms_current_user)
|
|
42
46
|
|
|
43
47
|
flash[:success] = 'Viewer was successfully removed.'
|
|
44
48
|
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id], repo: params[:repo]))
|
|
49
|
+
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
50
|
+
pms_handle_rescue(e)
|
|
51
|
+
flash[:error] = e.message
|
|
52
|
+
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id], repo: params[:repo]))
|
|
45
53
|
end
|
|
46
54
|
end
|
|
47
55
|
end
|
|
@@ -8,7 +8,7 @@ module PlanMyStuff
|
|
|
8
8
|
# POST /issues/:issue_id/waiting -> create (enters waiting-on-user)
|
|
9
9
|
# DELETE /issues/:issue_id/waiting -> destroy (clears waiting-on-user)
|
|
10
10
|
#
|
|
11
|
-
class WaitingsController < ApplicationController
|
|
11
|
+
class WaitingsController < PlanMyStuff::ApplicationController
|
|
12
12
|
# POST /issues/:issue_id/waiting
|
|
13
13
|
def create
|
|
14
14
|
unless support_user?
|
|
@@ -16,12 +16,13 @@ module PlanMyStuff
|
|
|
16
16
|
return
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
issue =
|
|
19
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
20
20
|
issue.enter_waiting_on_user!(user: pms_current_user)
|
|
21
21
|
|
|
22
22
|
flash[:success] = 'Issue marked as waiting on user reply.'
|
|
23
23
|
redirect_to(plan_my_stuff.issue_path(issue.number, repo: issue.repo.full_name))
|
|
24
|
-
rescue
|
|
24
|
+
rescue PlanMyStuff::Error, ArgumentError => e
|
|
25
|
+
pms_handle_rescue(e)
|
|
25
26
|
flash[:error] = e.message
|
|
26
27
|
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
27
28
|
end
|
|
@@ -33,12 +34,13 @@ module PlanMyStuff
|
|
|
33
34
|
return
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
issue =
|
|
37
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
37
38
|
issue.clear_waiting_on_user!
|
|
38
39
|
|
|
39
40
|
flash[:success] = 'Waiting-on-user state cleared.'
|
|
40
41
|
redirect_to(plan_my_stuff.issue_path(issue.number, repo: issue.repo.full_name))
|
|
41
|
-
rescue
|
|
42
|
+
rescue PlanMyStuff::Error, ArgumentError => e
|
|
43
|
+
pms_handle_rescue(e)
|
|
42
44
|
flash[:error] = e.message
|
|
43
45
|
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
44
46
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
class IssuesController < ApplicationController
|
|
4
|
+
class IssuesController < PlanMyStuff::ApplicationController
|
|
5
5
|
# GET /issues
|
|
6
6
|
def index
|
|
7
7
|
@page = (params[:page] || 1).to_i
|
|
@@ -10,7 +10,7 @@ module PlanMyStuff
|
|
|
10
10
|
@labels = params[:labels].present? ? Array.wrap(params[:labels]) : []
|
|
11
11
|
@repo = params[:repo]
|
|
12
12
|
|
|
13
|
-
@issues =
|
|
13
|
+
@issues = PlanMyStuff::Issue.list(
|
|
14
14
|
repo: @repo,
|
|
15
15
|
state: @state,
|
|
16
16
|
labels: @labels,
|
|
@@ -21,13 +21,12 @@ module PlanMyStuff
|
|
|
21
21
|
|
|
22
22
|
# GET /issues/new
|
|
23
23
|
def new
|
|
24
|
-
@issue =
|
|
25
|
-
@support_user = support_user?
|
|
24
|
+
@issue = PlanMyStuff::Issue.new
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
# POST /issues
|
|
29
28
|
def create
|
|
30
|
-
@issue =
|
|
29
|
+
@issue = PlanMyStuff::Issue.create!(
|
|
31
30
|
title: issue_params[:title],
|
|
32
31
|
body: issue_params[:body],
|
|
33
32
|
labels: parse_labels(issue_params[:labels]),
|
|
@@ -36,32 +35,31 @@ module PlanMyStuff
|
|
|
36
35
|
|
|
37
36
|
flash[:success] = 'Issue was successfully created.'
|
|
38
37
|
redirect_to(plan_my_stuff.issue_path(@issue.number, repo: @issue.repo.full_name))
|
|
39
|
-
rescue
|
|
40
|
-
|
|
41
|
-
@
|
|
38
|
+
rescue PlanMyStuff::ValidationError => e
|
|
39
|
+
pms_handle_rescue(e)
|
|
40
|
+
@issue = PlanMyStuff::Issue.new(title: issue_params[:title], body: issue_params[:body])
|
|
42
41
|
flash.now[:error] = e.message
|
|
43
|
-
render(:new, status:
|
|
42
|
+
render(:new, status: PlanMyStuff.unprocessable_status)
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
# GET /issues/:id
|
|
47
46
|
def show
|
|
48
|
-
@issue =
|
|
47
|
+
@issue = PlanMyStuff::Issue.find(params[:id].to_i, repo: params[:repo])
|
|
49
48
|
@comments = filter_visible_comments(@issue.comments)
|
|
50
|
-
@
|
|
51
|
-
@
|
|
52
|
-
@pipeline_enabled =
|
|
49
|
+
@current_user_id = pms_current_user.present? ? PlanMyStuff::UserResolver.user_id(pms_current_user) : nil
|
|
50
|
+
@current_user_login = PlanMyStuff.configuration.github_login_for[@current_user_id]
|
|
51
|
+
@pipeline_enabled = PlanMyStuff.configuration.pipeline_enabled
|
|
53
52
|
@pipeline_item = load_pipeline_item(@issue.number) if @pipeline_enabled
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
# GET /issues/:id/edit
|
|
57
56
|
def edit
|
|
58
|
-
@issue =
|
|
59
|
-
@support_user = support_user?
|
|
57
|
+
@issue = PlanMyStuff::Issue.find(params[:id].to_i, repo: params[:repo])
|
|
60
58
|
end
|
|
61
59
|
|
|
62
60
|
# PATCH/PUT /issues/:id
|
|
63
61
|
def update
|
|
64
|
-
@issue =
|
|
62
|
+
@issue = PlanMyStuff::Issue.find(params[:id].to_i, repo: params[:repo])
|
|
65
63
|
|
|
66
64
|
@issue.update!(
|
|
67
65
|
title: issue_params[:title],
|
|
@@ -71,14 +69,14 @@ module PlanMyStuff
|
|
|
71
69
|
|
|
72
70
|
flash[:success] = 'Issue was successfully updated.'
|
|
73
71
|
redirect_to(plan_my_stuff.issue_path(@issue.number, repo: @issue.repo.full_name))
|
|
74
|
-
rescue
|
|
75
|
-
|
|
72
|
+
rescue PlanMyStuff::StaleObjectError => e
|
|
73
|
+
pms_handle_rescue(e)
|
|
76
74
|
flash.now[:error] = 'Issue was modified by someone else. Please review the latest changes and try again.'
|
|
77
|
-
render(:edit, status:
|
|
78
|
-
rescue
|
|
79
|
-
|
|
75
|
+
render(:edit, status: PlanMyStuff.unprocessable_status)
|
|
76
|
+
rescue PlanMyStuff::ValidationError => e
|
|
77
|
+
pms_handle_rescue(e)
|
|
80
78
|
flash.now[:error] = e.message
|
|
81
|
-
render(:edit, status:
|
|
79
|
+
render(:edit, status: PlanMyStuff.unprocessable_status)
|
|
82
80
|
end
|
|
83
81
|
|
|
84
82
|
private
|
|
@@ -99,18 +97,16 @@ module PlanMyStuff
|
|
|
99
97
|
comments.select { |comment| comment.visible_to?(user) }
|
|
100
98
|
end
|
|
101
99
|
|
|
102
|
-
# Looks up the pipeline ProjectItem for this issue, if any.
|
|
103
|
-
#
|
|
104
|
-
# issue show should never break because the pipeline lookup
|
|
105
|
-
# failed.
|
|
100
|
+
# Looks up the pipeline ProjectItem for this issue, if any. Returns nil when the item cannot be loaded for any
|
|
101
|
+
# reason -- issue show should never break because the pipeline lookup failed.
|
|
106
102
|
#
|
|
107
103
|
# @param issue_number [Integer]
|
|
108
104
|
#
|
|
109
105
|
# @return [PlanMyStuff::ProjectItem, nil]
|
|
110
106
|
#
|
|
111
107
|
def load_pipeline_item(issue_number)
|
|
112
|
-
|
|
113
|
-
rescue ArgumentError,
|
|
108
|
+
PlanMyStuff::Pipeline::IssueLinker.find_project_item(issue_number)
|
|
109
|
+
rescue ArgumentError, PlanMyStuff::Error => e
|
|
114
110
|
Rails.logger.warn("[PlanMyStuff] Failed to load pipeline item for issue ##{issue_number}: #{e.message}")
|
|
115
111
|
nil
|
|
116
112
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
class LabelsController < ApplicationController
|
|
4
|
+
class LabelsController < PlanMyStuff::ApplicationController
|
|
5
5
|
# POST /issues/:issue_id/labels
|
|
6
6
|
def create
|
|
7
7
|
labels = parse_labels(params[:label_name])
|
|
@@ -11,20 +11,36 @@ module PlanMyStuff
|
|
|
11
11
|
return
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
issue =
|
|
15
|
-
|
|
14
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
15
|
+
|
|
16
|
+
missing = labels.reject { |label| PlanMyStuff::Label.exists?(repo: issue.repo, name: label) }
|
|
17
|
+
if missing.any?
|
|
18
|
+
flash[:error] = "Label#{'s' if missing.size > 1} not found in repo: #{missing.join(', ')}"
|
|
19
|
+
redirect_to(plan_my_stuff.issue_path(issue.number, repo: issue.repo.full_name))
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
PlanMyStuff::Label.add!(issue: issue, labels: labels)
|
|
16
24
|
|
|
17
25
|
flash[:success] = 'Label was successfully added.'
|
|
18
26
|
redirect_to(plan_my_stuff.issue_path(issue.number, repo: issue.repo.full_name))
|
|
27
|
+
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
28
|
+
pms_handle_rescue(e)
|
|
29
|
+
flash[:error] = e.message
|
|
30
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
19
31
|
end
|
|
20
32
|
|
|
21
33
|
# DELETE /issues/:issue_id/labels/:id
|
|
22
34
|
def destroy
|
|
23
|
-
issue =
|
|
24
|
-
|
|
35
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id].to_i, repo: params[:repo])
|
|
36
|
+
PlanMyStuff::Label.remove!(issue: issue, labels: [params[:id]])
|
|
25
37
|
|
|
26
38
|
flash[:success] = 'Label was successfully removed.'
|
|
27
39
|
redirect_to(plan_my_stuff.issue_path(issue.number, repo: issue.repo.full_name))
|
|
40
|
+
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
41
|
+
pms_handle_rescue(e)
|
|
42
|
+
flash[:error] = e.message
|
|
43
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id], repo: params[:repo]))
|
|
28
44
|
end
|
|
29
45
|
end
|
|
30
46
|
end
|
|
@@ -8,7 +8,7 @@ module PlanMyStuff
|
|
|
8
8
|
# PATCH /projects/:project_id/items/:item_id/assignment -> update (assign)
|
|
9
9
|
# DELETE /projects/:project_id/items/:item_id/assignment -> destroy (unassign)
|
|
10
10
|
#
|
|
11
|
-
class AssignmentsController < ApplicationController
|
|
11
|
+
class AssignmentsController < PlanMyStuff::ApplicationController
|
|
12
12
|
# PATCH /projects/:project_id/items/:item_id/assignment
|
|
13
13
|
def update
|
|
14
14
|
item = find_project_item
|
|
@@ -16,9 +16,15 @@ module PlanMyStuff
|
|
|
16
16
|
|
|
17
17
|
item.assign!(assignees)
|
|
18
18
|
|
|
19
|
-
flash[:success] =
|
|
19
|
+
flash[:success] =
|
|
20
|
+
if assignees.present?
|
|
21
|
+
"Item assigned to #{assignees.join(', ')}."
|
|
22
|
+
else
|
|
23
|
+
'All assignees removed.'
|
|
24
|
+
end
|
|
20
25
|
redirect_to(plan_my_stuff.project_path(params[:project_id]))
|
|
21
|
-
rescue ArgumentError,
|
|
26
|
+
rescue ArgumentError, PlanMyStuff::Error => e
|
|
27
|
+
pms_handle_rescue(e)
|
|
22
28
|
flash[:error] = e.message
|
|
23
29
|
redirect_to(plan_my_stuff.project_path(params[:project_id]))
|
|
24
30
|
end
|
|
@@ -39,7 +45,8 @@ module PlanMyStuff
|
|
|
39
45
|
|
|
40
46
|
flash[:success] = "#{params[:username]} unassigned."
|
|
41
47
|
redirect_to(plan_my_stuff.project_path(params[:project_id]))
|
|
42
|
-
rescue ArgumentError,
|
|
48
|
+
rescue ArgumentError, PlanMyStuff::Error => e
|
|
49
|
+
pms_handle_rescue(e)
|
|
43
50
|
flash[:error] = e.message
|
|
44
51
|
redirect_to(plan_my_stuff.project_path(params[:project_id]))
|
|
45
52
|
end
|
|
@@ -51,10 +58,10 @@ module PlanMyStuff
|
|
|
51
58
|
# @return [PlanMyStuff::ProjectItem]
|
|
52
59
|
#
|
|
53
60
|
def find_project_item
|
|
54
|
-
project =
|
|
61
|
+
project = PlanMyStuff::Project.find(params[:project_id].to_i)
|
|
55
62
|
item = project.items.find { |i| i.id == params[:item_id] }
|
|
56
63
|
|
|
57
|
-
raise(
|
|
64
|
+
raise(PlanMyStuff::APIError, "Item not found: #{params[:item_id]}") unless item
|
|
58
65
|
|
|
59
66
|
item
|
|
60
67
|
end
|
|
@@ -7,7 +7,7 @@ module PlanMyStuff
|
|
|
7
7
|
#
|
|
8
8
|
# PATCH /projects/:project_id/items/:item_id/status -> update (moves to new status)
|
|
9
9
|
#
|
|
10
|
-
class StatusesController < ApplicationController
|
|
10
|
+
class StatusesController < PlanMyStuff::ApplicationController
|
|
11
11
|
# PATCH /projects/:project_id/items/:item_id/status
|
|
12
12
|
def update
|
|
13
13
|
item = find_project_item
|
|
@@ -16,7 +16,8 @@ module PlanMyStuff
|
|
|
16
16
|
|
|
17
17
|
flash[:success] = "Item moved to #{params[:status]}."
|
|
18
18
|
redirect_to(plan_my_stuff.project_path(params[:project_id]))
|
|
19
|
-
rescue ArgumentError,
|
|
19
|
+
rescue ArgumentError, PlanMyStuff::Error => e
|
|
20
|
+
pms_handle_rescue(e)
|
|
20
21
|
flash[:error] = e.message
|
|
21
22
|
redirect_to(plan_my_stuff.project_path(params[:project_id]))
|
|
22
23
|
end
|
|
@@ -28,10 +29,10 @@ module PlanMyStuff
|
|
|
28
29
|
# @return [PlanMyStuff::ProjectItem]
|
|
29
30
|
#
|
|
30
31
|
def find_project_item
|
|
31
|
-
project =
|
|
32
|
+
project = PlanMyStuff::Project.find(params[:project_id].to_i)
|
|
32
33
|
item = project.items.find { |i| i.id == params[:item_id] }
|
|
33
34
|
|
|
34
|
-
raise(
|
|
35
|
+
raise(PlanMyStuff::APIError, "Item not found: #{params[:item_id]}") unless item
|
|
35
36
|
|
|
36
37
|
item
|
|
37
38
|
end
|