plan_my_stuff 0.6.0 → 0.8.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 +41 -1
- 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 +4 -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 +56 -3
- 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 +61 -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 +57 -57
- data/lib/plan_my_stuff/comment_metadata.rb +1 -1
- data/lib/plan_my_stuff/configuration.rb +95 -82
- 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 +501 -322
- data/lib/plan_my_stuff/issue_metadata.rb +10 -10
- data/lib/plan_my_stuff/label.rb +32 -16
- 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 +3 -2
|
@@ -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
|
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
class ProjectItemsController < ApplicationController
|
|
4
|
+
class ProjectItemsController < PlanMyStuff::ApplicationController
|
|
5
5
|
# POST /projects/:project_id/items
|
|
6
6
|
def create
|
|
7
7
|
project_number = params[:project_id].to_i
|
|
8
8
|
|
|
9
9
|
if params[:draft] == '1'
|
|
10
|
-
|
|
10
|
+
PlanMyStuff::ProjectItem.create!(
|
|
11
|
+
params[:title],
|
|
12
|
+
draft: true,
|
|
13
|
+
body: params[:body],
|
|
14
|
+
project_number: project_number,
|
|
15
|
+
)
|
|
11
16
|
flash[:success] = 'Draft item created.'
|
|
12
17
|
else
|
|
13
|
-
issue =
|
|
14
|
-
|
|
18
|
+
issue = PlanMyStuff::Issue.find(params[:issue_number].to_i)
|
|
19
|
+
PlanMyStuff::ProjectItem.create!(issue, project_number: project_number)
|
|
15
20
|
flash[:success] = "Issue ##{issue.number} added to project."
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
redirect_to(plan_my_stuff.project_path(project_number))
|
|
19
|
-
rescue ArgumentError,
|
|
24
|
+
rescue ArgumentError, PlanMyStuff::Error, Octokit::Error => e
|
|
25
|
+
pms_handle_rescue(e)
|
|
26
|
+
flash[:error] = e.message
|
|
27
|
+
redirect_to(plan_my_stuff.project_path(project_number))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# DELETE /projects/:project_id/items/:id
|
|
31
|
+
def destroy
|
|
32
|
+
project_number = params[:project_id].to_i
|
|
33
|
+
item_id = params[:id]
|
|
34
|
+
|
|
35
|
+
project = PlanMyStuff::Project.find(project_number)
|
|
36
|
+
item = project.items.find { |i| i.id == item_id }
|
|
37
|
+
raise(PlanMyStuff::APIError, "Item not found: #{item_id}") if item.nil?
|
|
38
|
+
|
|
39
|
+
item.destroy!(user: pms_current_user)
|
|
40
|
+
|
|
41
|
+
flash[:success] = 'Item removed from project.'
|
|
42
|
+
redirect_to(plan_my_stuff.project_path(project_number))
|
|
43
|
+
rescue ArgumentError, PlanMyStuff::Error, Octokit::Error => e
|
|
44
|
+
pms_handle_rescue(e)
|
|
20
45
|
flash[:error] = e.message
|
|
21
46
|
redirect_to(plan_my_stuff.project_path(project_number))
|
|
22
47
|
end
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
class ProjectsController < ApplicationController
|
|
4
|
+
class ProjectsController < PlanMyStuff::ApplicationController
|
|
5
5
|
# GET /projects
|
|
6
6
|
def index
|
|
7
|
-
all_projects =
|
|
7
|
+
all_projects = PlanMyStuff::BaseProject.list.reject(&:closed)
|
|
8
8
|
@filter = params[:filter].presence_in(%w[testing all]) || 'regular'
|
|
9
9
|
@projects =
|
|
10
10
|
case @filter
|
|
11
|
-
when 'testing' then all_projects.select { |p| p.is_a?(
|
|
12
|
-
when 'regular' then all_projects.reject { |p| p.is_a?(
|
|
11
|
+
when 'testing' then all_projects.select { |p| p.is_a?(PlanMyStuff::TestingProject) }
|
|
12
|
+
when 'regular' then all_projects.reject { |p| p.is_a?(PlanMyStuff::TestingProject) }
|
|
13
13
|
else all_projects
|
|
14
14
|
end
|
|
15
|
-
@support_user = support_user?
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
# GET /projects/new
|
|
19
18
|
def new
|
|
20
|
-
@project =
|
|
19
|
+
@project = PlanMyStuff::Project.new
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
# POST /projects
|
|
24
23
|
def create
|
|
25
|
-
@project =
|
|
24
|
+
@project = PlanMyStuff::Project.create!(
|
|
26
25
|
title: project_params[:title],
|
|
27
26
|
readme: project_params[:readme] || '',
|
|
28
27
|
description: project_params[:description],
|
|
@@ -31,32 +30,32 @@ module PlanMyStuff
|
|
|
31
30
|
|
|
32
31
|
flash[:success] = 'Project was successfully created.'
|
|
33
32
|
redirect_to(plan_my_stuff.project_path(@project.number))
|
|
34
|
-
rescue
|
|
35
|
-
|
|
33
|
+
rescue PlanMyStuff::ValidationError => e
|
|
34
|
+
pms_handle_rescue(e)
|
|
35
|
+
@project = PlanMyStuff::Project.new(
|
|
36
36
|
title: project_params[:title],
|
|
37
37
|
readme: project_params[:readme],
|
|
38
38
|
description: project_params[:description],
|
|
39
39
|
)
|
|
40
40
|
flash.now[:error] = e.message
|
|
41
|
-
render(:new, status:
|
|
41
|
+
render(:new, status: PlanMyStuff.unprocessable_status)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# GET /projects/:id
|
|
45
45
|
def show
|
|
46
|
-
@project =
|
|
46
|
+
@project = PlanMyStuff::Project.find(params[:id].to_i)
|
|
47
47
|
@statuses = @project.statuses.pluck(:name)
|
|
48
48
|
@items_by_status = @project.items.group_by(&:status)
|
|
49
|
-
@support_user = support_user?
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
# GET /projects/:id/edit
|
|
53
52
|
def edit
|
|
54
|
-
@project =
|
|
53
|
+
@project = PlanMyStuff::Project.find(params[:id].to_i)
|
|
55
54
|
end
|
|
56
55
|
|
|
57
56
|
# PATCH/PUT /projects/:id
|
|
58
57
|
def update
|
|
59
|
-
@project =
|
|
58
|
+
@project = PlanMyStuff::Project.find(params[:id].to_i)
|
|
60
59
|
|
|
61
60
|
@project.update!(
|
|
62
61
|
title: project_params[:title],
|
|
@@ -66,9 +65,10 @@ module PlanMyStuff
|
|
|
66
65
|
|
|
67
66
|
flash[:success] = 'Project was successfully updated.'
|
|
68
67
|
redirect_to(plan_my_stuff.project_path(@project.number))
|
|
69
|
-
rescue
|
|
68
|
+
rescue PlanMyStuff::StaleObjectError => e
|
|
69
|
+
pms_handle_rescue(e)
|
|
70
70
|
flash.now[:error] = 'Project was modified by someone else. Please review the latest changes and try again.'
|
|
71
|
-
render(:edit, status:
|
|
71
|
+
render(:edit, status: PlanMyStuff.unprocessable_status)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
private
|
|
@@ -7,7 +7,7 @@ module PlanMyStuff
|
|
|
7
7
|
# Pass is submitted directly via button_to with result: "pass".
|
|
8
8
|
# Fail renders a form (new) to capture result_notes before posting to create.
|
|
9
9
|
#
|
|
10
|
-
class ResultsController < ApplicationController
|
|
10
|
+
class ResultsController < PlanMyStuff::ApplicationController
|
|
11
11
|
# GET /testing_projects/:testing_project_id/items/:item_id/result/new
|
|
12
12
|
def new
|
|
13
13
|
@project_number = params[:testing_project_id].to_i
|
|
@@ -19,21 +19,31 @@ module PlanMyStuff
|
|
|
19
19
|
project_number = params[:testing_project_id].to_i
|
|
20
20
|
item = find_project_item(project_number)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
case params[:result]
|
|
23
|
+
when 'pass'
|
|
23
24
|
item.mark_passed!(pms_current_user)
|
|
24
25
|
flash[:success] = 'Item marked as Passed.'
|
|
25
|
-
|
|
26
|
+
when 'fail'
|
|
26
27
|
item.mark_failed!(pms_current_user, result_notes: params[:result_notes])
|
|
27
28
|
flash[:success] = 'Item marked as Failed.'
|
|
29
|
+
else
|
|
30
|
+
raise(ArgumentError, "Invalid result: #{params[:result].inspect}")
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
redirect_to(plan_my_stuff.testing_project_path(project_number))
|
|
31
|
-
rescue
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
rescue PlanMyStuff::ValidationError => e
|
|
35
|
+
pms_handle_rescue(e)
|
|
36
|
+
if params[:result] == 'fail'
|
|
37
|
+
flash.now[:error] = e.message
|
|
38
|
+
@project_number = params[:testing_project_id].to_i
|
|
39
|
+
@item_id = params[:item_id]
|
|
40
|
+
render(:new, status: PlanMyStuff.unprocessable_status)
|
|
41
|
+
else
|
|
42
|
+
flash[:error] = e.message
|
|
43
|
+
redirect_to(plan_my_stuff.testing_project_path(params[:testing_project_id].to_i))
|
|
44
|
+
end
|
|
45
|
+
rescue ArgumentError, PlanMyStuff::Error, Octokit::Error => e
|
|
46
|
+
pms_handle_rescue(e)
|
|
37
47
|
flash[:error] = e.message
|
|
38
48
|
redirect_to(plan_my_stuff.testing_project_path(params[:testing_project_id].to_i))
|
|
39
49
|
end
|
|
@@ -43,9 +53,9 @@ module PlanMyStuff
|
|
|
43
53
|
# @param project_number [Integer]
|
|
44
54
|
# @return [PlanMyStuff::TestingProjectItem]
|
|
45
55
|
def find_project_item(project_number)
|
|
46
|
-
project =
|
|
56
|
+
project = PlanMyStuff::TestingProject.find(project_number)
|
|
47
57
|
item = project.items.find { |i| i.id == params[:item_id] }
|
|
48
|
-
raise(
|
|
58
|
+
raise(PlanMyStuff::APIError, "Item not found: #{params[:item_id]}") unless item
|
|
49
59
|
|
|
50
60
|
item
|
|
51
61
|
end
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
class TestingProjectItemsController < ApplicationController
|
|
4
|
+
class TestingProjectItemsController < PlanMyStuff::ApplicationController
|
|
5
5
|
# GET /testing_projects/:testing_project_id/items/new
|
|
6
6
|
def new
|
|
7
|
-
@project =
|
|
7
|
+
@project = PlanMyStuff::TestingProject.find(params[:testing_project_id].to_i)
|
|
8
|
+
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
9
|
+
pms_handle_rescue(e)
|
|
10
|
+
flash[:error] = e.message
|
|
11
|
+
redirect_to(plan_my_stuff.testing_projects_path)
|
|
8
12
|
end
|
|
9
13
|
|
|
10
14
|
# POST /testing_projects/:testing_project_id/items
|
|
11
15
|
def create
|
|
12
16
|
project_number = params[:testing_project_id].to_i
|
|
13
|
-
item =
|
|
17
|
+
item = PlanMyStuff::TestingProjectItem.create!(
|
|
14
18
|
item_params[:title],
|
|
15
19
|
draft: true,
|
|
16
20
|
body: item_params[:body],
|
|
@@ -24,7 +28,8 @@ module PlanMyStuff
|
|
|
24
28
|
|
|
25
29
|
flash[:success] = 'Item added.'
|
|
26
30
|
redirect_to(plan_my_stuff.testing_project_path(project_number))
|
|
27
|
-
rescue ArgumentError,
|
|
31
|
+
rescue ArgumentError, PlanMyStuff::Error, Octokit::Error => e
|
|
32
|
+
pms_handle_rescue(e)
|
|
28
33
|
flash[:error] = e.message
|
|
29
34
|
redirect_to(plan_my_stuff.testing_project_path(project_number))
|
|
30
35
|
end
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
|
-
class TestingProjectsController < ApplicationController
|
|
4
|
+
class TestingProjectsController < PlanMyStuff::ApplicationController
|
|
5
5
|
# GET /testing_projects/new
|
|
6
6
|
def new
|
|
7
|
-
@project =
|
|
7
|
+
@project = PlanMyStuff::TestingProject.new
|
|
8
8
|
@project.metadata.subject_urls = [params[:subject_url]] if params[:subject_url].present?
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
# POST /testing_projects
|
|
12
12
|
def create
|
|
13
|
-
@project =
|
|
13
|
+
@project = PlanMyStuff::TestingProject.create!(
|
|
14
14
|
title: testing_project_params[:title],
|
|
15
15
|
description: testing_project_params[:description],
|
|
16
16
|
subject_urls: parse_subject_urls(testing_project_params[:subject_urls]),
|
|
@@ -21,28 +21,34 @@ module PlanMyStuff
|
|
|
21
21
|
|
|
22
22
|
flash[:success] = 'Testing project was successfully created.'
|
|
23
23
|
redirect_to(plan_my_stuff.testing_project_path(@project.number))
|
|
24
|
-
rescue
|
|
25
|
-
|
|
24
|
+
rescue PlanMyStuff::ValidationError => e
|
|
25
|
+
pms_handle_rescue(e)
|
|
26
|
+
@project = PlanMyStuff::TestingProject.new(
|
|
27
|
+
title: testing_project_params[:title],
|
|
28
|
+
description: testing_project_params[:description],
|
|
29
|
+
)
|
|
30
|
+
@project.metadata.subject_urls = parse_subject_urls(testing_project_params[:subject_urls])
|
|
31
|
+
@project.metadata.due_date = safe_parse_due_date(testing_project_params[:due_date])
|
|
32
|
+
@project.metadata.deadline_miss_reason = testing_project_params[:deadline_miss_reason]
|
|
26
33
|
flash.now[:error] = e.message
|
|
27
|
-
render(:new, status:
|
|
34
|
+
render(:new, status: PlanMyStuff.unprocessable_status)
|
|
28
35
|
end
|
|
29
36
|
|
|
30
37
|
# GET /testing_projects/:id
|
|
31
38
|
def show
|
|
32
|
-
@project =
|
|
39
|
+
@project = PlanMyStuff::TestingProject.find(params[:id].to_i)
|
|
33
40
|
@statuses = @project.statuses.pluck(:name)
|
|
34
41
|
@items_by_status = @project.items.group_by(&:status)
|
|
35
|
-
@support_user = support_user?
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
# GET /testing_projects/:id/edit
|
|
39
45
|
def edit
|
|
40
|
-
@project =
|
|
46
|
+
@project = PlanMyStuff::TestingProject.find(params[:id].to_i)
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
# PATCH/PUT /testing_projects/:id
|
|
44
50
|
def update
|
|
45
|
-
@project =
|
|
51
|
+
@project = PlanMyStuff::TestingProject.find(params[:id].to_i)
|
|
46
52
|
|
|
47
53
|
@project.update!(
|
|
48
54
|
title: testing_project_params[:title],
|
|
@@ -56,13 +62,15 @@ module PlanMyStuff
|
|
|
56
62
|
|
|
57
63
|
flash[:success] = 'Testing project was successfully updated.'
|
|
58
64
|
redirect_to(plan_my_stuff.testing_project_path(@project.number))
|
|
59
|
-
rescue
|
|
65
|
+
rescue PlanMyStuff::StaleObjectError => e
|
|
66
|
+
pms_handle_rescue(e)
|
|
60
67
|
flash.now[:error] =
|
|
61
68
|
'Testing project was modified by someone else. Please review the latest changes and try again.'
|
|
62
|
-
render(:edit, status:
|
|
63
|
-
rescue
|
|
69
|
+
render(:edit, status: PlanMyStuff.unprocessable_status)
|
|
70
|
+
rescue PlanMyStuff::ValidationError => e
|
|
71
|
+
pms_handle_rescue(e)
|
|
64
72
|
flash.now[:error] = e.message
|
|
65
|
-
render(:edit, status:
|
|
73
|
+
render(:edit, status: PlanMyStuff.unprocessable_status)
|
|
66
74
|
end
|
|
67
75
|
|
|
68
76
|
private
|
|
@@ -80,6 +88,14 @@ module PlanMyStuff
|
|
|
80
88
|
|
|
81
89
|
Date.parse(raw)
|
|
82
90
|
rescue ArgumentError, TypeError
|
|
91
|
+
raise(PlanMyStuff::ValidationError, 'Due date must be a valid date.')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @param raw [String, nil]
|
|
95
|
+
# @return [Date, nil]
|
|
96
|
+
def safe_parse_due_date(raw)
|
|
97
|
+
parse_due_date(raw)
|
|
98
|
+
rescue PlanMyStuff::ValidationError
|
|
83
99
|
nil
|
|
84
100
|
end
|
|
85
101
|
|
|
@@ -5,7 +5,7 @@ module PlanMyStuff
|
|
|
5
5
|
class AwsController < ActionController::API
|
|
6
6
|
VALID_SNS_MESSAGE_TYPES = %w[Notification SubscriptionConfirmation UnsubscribeConfirmation].freeze
|
|
7
7
|
|
|
8
|
-
before_action :verify_signature
|
|
8
|
+
before_action :verify_signature
|
|
9
9
|
|
|
10
10
|
# POST /webhooks/aws
|
|
11
11
|
def create
|
|
@@ -14,6 +14,13 @@ module PlanMyStuff
|
|
|
14
14
|
|
|
15
15
|
Rails.logger.info("[PlanMyStuff] SNS #{message_type}: #{sns_params.to_unsafe_h.inspect}")
|
|
16
16
|
|
|
17
|
+
if message_type == 'SubscriptionConfirmation'
|
|
18
|
+
confirm_subscription!
|
|
19
|
+
head(:ok)
|
|
20
|
+
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
17
24
|
unless message_type == 'Notification'
|
|
18
25
|
head(:ok)
|
|
19
26
|
|
|
@@ -42,14 +49,36 @@ module PlanMyStuff
|
|
|
42
49
|
|
|
43
50
|
private
|
|
44
51
|
|
|
52
|
+
# Visits the SubscribeURL provided by SNS to confirm the subscription. Without this, SNS leaves the
|
|
53
|
+
# endpoint in +PendingConfirmation+ and never delivers Notifications.
|
|
54
|
+
#
|
|
55
|
+
# @return [void]
|
|
56
|
+
#
|
|
57
|
+
def confirm_subscription!
|
|
58
|
+
subscribe_url = sns_params[:subscribe_url].to_s
|
|
59
|
+
if subscribe_url.blank?
|
|
60
|
+
Rails.logger.warn('[PlanMyStuff] SubscriptionConfirmation missing SubscribeURL')
|
|
61
|
+
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
uri = URI.parse(subscribe_url)
|
|
66
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
67
|
+
http.use_ssl = (uri.scheme == 'https')
|
|
68
|
+
response = http.request(Net::HTTP::Get.new(uri.request_uri))
|
|
69
|
+
|
|
70
|
+
Rails.logger.info("[PlanMyStuff] Confirmed SNS subscription: HTTP #{response.code}")
|
|
71
|
+
rescue => e
|
|
72
|
+
Rails.logger.error("[PlanMyStuff] SNS subscription confirmation failed: #{e.class}: #{e.message}")
|
|
73
|
+
end
|
|
74
|
+
|
|
45
75
|
# Validates the SNS message signature.
|
|
46
|
-
# Checks message type header and topic ARN first (cheap), then
|
|
47
|
-
#
|
|
48
|
-
# certificate-based verification.
|
|
76
|
+
# Checks message type header and topic ARN first (cheap), then delegates to the configured verifier class
|
|
77
|
+
# for cryptographic certificate-based verification.
|
|
49
78
|
#
|
|
50
79
|
# @return [void]
|
|
51
80
|
#
|
|
52
|
-
def verify_signature
|
|
81
|
+
def verify_signature
|
|
53
82
|
message_type = request.headers['x-amz-sns-message-type'].to_s
|
|
54
83
|
|
|
55
84
|
if VALID_SNS_MESSAGE_TYPES.exclude?(message_type)
|
|
@@ -82,9 +111,8 @@ module PlanMyStuff
|
|
|
82
111
|
end
|
|
83
112
|
end
|
|
84
113
|
|
|
85
|
-
# Parses the SNS envelope. Returns empty params if the body is
|
|
86
|
-
#
|
|
87
|
-
# with 401 instead of bubbling a 500.
|
|
114
|
+
# Parses the SNS envelope. Returns empty params if the body is not valid JSON so signature verification
|
|
115
|
+
# rejects the request with 401 instead of bubbling a 500.
|
|
88
116
|
#
|
|
89
117
|
# @return [ActionController::Parameters]
|
|
90
118
|
#
|
|
@@ -94,8 +122,8 @@ module PlanMyStuff
|
|
|
94
122
|
@sns_params ||= ActionController::Parameters.new
|
|
95
123
|
end
|
|
96
124
|
|
|
97
|
-
# Parses the nested SNS message payload. Returns empty params on
|
|
98
|
-
#
|
|
125
|
+
# Parses the nested SNS message payload. Returns empty params on malformed JSON so callers degrade
|
|
126
|
+
# gracefully.
|
|
99
127
|
#
|
|
100
128
|
# @return [ActionController::Parameters]
|
|
101
129
|
#
|
|
@@ -106,8 +134,7 @@ module PlanMyStuff
|
|
|
106
134
|
ActionController::Parameters.new
|
|
107
135
|
end
|
|
108
136
|
|
|
109
|
-
# Checks whether any resource ARN in the ECS event matches the
|
|
110
|
-
# configured service identifier suffix.
|
|
137
|
+
# Checks whether any resource ARN in the ECS event matches the configured service identifier suffix.
|
|
111
138
|
#
|
|
112
139
|
# @param service_identifier [String, nil]
|
|
113
140
|
#
|
|
@@ -119,15 +146,21 @@ module PlanMyStuff
|
|
|
119
146
|
sns_message_params.fetch(:resources, []).any? { |arn| arn.end_with?(service_identifier) }
|
|
120
147
|
end
|
|
121
148
|
|
|
122
|
-
# Finds "Release in Progress" items whose linked issue commit SHA
|
|
123
|
-
#
|
|
124
|
-
# then completes deployment for each.
|
|
149
|
+
# Finds "Release in Progress" items whose linked issue commit SHA matches the configured production
|
|
150
|
+
# commit SHA (prefix match), then completes deployment for each.
|
|
125
151
|
#
|
|
126
152
|
# @return [void]
|
|
127
153
|
#
|
|
128
154
|
def handle_deployment_completed
|
|
129
155
|
sha = PlanMyStuff.configuration.production_commit_sha
|
|
130
|
-
|
|
156
|
+
|
|
157
|
+
if sha.blank?
|
|
158
|
+
Rails.logger.error(
|
|
159
|
+
'[PlanMyStuff] production_commit_sha is not configured - skipping deployment completion',
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return
|
|
163
|
+
end
|
|
131
164
|
|
|
132
165
|
release_in_progress_items.each do |item|
|
|
133
166
|
next if item.draft?
|
|
@@ -143,7 +176,7 @@ module PlanMyStuff
|
|
|
143
176
|
# @return [Array<PlanMyStuff::ProjectItem>]
|
|
144
177
|
#
|
|
145
178
|
def release_in_progress_items
|
|
146
|
-
project_number = PlanMyStuff::Pipeline.resolve_pipeline_project_number
|
|
179
|
+
project_number = PlanMyStuff::Pipeline.resolve_pipeline_project_number!
|
|
147
180
|
project = PlanMyStuff::Project.find(project_number)
|
|
148
181
|
|
|
149
182
|
release_status = PlanMyStuff::Pipeline.resolve_status_name(
|