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
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_model'
|
|
4
|
+
|
|
3
5
|
module PlanMyStuff
|
|
4
6
|
# Dynamic accessor object for app-defined custom fields stored in metadata.
|
|
5
7
|
# Backed by the config.custom_fields schema, provides both hash-style and
|
|
6
8
|
# method-style access to field values.
|
|
9
|
+
#
|
|
10
|
+
# Includes ActiveModel::Validations for type checking, required field
|
|
11
|
+
# enforcement, and unknown field detection.
|
|
7
12
|
class CustomFields
|
|
13
|
+
TYPE_MAP = {
|
|
14
|
+
string: [String],
|
|
15
|
+
integer: [Integer],
|
|
16
|
+
boolean: [TrueClass, FalseClass],
|
|
17
|
+
array: [Array],
|
|
18
|
+
hash: [Hash],
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
include ActiveModel::Validations
|
|
22
|
+
|
|
23
|
+
validate :validate_custom_fields
|
|
24
|
+
|
|
25
|
+
# @return [Hash{Symbol => Hash}]
|
|
26
|
+
attr_reader :schema
|
|
27
|
+
|
|
8
28
|
# @param schema [Hash{Symbol => Hash}] field definitions from config.custom_fields
|
|
9
29
|
# @param data [Hash] parsed field data from metadata JSON
|
|
10
30
|
#
|
|
@@ -40,26 +60,90 @@ module PlanMyStuff
|
|
|
40
60
|
to_h.to_json(...)
|
|
41
61
|
end
|
|
42
62
|
|
|
63
|
+
# @param method_name [Symbol]
|
|
64
|
+
# @param include_private [Boolean]
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
#
|
|
68
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
69
|
+
key = method_name.to_s.delete_suffix('=').to_sym
|
|
70
|
+
@schema.key?(key) || @data.key?(key) || super
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Dynamic reader/writer access to custom field values. Only resolves field
|
|
74
|
+
# names that appear in the schema or already have a value in @data; unknown
|
|
75
|
+
# names fall through to super (raising NoMethodError).
|
|
76
|
+
#
|
|
77
|
+
# @param method_name [Symbol]
|
|
78
|
+
# @param args [Array]
|
|
79
|
+
#
|
|
80
|
+
# @return [Object]
|
|
81
|
+
#
|
|
82
|
+
def method_missing(method_name, *args)
|
|
83
|
+
name = method_name.to_s
|
|
84
|
+
|
|
85
|
+
if name.end_with?('=')
|
|
86
|
+
key = name.delete_suffix('=').to_sym
|
|
87
|
+
if @schema.key?(key) || @data.key?(key)
|
|
88
|
+
return @data[key] = args.first
|
|
89
|
+
end
|
|
90
|
+
elsif @schema.key?(method_name) || @data.key?(method_name)
|
|
91
|
+
return @data[method_name]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
|
|
43
97
|
private
|
|
44
98
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
99
|
+
# @return [void]
|
|
100
|
+
def validate_custom_fields
|
|
101
|
+
validate_unknown_fields
|
|
102
|
+
validate_required_fields
|
|
103
|
+
validate_field_types
|
|
48
104
|
end
|
|
49
105
|
|
|
50
|
-
|
|
51
|
-
|
|
106
|
+
# @return [void]
|
|
107
|
+
def validate_unknown_fields
|
|
108
|
+
known_keys = @schema.keys
|
|
109
|
+
@data.each_key do |key|
|
|
110
|
+
if known_keys.exclude?(key)
|
|
111
|
+
errors.add(:base, "#{key} is not a recognized custom field")
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
52
115
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
116
|
+
# @return [void]
|
|
117
|
+
def validate_required_fields
|
|
118
|
+
@schema.each do |field_name, field_config|
|
|
119
|
+
if field_config[:required] && !@data.key?(field_name)
|
|
120
|
+
errors.add(:base, "#{field_name} is required")
|
|
57
121
|
end
|
|
58
|
-
elsif @schema.key?(method_name) || @data.key?(method_name)
|
|
59
|
-
return @data[method_name]
|
|
60
122
|
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @return [void]
|
|
126
|
+
def validate_field_types
|
|
127
|
+
@schema.each do |field_name, field_config|
|
|
128
|
+
next unless @data.key?(field_name)
|
|
129
|
+
|
|
130
|
+
value = @data[field_name]
|
|
131
|
+
next if value.nil?
|
|
61
132
|
|
|
62
|
-
|
|
133
|
+
expected_type = field_config[:type]
|
|
134
|
+
ruby_types = TYPE_MAP[expected_type]
|
|
135
|
+
next if ruby_types.nil?
|
|
136
|
+
next if ruby_types.any? { |t| value.is_a?(t) }
|
|
137
|
+
|
|
138
|
+
expected_name =
|
|
139
|
+
if expected_type == :boolean
|
|
140
|
+
'TrueClass/FalseClass'
|
|
141
|
+
else
|
|
142
|
+
ruby_types.first.name
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
errors.add(:base, "#{field_name} must be a #{expected_name}, got #{value.class}")
|
|
146
|
+
end
|
|
63
147
|
end
|
|
64
148
|
end
|
|
65
149
|
end
|
data/lib/plan_my_stuff/engine.rb
CHANGED
|
@@ -4,8 +4,20 @@ module PlanMyStuff
|
|
|
4
4
|
class Engine < ::Rails::Engine
|
|
5
5
|
isolate_namespace PlanMyStuff
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# Opt-in (via +config.eager_load_controllers_on_boot+): eager-load the engine's controllers regardless of the host
|
|
8
|
+
# app's eager_load setting. Without this, `defined?(PlanMyStuff::SomeController)` returns nil in host-app dev mode
|
|
9
|
+
# until something explicitly references the constant, since `defined?` does not trigger Zeitwerk autoload.
|
|
10
|
+
config.after_initialize do
|
|
11
|
+
next unless PlanMyStuff.configuration.eager_load_controllers_on_boot
|
|
12
|
+
|
|
13
|
+
controllers_dir = File.expand_path('../../app/controllers', __dir__)
|
|
14
|
+
loader = Rails.autoloaders.main
|
|
15
|
+
|
|
16
|
+
if loader.respond_to?(:eager_load_dir)
|
|
17
|
+
loader.eager_load_dir(controllers_dir)
|
|
18
|
+
else
|
|
19
|
+
Dir.glob(File.join(controllers_dir, '**/*.rb')).each { |path| require path }
|
|
20
|
+
end
|
|
9
21
|
end
|
|
10
22
|
end
|
|
11
23
|
end
|
data/lib/plan_my_stuff/errors.rb
CHANGED
|
@@ -6,7 +6,7 @@ module PlanMyStuff
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
# Raised when GitHub REST API returns a non-success HTTP status
|
|
9
|
-
class APIError < Error
|
|
9
|
+
class APIError < PlanMyStuff::Error
|
|
10
10
|
# @return [Integer]
|
|
11
11
|
attr_reader :status
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ module PlanMyStuff
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# Raised when GitHub GraphQL API returns errors in the response body
|
|
23
|
-
class GraphQLError < Error
|
|
23
|
+
class GraphQLError < PlanMyStuff::Error
|
|
24
24
|
# @return [Array<Hash>]
|
|
25
25
|
attr_reader :errors
|
|
26
26
|
|
|
@@ -34,7 +34,7 @@ module PlanMyStuff
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Raised when GitHub rate limit is exhausted (429 or rate limit headers)
|
|
37
|
-
class RateLimitError < Error
|
|
37
|
+
class RateLimitError < PlanMyStuff::Error
|
|
38
38
|
# @return [Time]
|
|
39
39
|
attr_reader :retry_after
|
|
40
40
|
|
|
@@ -48,7 +48,7 @@ module PlanMyStuff
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Raised when an object has been modified remotely since it was loaded
|
|
51
|
-
class StaleObjectError < Error
|
|
51
|
+
class StaleObjectError < PlanMyStuff::Error
|
|
52
52
|
# @return [Time, nil]
|
|
53
53
|
attr_reader :local_updated_at
|
|
54
54
|
|
|
@@ -66,8 +66,19 @@ module PlanMyStuff
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Raised when a webhook HMAC-SHA256 signature check fails
|
|
70
|
+
class WebhookSignatureError < PlanMyStuff::Error
|
|
71
|
+
def initialize(message = 'Invalid webhook signature')
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Raised when a pipeline operation fails
|
|
77
|
+
class PipelineError < PlanMyStuff::Error
|
|
78
|
+
end
|
|
79
|
+
|
|
69
80
|
# Raised when custom field validation fails (Phase 1)
|
|
70
|
-
class ValidationError < Error
|
|
81
|
+
class ValidationError < PlanMyStuff::Error
|
|
71
82
|
# @return [String, nil]
|
|
72
83
|
attr_reader :field
|
|
73
84
|
|
|
@@ -84,4 +95,53 @@ module PlanMyStuff
|
|
|
84
95
|
super(message)
|
|
85
96
|
end
|
|
86
97
|
end
|
|
98
|
+
|
|
99
|
+
# Raised when a caller is not authorized to perform an action (e.g.
|
|
100
|
+
# a non-support user attempts to manage approvers on an issue).
|
|
101
|
+
class AuthorizationError < PlanMyStuff::Error
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Raised when an operation is attempted on an issue whose conversation
|
|
105
|
+
# is locked on GitHub (archived or manually locked).
|
|
106
|
+
class LockedIssueError < PlanMyStuff::Error
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Raised when an Issue Fields API call is attempted while
|
|
110
|
+
# +config.issue_fields_enabled+ is +false+. Consumers whose org has not been
|
|
111
|
+
# admitted to the Issue Fields public preview flip the flag off; this error
|
|
112
|
+
# surfaces faster (and with a clearer message) than the underlying
|
|
113
|
+
# +GraphQLError+ that GitHub would otherwise return.
|
|
114
|
+
class IssueFieldsNotEnabledError < PlanMyStuff::Error
|
|
115
|
+
def initialize(message = nil)
|
|
116
|
+
super(message || 'Issue Fields are disabled; set config.issue_fields_enabled = true to enable')
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Raised by +PlanMyStuff::Pipeline+ forward transitions when the linked
|
|
121
|
+
# +Issue+ has any pending manager approvals.
|
|
122
|
+
class PendingApprovalsError < PlanMyStuff::ValidationError
|
|
123
|
+
# @return [PlanMyStuff::Issue, nil]
|
|
124
|
+
attr_reader :issue
|
|
125
|
+
|
|
126
|
+
# @return [Integer]
|
|
127
|
+
attr_reader :pending_count
|
|
128
|
+
|
|
129
|
+
# @param message [String, nil]
|
|
130
|
+
# @param issue [PlanMyStuff::Issue, nil]
|
|
131
|
+
# @param pending_count [Integer]
|
|
132
|
+
#
|
|
133
|
+
def initialize(message = nil, issue: nil, pending_count: 0)
|
|
134
|
+
@issue = issue
|
|
135
|
+
@pending_count = pending_count
|
|
136
|
+
super(message || default_message)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
# @return [String]
|
|
142
|
+
def default_message
|
|
143
|
+
"Issue ##{issue&.number} has #{pending_count} pending approval(s); " \
|
|
144
|
+
'cannot move forward through pipeline.'
|
|
145
|
+
end
|
|
146
|
+
end
|
|
87
147
|
end
|