plan_my_stuff 0.8.0 → 0.10.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.
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlanMyStuff
4
+ module BaseProjectExtractions
5
+ module GraphqlHydration
6
+ private
7
+
8
+ # Builds a summary Project from a list query node. Dispatches to TestingProject when the readme metadata has
9
+ # kind: "testing".
10
+ #
11
+ # @param node [Hash]
12
+ #
13
+ # @return [PlanMyStuff::BaseProject]
14
+ #
15
+ def build_summary(node)
16
+ raw_readme = node[:readme] || ''
17
+ parsed_meta = PlanMyStuff::MetadataParser.parse(raw_readme)
18
+ klass = dispatch_project_class(parsed_meta[:metadata])
19
+ project = klass.new
20
+ project.__send__(:hydrate_summary, node, raw_readme: raw_readme, parsed_meta: parsed_meta)
21
+ project
22
+ end
23
+
24
+ # Builds a detailed Project from a find query response. Dispatches to TestingProject when the readme metadata
25
+ # has kind: "testing".
26
+ #
27
+ # @param graphql_project [Hash]
28
+ # @param items [Array<Hash>]
29
+ # @param next_cursor [String, nil]
30
+ # @param has_next_page [Boolean, nil]
31
+ #
32
+ # @return [PlanMyStuff::BaseProject]
33
+ #
34
+ def build_detail(graphql_project, items:, next_cursor: nil, has_next_page: nil)
35
+ raw_readme = graphql_project[:readme] || ''
36
+ parsed_meta = PlanMyStuff::MetadataParser.parse(raw_readme)
37
+ klass = dispatch_project_class(parsed_meta[:metadata])
38
+ project = klass.new
39
+ project.__send__(
40
+ :hydrate_detail,
41
+ graphql_project,
42
+ items: items,
43
+ next_cursor: next_cursor,
44
+ has_next_page: has_next_page,
45
+ )
46
+ project
47
+ end
48
+
49
+ # @param org [String]
50
+ # @param number [Integer]
51
+ #
52
+ # @return [PlanMyStuff::BaseProject]
53
+ #
54
+ def find_auto_paginated(org, number)
55
+ all_items = []
56
+ cursor = nil
57
+ raw_project = nil
58
+ page = nil
59
+
60
+ loop do
61
+ page = fetch_project_page(org, number, cursor)
62
+ raw_project ||= page[:raw]
63
+ all_items.concat(page[:items])
64
+
65
+ break if !page[:has_next_page] || all_items.length >= PlanMyStuff::BaseProject::MAX_AUTO_PAGINATE_ITEMS
66
+
67
+ cursor = page[:next_cursor]
68
+ end
69
+
70
+ build_detail(
71
+ raw_project,
72
+ items: all_items,
73
+ next_cursor: page[:next_cursor],
74
+ has_next_page: page[:has_next_page],
75
+ )
76
+ end
77
+
78
+ # @param org [String]
79
+ # @param number [Integer]
80
+ # @param cursor [String, nil]
81
+ #
82
+ # @return [PlanMyStuff::BaseProject]
83
+ #
84
+ def find_with_cursor(org, number, cursor:)
85
+ page = fetch_project_page(org, number, cursor)
86
+ build_detail(
87
+ page[:raw],
88
+ items: page[:items],
89
+ next_cursor: page[:next_cursor],
90
+ has_next_page: page[:has_next_page],
91
+ )
92
+ end
93
+
94
+ # Fetches a single page of project data. Returns a lightweight hash for pagination loop consumption (not a
95
+ # Project instance).
96
+ #
97
+ # @param org [String]
98
+ # @param number [Integer]
99
+ # @param cursor [String, nil]
100
+ #
101
+ # @return [Hash] with :raw, :items, :next_cursor, :has_next_page
102
+ #
103
+ def fetch_project_page(org, number, cursor)
104
+ variables = { org: org, number: number }
105
+ variables[:cursor] = cursor if cursor
106
+
107
+ data = PlanMyStuff.client.graphql(
108
+ PlanMyStuff::GraphQL::Queries::FIND_PROJECT,
109
+ variables: variables,
110
+ )
111
+
112
+ raw_project = data.dig(:organization, :projectV2)
113
+ page_info = raw_project.dig(:items, :pageInfo) || {}
114
+ items_data = raw_project.dig(:items, :nodes) || []
115
+
116
+ {
117
+ raw: raw_project,
118
+ items: items_data.map { |item| parse_project_item(item) },
119
+ next_cursor: page_info[:endCursor],
120
+ has_next_page: page_info[:hasNextPage],
121
+ }
122
+ end
123
+
124
+ # @param item [Hash] raw GraphQL project item node
125
+ #
126
+ # @return [Hash]
127
+ #
128
+ def parse_project_item(item)
129
+ content = item[:content] || {}
130
+ field_values = item.dig(:fieldValues, :nodes) || []
131
+ repo_name = content.dig(:repository, :nameWithOwner)
132
+
133
+ {
134
+ id: item[:id],
135
+ type: item[:type],
136
+ content_node_id: content[:id],
137
+ title: content[:title],
138
+ body: content[:body],
139
+ number: content[:number],
140
+ url: content[:url],
141
+ state: content[:state],
142
+ repo: repo_name.present? ? PlanMyStuff::Repo.resolve!(repo_name) : nil,
143
+ status: extract_item_status(field_values),
144
+ field_values: parse_field_values(field_values),
145
+ updated_at: item[:updatedAt],
146
+ github_response: item,
147
+ }
148
+ end
149
+
150
+ # @param field_values [Array<Hash>]
151
+ #
152
+ # @return [String, nil]
153
+ #
154
+ def extract_item_status(field_values)
155
+ status_value = field_values.find { |fv| fv.dig(:field, :name) == 'Status' }
156
+
157
+ status_value&.dig(:name)
158
+ end
159
+
160
+ # @param field_values [Array<Hash>]
161
+ #
162
+ # @return [Hash]
163
+ #
164
+ def parse_field_values(field_values)
165
+ result = {}
166
+
167
+ field_values.each do |fv|
168
+ field_name = fv.dig(:field, :name)
169
+ next unless field_name
170
+
171
+ value = fv[:name] || fv[:text]
172
+ users_node = fv[:users]
173
+ if users_node
174
+ value = (users_node[:nodes] || []).map { |u| u[:login] }
175
+ end
176
+
177
+ result[field_name] = value
178
+ end
179
+
180
+ result
181
+ end
182
+ end
183
+ end
184
+ end
@@ -73,6 +73,7 @@ module PlanMyStuff
73
73
  repo: item_hash[:repo],
74
74
  state: item_hash[:state],
75
75
  status: item_hash[:status],
76
+ updated_at: item_hash[:updated_at],
76
77
  field_values: item_hash[:field_values] || {},
77
78
  project: project,
78
79
  )
@@ -32,6 +32,8 @@ module PlanMyStuff
32
32
  class << self
33
33
  # Creates a comment on a GitHub issue with PMS metadata and a visible header.
34
34
  #
35
+ # @raise [PlanMyStuff::LockedIssueError] if the parent issue is locked
36
+ #
35
37
  # @param issue [PlanMyStuff::Issue] parent issue
36
38
  # @param body [String]
37
39
  # @param user [Object, Integer] user object or user_id
@@ -41,8 +43,6 @@ module PlanMyStuff
41
43
  # @param waiting_on_reply [Boolean] when true and the author is a support user, marks the issue as waiting on
42
44
  # an end-user reply. Ignored for non-support authors.
43
45
  #
44
- # @raise [PlanMyStuff::LockedIssueError] if the parent issue is locked
45
- #
46
46
  # @return [PlanMyStuff::Comment]
47
47
  #
48
48
  def create!(
@@ -279,7 +279,9 @@ module PlanMyStuff
279
279
  )
280
280
  hydrate_from_comment(created)
281
281
  else
282
- update!(user: user, body: body)
282
+ update_attrs = { user: user, body: body }
283
+ update_attrs[:visibility] = visibility if visibility_changed?
284
+ update!(**update_attrs)
283
285
  end
284
286
 
285
287
  self
@@ -84,19 +84,6 @@ module PlanMyStuff
84
84
  #
85
85
  attr_accessor :markdown_options
86
86
 
87
- # Proc returning boolean, or nil (always send). When it returns false the request is deferred to a background job
88
- # instead of hitting GitHub.
89
- #
90
- # @return [Proc, nil]
91
- #
92
- attr_accessor :should_send_request
93
-
94
- # Map of action type to job class name for deferred requests. Keys: :create_ticket, :post_comment, :update_status.
95
- #
96
- # @return [Hash{Symbol => String}]
97
- #
98
- attr_accessor :job_classes
99
-
100
87
  # Fallback actor for notification events when a caller does not pass +user:+. Set to a proc/lambda that returns the
101
88
  # current request user.
102
89
  #
@@ -344,11 +331,12 @@ module PlanMyStuff
344
331
  #
345
332
  attr_accessor :sns_verifier_error
346
333
 
347
- # Named repo configs. Set via config.repos[:element] = 'BrandsInsurance/Element'.
334
+ # Named repo configs. Set via config.repos[:element] = 'BrandsInsurance/Element', or assign a whole hash with
335
+ # config.repos = { element: 'BrandsInsurance/Element', underwriter: 'BrandsInsurance/Underwriter' }.
348
336
  #
349
337
  # @return [Hash{Symbol => String}]
350
338
  #
351
- attr_reader :repos
339
+ attr_accessor :repos
352
340
 
353
341
  # @return [Configuration]
354
342
  def initialize
@@ -359,7 +347,6 @@ module PlanMyStuff
359
347
  @support_method = :support?
360
348
  @markdown_renderer = :commonmarker
361
349
  @markdown_options = {}
362
- @job_classes = {}
363
350
  @custom_fields = {}
364
351
  @issue_custom_fields = {}
365
352
  @comment_custom_fields = {}