plan_my_stuff 0.2.0 → 0.4.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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +569 -38
  4. data/app/controllers/plan_my_stuff/comments_controller.rb +5 -1
  5. data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +102 -0
  6. data/app/controllers/plan_my_stuff/issues/closures_controller.rb +37 -0
  7. data/app/controllers/plan_my_stuff/issues/links_controller.rb +127 -0
  8. data/app/controllers/plan_my_stuff/issues/takes_controller.rb +88 -0
  9. data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +48 -0
  10. data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +47 -0
  11. data/app/controllers/plan_my_stuff/issues_controller.rb +22 -55
  12. data/app/controllers/plan_my_stuff/labels_controller.rb +4 -4
  13. data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +75 -0
  14. data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +40 -0
  15. data/app/controllers/plan_my_stuff/project_items_controller.rb +0 -75
  16. data/app/controllers/plan_my_stuff/projects_controller.rb +65 -1
  17. data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +54 -0
  18. data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +39 -0
  19. data/app/controllers/plan_my_stuff/testing_projects_controller.rb +93 -0
  20. data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +148 -0
  21. data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +284 -0
  22. data/app/jobs/plan_my_stuff/application_job.rb +9 -0
  23. data/app/jobs/plan_my_stuff/reminders_sweep_job.rb +81 -0
  24. data/app/views/plan_my_stuff/comments/partials/_form.html.erb +7 -0
  25. data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +87 -0
  26. data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +2 -2
  27. data/app/views/plan_my_stuff/issues/partials/_links.html.erb +70 -0
  28. data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +2 -2
  29. data/app/views/plan_my_stuff/issues/show.html.erb +46 -3
  30. data/app/views/plan_my_stuff/projects/edit.html.erb +7 -0
  31. data/app/views/plan_my_stuff/projects/index.html.erb +17 -1
  32. data/app/views/plan_my_stuff/projects/new.html.erb +7 -0
  33. data/app/views/plan_my_stuff/projects/partials/_form.html.erb +29 -0
  34. data/app/views/plan_my_stuff/projects/show.html.erb +11 -4
  35. data/app/views/plan_my_stuff/testing_project_items/new.html.erb +12 -0
  36. data/app/views/plan_my_stuff/testing_project_items/results/new.html.erb +22 -0
  37. data/app/views/plan_my_stuff/testing_projects/edit.html.erb +7 -0
  38. data/app/views/plan_my_stuff/testing_projects/new.html.erb +7 -0
  39. data/app/views/plan_my_stuff/testing_projects/partials/_form.html.erb +39 -0
  40. data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +51 -0
  41. data/app/views/plan_my_stuff/testing_projects/partials/items/_form.html.erb +35 -0
  42. data/app/views/plan_my_stuff/testing_projects/show.html.erb +65 -0
  43. data/config/routes.rb +38 -15
  44. data/lib/generators/plan_my_stuff/install/templates/initializer.rb +138 -5
  45. data/lib/plan_my_stuff/application_record.rb +144 -0
  46. data/lib/plan_my_stuff/approval.rb +80 -0
  47. data/lib/plan_my_stuff/archive/sweep.rb +85 -0
  48. data/lib/plan_my_stuff/archive.rb +14 -0
  49. data/lib/plan_my_stuff/aws_sns_simulator.rb +110 -0
  50. data/lib/plan_my_stuff/base_metadata.rb +0 -11
  51. data/lib/plan_my_stuff/base_project.rb +661 -0
  52. data/lib/plan_my_stuff/base_project_item.rb +562 -0
  53. data/lib/plan_my_stuff/base_project_metadata.rb +16 -0
  54. data/lib/plan_my_stuff/cache.rb +197 -0
  55. data/lib/plan_my_stuff/client.rb +7 -0
  56. data/lib/plan_my_stuff/comment.rb +174 -54
  57. data/lib/plan_my_stuff/configuration.rb +254 -8
  58. data/lib/plan_my_stuff/custom_fields.rb +31 -17
  59. data/lib/plan_my_stuff/engine.rb +0 -4
  60. data/lib/plan_my_stuff/errors.rb +49 -0
  61. data/lib/plan_my_stuff/graphql/queries.rb +392 -0
  62. data/lib/plan_my_stuff/issue.rb +1477 -174
  63. data/lib/plan_my_stuff/issue_metadata.rb +122 -0
  64. data/lib/plan_my_stuff/label.rb +82 -11
  65. data/lib/plan_my_stuff/link.rb +144 -0
  66. data/lib/plan_my_stuff/notifications.rb +142 -0
  67. data/lib/plan_my_stuff/pipeline/issue_linker.rb +62 -0
  68. data/lib/plan_my_stuff/pipeline/status.rb +44 -0
  69. data/lib/plan_my_stuff/pipeline.rb +293 -0
  70. data/lib/plan_my_stuff/project.rb +62 -468
  71. data/lib/plan_my_stuff/project_item.rb +3 -417
  72. data/lib/plan_my_stuff/project_item_metadata.rb +55 -0
  73. data/lib/plan_my_stuff/project_metadata.rb +47 -0
  74. data/lib/plan_my_stuff/reminders/closer.rb +70 -0
  75. data/lib/plan_my_stuff/reminders/fire.rb +129 -0
  76. data/lib/plan_my_stuff/reminders/sweep.rb +54 -0
  77. data/lib/plan_my_stuff/reminders.rb +16 -0
  78. data/lib/plan_my_stuff/test_helpers.rb +260 -15
  79. data/lib/plan_my_stuff/testing_project.rb +291 -0
  80. data/lib/plan_my_stuff/testing_project_item.rb +184 -0
  81. data/lib/plan_my_stuff/testing_project_metadata.rb +94 -0
  82. data/lib/plan_my_stuff/user_resolver.rb +8 -3
  83. data/lib/plan_my_stuff/version.rb +1 -1
  84. data/lib/plan_my_stuff/webhook_replayer.rb +280 -0
  85. data/lib/plan_my_stuff.rb +16 -0
  86. data/lib/tasks/plan_my_stuff.rake +163 -0
  87. metadata +54 -2
@@ -7,6 +7,13 @@ module PlanMyStuff
7
7
  # Provides shared persistence predicates and utility helpers.
8
8
  class ApplicationRecord
9
9
  include ActiveModel::Model
10
+ include ActiveModel::Attributes
11
+ include ActiveModel::Dirty
12
+ include ActiveModel::Serializers::JSON
13
+
14
+ # @return [Object, nil] raw GitHub API response this record was hydrated from.
15
+ # Escape hatch for consuming apps to access fields the gem doesn't expose.
16
+ attr_reader :github_response
10
17
 
11
18
  class << self
12
19
  # Reads a field from an object that may respond to method calls or hash access.
@@ -19,11 +26,96 @@ module PlanMyStuff
19
26
  def read_field(obj, field)
20
27
  obj.respond_to?(field) ? obj.public_send(field) : obj[field]
21
28
  end
29
+
30
+ private
31
+
32
+ # @param client [PlanMyStuff::Client]
33
+ #
34
+ # @return [Boolean]
35
+ #
36
+ def not_modified?(client)
37
+ response = client.last_response
38
+ return false if response.nil?
39
+
40
+ response.status == 304
41
+ end
42
+
43
+ # Captures the +ETag+ header from the most recent REST response and
44
+ # forwards it to +cache_writer+.
45
+ #
46
+ # @param client [PlanMyStuff::Client]
47
+ # @param repo [String]
48
+ # @param id [Integer, nil]
49
+ # @param body [Object] parsed GitHub response
50
+ # @param cache_writer [Symbol] +PlanMyStuff::Cache+ method name, e.g. +:write_issue+
51
+ #
52
+ # @return [void]
53
+ #
54
+ def store_etag_to_cache(client, repo, id, body, cache_writer:)
55
+ return if id.nil?
56
+
57
+ response = client.last_response
58
+ return if response.nil?
59
+
60
+ etag = response.headers && response.headers['etag']
61
+ return if etag.blank?
62
+
63
+ PlanMyStuff::Cache.public_send(cache_writer, repo, id, etag: etag, body: body)
64
+ end
65
+
66
+ # Captures the +ETag+ header from the most recent REST response and
67
+ # forwards it to +Cache.write_list+.
68
+ #
69
+ # @param client [PlanMyStuff::Client]
70
+ # @param resource [Symbol] :issue or :comment
71
+ # @param repo [String]
72
+ # @param params [Hash] query params that identify this list
73
+ # @param body [Object] parsed GitHub list response
74
+ #
75
+ # @return [void]
76
+ #
77
+ def store_list_etag_to_cache(client, resource, repo, params, body)
78
+ response = client.last_response
79
+ return if response.nil?
80
+
81
+ etag = response.headers && response.headers['etag']
82
+ return if etag.blank?
83
+
84
+ PlanMyStuff::Cache.write_list(resource, repo, params, etag: etag, body: body)
85
+ end
86
+
87
+ # Reads a REST resource through the ETag cache.
88
+ #
89
+ # On a cache hit sends +If-None-Match+; a 304 returns the cached
90
+ # body. A 200 stores the new ETag via +cache_writer+ and returns
91
+ # the fresh result.
92
+ #
93
+ # @param client [PlanMyStuff::Client]
94
+ # @param repo [String]
95
+ # @param id [Integer]
96
+ # @param rest_method [Symbol] Octokit method name
97
+ # @param cache_reader [Symbol] +PlanMyStuff::Cache+ method name, e.g. +:read_issue+
98
+ # @param cache_writer [Symbol] +PlanMyStuff::Cache+ method name, e.g. +:write_issue+
99
+ #
100
+ # @return [Object] parsed GitHub response
101
+ #
102
+ def fetch_with_etag_cache(client, repo, id, rest_method:, cache_reader:, cache_writer:)
103
+ cached = PlanMyStuff::Cache.public_send(cache_reader, repo, id)
104
+ options = cached ? { headers: { 'If-None-Match' => cached[:etag] } } : {}
105
+
106
+ result = client.rest(rest_method, repo, id, **options)
107
+
108
+ return cached[:body] if cached && not_modified?(client)
109
+
110
+ store_etag_to_cache(client, repo, id, result, cache_writer: cache_writer)
111
+ result
112
+ end
22
113
  end
23
114
 
24
115
  def initialize(**)
25
116
  super
26
117
  @persisted = false
118
+ @destroyed = false
27
119
  end
28
120
 
29
121
  # @return [Boolean]
@@ -36,8 +128,37 @@ module PlanMyStuff
36
128
  !@persisted
37
129
  end
38
130
 
131
+ # @return [Boolean]
132
+ def destroyed?
133
+ @destroyed
134
+ end
135
+
39
136
  private
40
137
 
138
+ # Marks this record as persisted. Subclasses call this after a
139
+ # successful create/find/update against the underlying GitHub
140
+ # resource. Also applies any pending dirty changes so the record
141
+ # is clean after hydration and +#previous_changes+ reflects the
142
+ # values that were just loaded.
143
+ #
144
+ # @return [void]
145
+ #
146
+ def persisted!
147
+ @persisted = true
148
+ changes_applied
149
+ end
150
+
151
+ # Marks this record as destroyed. Subclasses call this from their
152
+ # +destroy!+ implementations after the underlying remote resource
153
+ # has been deleted.
154
+ #
155
+ # @return [void]
156
+ #
157
+ def destroyed!
158
+ @destroyed = true
159
+ @persisted = false
160
+ end
161
+
41
162
  # Reads a field from an object that may respond to method calls or hash access.
42
163
  #
43
164
  # @param obj [Object]
@@ -48,5 +169,28 @@ module PlanMyStuff
48
169
  def read_field(obj, field)
49
170
  self.class.read_field(obj, field)
50
171
  end
172
+
173
+ # Reads a field from an object, returning nil if the field does not exist.
174
+ #
175
+ # @param obj [Object]
176
+ # @param field [Symbol]
177
+ #
178
+ # @return [Object, nil]
179
+ #
180
+ def safe_read_field(obj, field)
181
+ read_field(obj, field)
182
+ rescue NameError
183
+ nil
184
+ end
185
+
186
+ # @return [Time, nil]
187
+ def parse_github_time(value)
188
+ return if value.nil?
189
+ return value.utc if value.is_a?(Time)
190
+
191
+ Time.parse(value.to_s).utc
192
+ rescue ArgumentError
193
+ nil
194
+ end
51
195
  end
52
196
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model'
4
+
5
+ module PlanMyStuff
6
+ # Value object representing a single manager approval on an +Issue+.
7
+ # Persisted in +IssueMetadata#approvals+ and returned from
8
+ # +Issue.request_approvals!+, +Issue.approve!+, and
9
+ # +Issue.revoke_approval!+.
10
+ #
11
+ # Mirrors +PlanMyStuff::Link+: +ActiveModel::Attributes+-backed, with
12
+ # +Serializers::JSON+ for round-trip through the metadata blob.
13
+ #
14
+ class Approval
15
+ STATUSES = %w[pending approved].freeze
16
+
17
+ include ActiveModel::Model
18
+ include ActiveModel::Attributes
19
+ include ActiveModel::Serializers::JSON
20
+
21
+ # @return [Integer] app-side user id of the required approver
22
+ attribute :user_id, :integer
23
+ # @return [String] +"pending"+ or +"approved"+
24
+ attribute :status, :string, default: 'pending'
25
+ # @return [DateTime, nil] timestamp when status flipped to +"approved"+
26
+ attribute :approved_at, :datetime
27
+
28
+ validates :user_id, presence: true, numericality: { greater_than: 0, only_integer: true }
29
+ validates :status, inclusion: { in: STATUSES }
30
+
31
+ # @return [Boolean]
32
+ def pending?
33
+ status == 'pending'
34
+ end
35
+
36
+ # @return [Boolean]
37
+ def approved?
38
+ status == 'approved'
39
+ end
40
+
41
+ # Lazy-resolves the app-side user for this approval.
42
+ # Not memoized -- +PlanMyStuff::UserResolver+ owns caching.
43
+ #
44
+ # @return [Object, nil]
45
+ #
46
+ def user
47
+ PlanMyStuff::UserResolver.resolve(user_id)
48
+ end
49
+
50
+ # @return [Hash]
51
+ def to_h
52
+ {
53
+ user_id: user_id,
54
+ status: status,
55
+ approved_at: approved_at&.iso8601,
56
+ }
57
+ end
58
+
59
+ # Two approvals are equal when they track the same user AND carry the
60
+ # same status. A pending and an approved record for the same user are
61
+ # NOT equal -- matters for set arithmetic during state transitions.
62
+ #
63
+ # @param other [Object]
64
+ #
65
+ # @return [Boolean]
66
+ #
67
+ def ==(other)
68
+ return false unless other.is_a?(PlanMyStuff::Approval)
69
+
70
+ user_id == other.user_id && status == other.status
71
+ end
72
+
73
+ alias eql? ==
74
+
75
+ # @return [Integer]
76
+ def hash
77
+ [user_id, status].hash
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlanMyStuff
4
+ module Archive
5
+ # Walks a single repo's closed issues, archiving those that have
6
+ # aged past +config.archive_closed_after_days+. Excludes issues
7
+ # auto-closed by +Reminders::Closer+ (+metadata.closed_by_inactivity+)
8
+ # and any that are already archived (marker timestamp or label).
9
+ #
10
+ # Paginates +Issue.list(state: :closed)+ until either an empty page
11
+ # or the hard +MAX_PAGES+ cap. GitHub's +list_issues+ returns in
12
+ # created-desc order, so closed_at is not monotonic across pages;
13
+ # we can't short-circuit on "this page is all within cutoff." Each
14
+ # subsequent sweep skips already-archived issues cheaply via
15
+ # +skip?+, so the steady-state walk is bounded in practice.
16
+ class Sweep
17
+ PAGE_SIZE = 50
18
+ MAX_PAGES = 20
19
+
20
+ # @param repo [Symbol, String] repo key or full name
21
+ # @param now [Time] clock reference
22
+ #
23
+ def initialize(repo:, now: Time.now.utc)
24
+ @repo = repo
25
+ @now = now.utc
26
+ end
27
+
28
+ # Runs the sweep. No-op when +config.archiving_enabled+ is false.
29
+ #
30
+ # @return [void]
31
+ #
32
+ def call
33
+ return unless PlanMyStuff.configuration.archiving_enabled
34
+
35
+ (1..MAX_PAGES).each do |page|
36
+ issues = PlanMyStuff::Issue.list(
37
+ repo: @repo,
38
+ state: :closed,
39
+ page: page,
40
+ per_page: PAGE_SIZE,
41
+ )
42
+
43
+ break if issues.empty?
44
+
45
+ process(issues)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # Per-issue exceptions are swallowed so a single bad archive
52
+ # doesn't halt the whole sweep.
53
+ #
54
+ # @param issues [Array<PlanMyStuff::Issue>]
55
+ #
56
+ # @return [void]
57
+ #
58
+ def process(issues)
59
+ issues.each do |issue|
60
+ next if skip?(issue)
61
+
62
+ issue.archive!(now: @now)
63
+ rescue => e
64
+ warn("[PlanMyStuff::Archive::Sweep] #{issue.repo}##{issue.number} failed: #{e.class}: #{e.message}")
65
+ end
66
+ end
67
+
68
+ # @param issue [PlanMyStuff::Issue]
69
+ #
70
+ # @return [Boolean]
71
+ #
72
+ def skip?(issue)
73
+ return true unless issue.pms_issue?
74
+
75
+ return true if issue.metadata.closed_by_inactivity
76
+ return true if issue.metadata.archived_at.present?
77
+ return true if issue.labels.include?(PlanMyStuff.configuration.archived_label)
78
+ return true if issue.closed_at.nil?
79
+
80
+ age_days = (@now - issue.closed_at.utc) / 1.day
81
+ age_days < PlanMyStuff.configuration.archive_closed_after_days
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlanMyStuff
4
+ # Auto-archive engine. The +Sweep+ class walks closed issues in a repo
5
+ # and delegates to +Issue#archive!+ for each issue whose +closed_at+
6
+ # has aged past +config.archive_closed_after_days+.
7
+ #
8
+ # Entry point for the sweep lives in +RemindersSweepJob+; this module
9
+ # holds the POROs so they can be unit-tested without ActiveJob.
10
+ module Archive
11
+ end
12
+ end
13
+
14
+ require_relative 'archive/sweep'
@@ -0,0 +1,110 @@
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
+ # @param endpoint_url [String] full URL including path
31
+ # @param event_name [String] ECS eventName (default completed)
32
+ #
33
+ # @return [Net::HTTPResponse]
34
+ #
35
+ def post(endpoint_url:, event_name: DEFAULT_EVENT)
36
+ config = PlanMyStuff.configuration
37
+ raise('PlanMyStuff.configuration.sns_topic_arn is blank') if config.sns_topic_arn.blank?
38
+ raise('PlanMyStuff.configuration.aws_service_identifier is blank') if config.aws_service_identifier.blank?
39
+
40
+ topic_arn = config.sns_topic_arn
41
+ service_arn = "arn:aws:ecs:us-east-1:000000000000:service/simulated-cluster/#{config.aws_service_identifier}"
42
+ commit_sha = config.production_commit_sha
43
+
44
+ message = build_ecs_message(event_name: event_name, service_arn: service_arn, commit_sha: commit_sha)
45
+ envelope = build_sns_envelope(message: message, topic_arn: topic_arn)
46
+ raw_body = JSON.generate(envelope)
47
+
48
+ uri = URI(endpoint_url)
49
+ http = Net::HTTP.new(uri.host, uri.port)
50
+ http.use_ssl = uri.scheme == 'https'
51
+
52
+ request = Net::HTTP::Post.new(uri.request_uri)
53
+ request['Content-Type'] = 'application/json'
54
+ request['x-amz-sns-message-type'] = 'Notification'
55
+ request.body = raw_body
56
+
57
+ $stdout.puts("POST #{endpoint_url}")
58
+ $stdout.puts(" event: #{event_name}")
59
+ $stdout.puts(" topic: #{topic_arn}")
60
+ $stdout.puts(" resources: #{service_arn}")
61
+ $stdout.puts(" commit: #{commit_sha || '(none)'}")
62
+ $stdout.puts('---')
63
+
64
+ response = http.request(request)
65
+ $stdout.puts("HTTP #{response.code} #{response.message}")
66
+ $stdout.puts(response.body) if response.body.present?
67
+ response
68
+ end
69
+
70
+ # @return [Hash]
71
+ def build_ecs_message(event_name:, service_arn:, commit_sha:)
72
+ detail = {
73
+ 'eventType' => 'INFO',
74
+ 'eventName' => event_name,
75
+ 'deploymentId' => "ecs-svc/#{SecureRandom.hex(8)}",
76
+ 'updatedAt' => Time.now.utc.iso8601,
77
+ }
78
+ detail['commitSha'] = commit_sha if commit_sha
79
+
80
+ {
81
+ 'version' => '0',
82
+ 'id' => SecureRandom.uuid,
83
+ 'detail-type' => 'ECS Deployment State Change',
84
+ 'source' => 'aws.ecs',
85
+ 'account' => '000000000000',
86
+ 'time' => Time.now.utc.iso8601,
87
+ 'region' => 'us-east-1',
88
+ 'resources' => [service_arn],
89
+ 'detail' => detail,
90
+ }
91
+ end
92
+
93
+ # @return [Hash]
94
+ def build_sns_envelope(message:, topic_arn:)
95
+ now = Time.now.utc.iso8601
96
+ {
97
+ 'Type' => 'Notification',
98
+ 'MessageId' => SecureRandom.uuid,
99
+ 'TopicArn' => topic_arn,
100
+ 'Subject' => 'ECS Deployment State Change',
101
+ 'Message' => JSON.generate(message),
102
+ 'Timestamp' => now,
103
+ 'SignatureVersion' => '1',
104
+ 'Signature' => 'simulated',
105
+ 'SigningCertURL' => 'https://example.com/cert.pem',
106
+ 'UnsubscribeURL' => 'https://example.com/unsubscribe',
107
+ }
108
+ end
109
+ end
110
+ 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"
@@ -42,8 +38,6 @@ module PlanMyStuff
42
38
  metadata.gem_version = hash[:gem_version]
43
39
  metadata.rails_env = hash[:rails_env]
44
40
  metadata.app_name = hash[:app_name]
45
- metadata.created_at = parse_time(hash[:created_at])
46
- metadata.updated_at = parse_time(hash[:updated_at])
47
41
  metadata.created_by = hash[:created_by]
48
42
  metadata.visibility = hash.fetch(:visibility, 'internal')
49
43
  metadata.custom_fields = CustomFields.new(
@@ -70,14 +64,11 @@ module PlanMyStuff
70
64
  custom_fields_schema: {}
71
65
  )
72
66
  config = PlanMyStuff.configuration
73
- now = Time.now.utc
74
67
 
75
68
  metadata.schema_version = self::SCHEMA_VERSION
76
69
  metadata.gem_version = PlanMyStuff::VERSION::STRING
77
70
  metadata.rails_env = (defined?(Rails) && Rails.respond_to?(:env)) ? Rails.env.to_s : nil
78
71
  metadata.app_name = config.app_name
79
- metadata.created_at = now
80
- metadata.updated_at = now
81
72
  resolved = UserResolver.resolve(user)
82
73
  metadata.created_by = resolved.present? ? UserResolver.user_id(resolved) : nil
83
74
  metadata.visibility = visibility
@@ -110,8 +101,6 @@ module PlanMyStuff
110
101
  gem_version: gem_version,
111
102
  rails_env: rails_env,
112
103
  app_name: app_name,
113
- created_at: format_time(created_at),
114
- updated_at: format_time(updated_at),
115
104
  created_by: created_by,
116
105
  visibility: visibility,
117
106
  custom_fields: custom_fields.to_h,