plan_my_stuff 0.1.0 → 1.0.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 +595 -0
- data/CONFIGURATION.md +487 -0
- data/README.md +612 -88
- data/app/controllers/plan_my_stuff/application_controller.rb +27 -5
- data/app/controllers/plan_my_stuff/comments_controller.rb +50 -19
- data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +127 -0
- data/app/controllers/plan_my_stuff/issues/closures_controller.rb +53 -0
- data/app/controllers/plan_my_stuff/issues/links_controller.rb +129 -0
- data/app/controllers/plan_my_stuff/issues/takes_controller.rb +161 -0
- data/app/controllers/plan_my_stuff/issues/testings_controller.rb +82 -0
- data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +62 -0
- data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +55 -0
- data/app/controllers/plan_my_stuff/issues_controller.rb +53 -70
- data/app/controllers/plan_my_stuff/labels_controller.rb +32 -10
- data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +88 -0
- data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +44 -0
- data/app/controllers/plan_my_stuff/project_items_controller.rb +32 -69
- data/app/controllers/plan_my_stuff/projects_controller.rb +81 -3
- data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +67 -0
- data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +49 -0
- data/app/controllers/plan_my_stuff/testing_projects_controller.rb +121 -0
- data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +202 -0
- data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +371 -0
- data/app/jobs/plan_my_stuff/application_job.rb +8 -0
- data/app/jobs/plan_my_stuff/reminders_sweep_job.rb +75 -0
- data/app/views/plan_my_stuff/comments/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/comments/partials/_form.html.erb +8 -0
- data/app/views/plan_my_stuff/issues/edit.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/index.html.erb +5 -5
- data/app/views/plan_my_stuff/issues/new.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +108 -0
- data/app/views/plan_my_stuff/issues/partials/_form.html.erb +11 -6
- data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +4 -3
- data/app/views/plan_my_stuff/issues/partials/_links.html.erb +113 -0
- data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +4 -3
- data/app/views/plan_my_stuff/issues/show.html.erb +67 -6
- data/app/views/plan_my_stuff/partials/_flash.html.erb +3 -0
- data/app/views/plan_my_stuff/projects/edit.html.erb +5 -0
- data/app/views/plan_my_stuff/projects/index.html.erb +18 -2
- data/app/views/plan_my_stuff/projects/new.html.erb +5 -0
- data/app/views/plan_my_stuff/projects/partials/_form.html.erb +30 -0
- data/app/views/plan_my_stuff/projects/show.html.erb +30 -11
- data/app/views/plan_my_stuff/testing_project_items/new.html.erb +10 -0
- data/app/views/plan_my_stuff/testing_project_items/results/new.html.erb +20 -0
- data/app/views/plan_my_stuff/testing_projects/edit.html.erb +5 -0
- data/app/views/plan_my_stuff/testing_projects/new.html.erb +5 -0
- data/app/views/plan_my_stuff/testing_projects/partials/_form.html.erb +40 -0
- data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +52 -0
- data/app/views/plan_my_stuff/testing_projects/partials/items/_form.html.erb +36 -0
- data/app/views/plan_my_stuff/testing_projects/show.html.erb +65 -0
- data/config/routes.rb +43 -15
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +302 -20
- data/lib/plan_my_stuff/application_record.rb +158 -1
- data/lib/plan_my_stuff/approval.rb +88 -0
- data/lib/plan_my_stuff/archive/sweep.rb +85 -0
- data/lib/plan_my_stuff/archive.rb +12 -0
- data/lib/plan_my_stuff/attachment.rb +83 -0
- data/lib/plan_my_stuff/attachment_uploader.rb +245 -0
- data/lib/plan_my_stuff/aws_sns_simulator.rb +116 -0
- data/lib/plan_my_stuff/base_metadata.rb +25 -28
- data/lib/plan_my_stuff/base_project.rb +502 -0
- data/lib/plan_my_stuff/base_project_extractions/graphql_hydration.rb +186 -0
- data/lib/plan_my_stuff/base_project_item.rb +588 -0
- data/lib/plan_my_stuff/base_project_metadata.rb +16 -0
- data/lib/plan_my_stuff/cache.rb +197 -0
- data/lib/plan_my_stuff/client.rb +139 -64
- data/lib/plan_my_stuff/comment.rb +225 -100
- data/lib/plan_my_stuff/comment_metadata.rb +68 -5
- data/lib/plan_my_stuff/configuration.rb +459 -28
- data/lib/plan_my_stuff/custom_fields.rb +96 -12
- data/lib/plan_my_stuff/engine.rb +14 -2
- data/lib/plan_my_stuff/errors.rb +65 -5
- data/lib/plan_my_stuff/graphql/queries.rb +454 -0
- data/lib/plan_my_stuff/issue.rb +1097 -166
- data/lib/plan_my_stuff/issue_extractions/approvals.rb +370 -0
- data/lib/plan_my_stuff/issue_extractions/links.rb +525 -0
- data/lib/plan_my_stuff/issue_extractions/viewers.rb +75 -0
- data/lib/plan_my_stuff/issue_extractions/waiting.rb +171 -0
- data/lib/plan_my_stuff/issue_field.rb +126 -0
- data/lib/plan_my_stuff/issue_field_translation.rb +67 -0
- data/lib/plan_my_stuff/issue_field_value_set.rb +68 -0
- data/lib/plan_my_stuff/issue_metadata.rb +132 -21
- data/lib/plan_my_stuff/label.rb +100 -13
- data/lib/plan_my_stuff/link.rb +144 -0
- data/lib/plan_my_stuff/markdown.rb +13 -7
- data/lib/plan_my_stuff/metadata_parser.rb +51 -12
- data/lib/plan_my_stuff/notifications.rb +148 -0
- data/lib/plan_my_stuff/pipeline/completed_sweep.rb +46 -0
- data/lib/plan_my_stuff/pipeline/issue_linker.rb +62 -0
- data/lib/plan_my_stuff/pipeline/status.rb +40 -0
- data/lib/plan_my_stuff/pipeline/testing.rb +23 -0
- data/lib/plan_my_stuff/pipeline.rb +310 -0
- data/lib/plan_my_stuff/project.rb +63 -465
- data/lib/plan_my_stuff/project_item.rb +3 -409
- data/lib/plan_my_stuff/project_item_metadata.rb +55 -0
- data/lib/plan_my_stuff/project_metadata.rb +47 -0
- data/lib/plan_my_stuff/reminders/closer.rb +70 -0
- data/lib/plan_my_stuff/reminders/fire.rb +129 -0
- data/lib/plan_my_stuff/reminders/sweep.rb +54 -0
- data/lib/plan_my_stuff/reminders.rb +12 -0
- data/lib/plan_my_stuff/repo.rb +145 -0
- data/lib/plan_my_stuff/test_helpers.rb +265 -25
- data/lib/plan_my_stuff/testing_project.rb +292 -0
- data/lib/plan_my_stuff/testing_project_item.rb +218 -0
- data/lib/plan_my_stuff/testing_project_metadata.rb +94 -0
- data/lib/plan_my_stuff/user_resolver.rb +24 -3
- data/lib/plan_my_stuff/verifier.rb +10 -0
- data/lib/plan_my_stuff/version.rb +2 -2
- data/lib/plan_my_stuff/webhook_replayer.rb +292 -0
- data/lib/plan_my_stuff.rb +55 -20
- data/lib/tasks/plan_my_stuff.rake +331 -0
- metadata +99 -4
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require 'time'
|
|
7
|
+
require 'uri'
|
|
8
|
+
|
|
9
|
+
module PlanMyStuff
|
|
10
|
+
# Dev helper: build a fake SNS notification wrapping an ECS Deployment
|
|
11
|
+
# State Change event and POST it to a local +/webhooks/aws+ endpoint.
|
|
12
|
+
# Used by +plan_my_stuff:webhooks:simulate_aws+ so deployment flows
|
|
13
|
+
# can be exercised without waiting for a real AWS deployment.
|
|
14
|
+
#
|
|
15
|
+
# The consuming app must be configured with a no-op SNS verifier
|
|
16
|
+
# (see +PlanMyStuff::NullSnsVerifier+) or signature verification will
|
|
17
|
+
# reject the simulated envelope. Never enable that verifier in
|
|
18
|
+
# production.
|
|
19
|
+
#
|
|
20
|
+
module AwsSnsSimulator
|
|
21
|
+
DEFAULT_EVENT = 'SERVICE_DEPLOYMENT_COMPLETED'
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# POSTs a simulated SNS envelope to the endpoint. Pulls
|
|
26
|
+
# +sns_topic_arn+, +aws_service_identifier+, and
|
|
27
|
+
# +production_commit_sha+ from +PlanMyStuff.configuration+ (this
|
|
28
|
+
# task is dev-only, so there's no reason to parameterize them).
|
|
29
|
+
#
|
|
30
|
+
# @raise [PlanMyStuff::ConfigurationError] if sns details are missing
|
|
31
|
+
#
|
|
32
|
+
# @param endpoint_url [String] full URL including path
|
|
33
|
+
# @param event_name [String] ECS eventName (default completed)
|
|
34
|
+
#
|
|
35
|
+
# @return [Net::HTTPResponse]
|
|
36
|
+
#
|
|
37
|
+
def post!(endpoint_url:, event_name: DEFAULT_EVENT)
|
|
38
|
+
config = PlanMyStuff.configuration
|
|
39
|
+
if config.sns_topic_arn.blank?
|
|
40
|
+
raise(PlanMyStuff::ConfigurationError, 'PlanMyStuff.configuration.sns_topic_arn is blank')
|
|
41
|
+
end
|
|
42
|
+
if config.aws_service_identifier.blank?
|
|
43
|
+
raise(PlanMyStuff::ConfigurationError, 'PlanMyStuff.configuration.aws_service_identifier is blank')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
topic_arn = config.sns_topic_arn
|
|
47
|
+
service_arn = "arn:aws:ecs:us-east-1:000000000000:service/simulated-cluster/#{config.aws_service_identifier}"
|
|
48
|
+
commit_sha = config.production_commit_sha
|
|
49
|
+
|
|
50
|
+
message = build_ecs_message(event_name: event_name, service_arn: service_arn, commit_sha: commit_sha)
|
|
51
|
+
envelope = build_sns_envelope(message: message, topic_arn: topic_arn)
|
|
52
|
+
raw_body = JSON.generate(envelope)
|
|
53
|
+
|
|
54
|
+
uri = URI(endpoint_url)
|
|
55
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
56
|
+
http.use_ssl = uri.scheme == 'https'
|
|
57
|
+
|
|
58
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
59
|
+
request['Content-Type'] = 'application/json'
|
|
60
|
+
request['x-amz-sns-message-type'] = 'Notification'
|
|
61
|
+
request.body = raw_body
|
|
62
|
+
|
|
63
|
+
$stdout.puts("POST #{endpoint_url}")
|
|
64
|
+
$stdout.puts(" event: #{event_name}")
|
|
65
|
+
$stdout.puts(" topic: #{topic_arn}")
|
|
66
|
+
$stdout.puts(" resources: #{service_arn}")
|
|
67
|
+
$stdout.puts(" commit: #{commit_sha || '(none)'}")
|
|
68
|
+
$stdout.puts('---')
|
|
69
|
+
|
|
70
|
+
response = http.request(request)
|
|
71
|
+
$stdout.puts("HTTP #{response.code} #{response.message}")
|
|
72
|
+
$stdout.puts(response.body) if response.body.present?
|
|
73
|
+
response
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [Hash]
|
|
77
|
+
def build_ecs_message(event_name:, service_arn:, commit_sha:)
|
|
78
|
+
detail = {
|
|
79
|
+
'eventType' => 'INFO',
|
|
80
|
+
'eventName' => event_name,
|
|
81
|
+
'deploymentId' => "ecs-svc/#{SecureRandom.hex(8)}",
|
|
82
|
+
'updatedAt' => PlanMyStuff.format_time(Time.now.utc),
|
|
83
|
+
}
|
|
84
|
+
detail['commitSha'] = commit_sha if commit_sha
|
|
85
|
+
|
|
86
|
+
{
|
|
87
|
+
'version' => '0',
|
|
88
|
+
'id' => SecureRandom.uuid,
|
|
89
|
+
'detail-type' => 'ECS Deployment State Change',
|
|
90
|
+
'source' => 'aws.ecs',
|
|
91
|
+
'account' => '000000000000',
|
|
92
|
+
'time' => PlanMyStuff.format_time(Time.now.utc),
|
|
93
|
+
'region' => 'us-east-1',
|
|
94
|
+
'resources' => [service_arn],
|
|
95
|
+
'detail' => detail,
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [Hash]
|
|
100
|
+
def build_sns_envelope(message:, topic_arn:)
|
|
101
|
+
now = PlanMyStuff.format_time(Time.now.utc)
|
|
102
|
+
{
|
|
103
|
+
'Type' => 'Notification',
|
|
104
|
+
'MessageId' => SecureRandom.uuid,
|
|
105
|
+
'TopicArn' => topic_arn,
|
|
106
|
+
'Subject' => 'ECS Deployment State Change',
|
|
107
|
+
'Message' => JSON.generate(message),
|
|
108
|
+
'Timestamp' => now,
|
|
109
|
+
'SignatureVersion' => '1',
|
|
110
|
+
'Signature' => 'simulated',
|
|
111
|
+
'SigningCertURL' => 'https://example.com/cert.pem',
|
|
112
|
+
'UnsubscribeURL' => 'https://example.com/unsubscribe',
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -15,10 +15,6 @@ module PlanMyStuff
|
|
|
15
15
|
attr_accessor :rails_env
|
|
16
16
|
# @return [String, nil] consuming app name from config
|
|
17
17
|
attr_accessor :app_name
|
|
18
|
-
# @return [Time, nil] timestamp of creation
|
|
19
|
-
attr_accessor :created_at
|
|
20
|
-
# @return [Time, nil] timestamp of last update
|
|
21
|
-
attr_accessor :updated_at
|
|
22
18
|
# @return [Integer, nil] consuming app's user ID of the creator
|
|
23
19
|
attr_accessor :created_by
|
|
24
20
|
# @return [String] "public" or "internal"
|
|
@@ -33,20 +29,19 @@ module PlanMyStuff
|
|
|
33
29
|
#
|
|
34
30
|
# @param metadata [BaseMetadata]
|
|
35
31
|
# @param hash [Hash]
|
|
32
|
+
# @param custom_fields_schema [Hash{Symbol => Hash}] merged schema for this context
|
|
36
33
|
#
|
|
37
34
|
# @return [void]
|
|
38
35
|
#
|
|
39
|
-
def apply_common_from_hash(metadata, hash)
|
|
36
|
+
def apply_common_from_hash(metadata, hash, custom_fields_schema)
|
|
40
37
|
metadata.schema_version = hash[:schema_version]
|
|
41
38
|
metadata.gem_version = hash[:gem_version]
|
|
42
39
|
metadata.rails_env = hash[:rails_env]
|
|
43
40
|
metadata.app_name = hash[:app_name]
|
|
44
|
-
metadata.created_at = parse_time(hash[:created_at])
|
|
45
|
-
metadata.updated_at = parse_time(hash[:updated_at])
|
|
46
41
|
metadata.created_by = hash[:created_by]
|
|
47
42
|
metadata.visibility = hash.fetch(:visibility, 'internal')
|
|
48
|
-
metadata.custom_fields = CustomFields.new(
|
|
49
|
-
|
|
43
|
+
metadata.custom_fields = PlanMyStuff::CustomFields.new(
|
|
44
|
+
custom_fields_schema,
|
|
50
45
|
hash[:custom_fields] || {},
|
|
51
46
|
)
|
|
52
47
|
end
|
|
@@ -57,24 +52,28 @@ module PlanMyStuff
|
|
|
57
52
|
# @param user [Object, Integer] user object or user_id
|
|
58
53
|
# @param visibility [String] "public" or "internal"
|
|
59
54
|
# @param custom_fields_data [Hash]
|
|
55
|
+
# @param custom_fields_schema [Hash{Symbol => Hash}] merged schema for this context
|
|
60
56
|
#
|
|
61
57
|
# @return [void]
|
|
62
58
|
#
|
|
63
|
-
def apply_common_build(
|
|
59
|
+
def apply_common_build(
|
|
60
|
+
metadata,
|
|
61
|
+
user:,
|
|
62
|
+
visibility: 'internal',
|
|
63
|
+
custom_fields_data: {},
|
|
64
|
+
custom_fields_schema: {}
|
|
65
|
+
)
|
|
64
66
|
config = PlanMyStuff.configuration
|
|
65
|
-
now = Time.now.utc
|
|
66
67
|
|
|
67
68
|
metadata.schema_version = self::SCHEMA_VERSION
|
|
68
69
|
metadata.gem_version = PlanMyStuff::VERSION::STRING
|
|
69
70
|
metadata.rails_env = (defined?(Rails) && Rails.respond_to?(:env)) ? Rails.env.to_s : nil
|
|
70
71
|
metadata.app_name = config.app_name
|
|
71
|
-
|
|
72
|
-
metadata.
|
|
73
|
-
resolved = UserResolver.resolve(user)
|
|
74
|
-
metadata.created_by = resolved.present? ? UserResolver.user_id(resolved) : nil
|
|
72
|
+
resolved = PlanMyStuff::UserResolver.resolve(user)
|
|
73
|
+
metadata.created_by = resolved.present? ? PlanMyStuff::UserResolver.user_id(resolved) : nil
|
|
75
74
|
metadata.visibility = visibility
|
|
76
|
-
metadata.custom_fields = CustomFields.new(
|
|
77
|
-
|
|
75
|
+
metadata.custom_fields = PlanMyStuff::CustomFields.new(
|
|
76
|
+
custom_fields_schema,
|
|
78
77
|
custom_fields_data,
|
|
79
78
|
)
|
|
80
79
|
end
|
|
@@ -92,6 +91,7 @@ module PlanMyStuff
|
|
|
92
91
|
|
|
93
92
|
def initialize
|
|
94
93
|
@visibility = 'internal'
|
|
94
|
+
@custom_fields = {}
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
# @return [Hash]
|
|
@@ -101,8 +101,6 @@ module PlanMyStuff
|
|
|
101
101
|
gem_version: gem_version,
|
|
102
102
|
rails_env: rails_env,
|
|
103
103
|
app_name: app_name,
|
|
104
|
-
created_at: format_time(created_at),
|
|
105
|
-
updated_at: format_time(updated_at),
|
|
106
104
|
created_by: created_by,
|
|
107
105
|
visibility: visibility,
|
|
108
106
|
custom_fields: custom_fields.to_h,
|
|
@@ -119,18 +117,17 @@ module PlanMyStuff
|
|
|
119
117
|
visibility == 'public'
|
|
120
118
|
end
|
|
121
119
|
|
|
120
|
+
# Validates custom fields against the schema.
|
|
121
|
+
#
|
|
122
|
+
# @return [true]
|
|
123
|
+
#
|
|
124
|
+
def validate_custom_fields!
|
|
125
|
+
custom_fields.validate! if custom_fields.is_a?(PlanMyStuff::CustomFields)
|
|
126
|
+
end
|
|
127
|
+
|
|
122
128
|
# @return [String]
|
|
123
129
|
def to_json(...)
|
|
124
130
|
to_h.to_json(...)
|
|
125
131
|
end
|
|
126
|
-
|
|
127
|
-
private
|
|
128
|
-
|
|
129
|
-
# @return [String, nil]
|
|
130
|
-
def format_time(time)
|
|
131
|
-
return if time.nil?
|
|
132
|
-
|
|
133
|
-
time.utc.iso8601
|
|
134
|
-
end
|
|
135
132
|
end
|
|
136
133
|
end
|