Class: PlanMyStuff::Issue
- Inherits:
-
ApplicationRecord
- Object
- ApplicationRecord
- PlanMyStuff::Issue
- Defined in:
- lib/plan_my_stuff/issue.rb
Overview
Wraps a GitHub issue with parsed PMS metadata and comments. Class methods provide the public API for CRUD operations.
Follows an ActiveRecord-style pattern:
-
‘Issue.new(**attrs)` creates an unpersisted instance
-
‘Issue.create!` / `Issue.find` / `Issue.list` return persisted instances
-
‘issue.save!` / `issue.update!` / `issue.reload` for persistence
Instance Attribute Summary collapse
-
#body ⇒ String?
Returns the issue body content.
-
#labels ⇒ Array<String>
Label names.
-
#metadata ⇒ PlanMyStuff::IssueMetadata
readonly
Parsed metadata (empty when no PMS metadata present).
-
#number ⇒ Integer
readonly
GitHub issue number.
-
#raw_body ⇒ String
readonly
Full body as stored on GitHub.
-
#repo ⇒ String
Resolved repo path (e.g. “Org/Repo”).
-
#state ⇒ String
Issue state (“open” or “closed”).
-
#title ⇒ String
Issue title.
Class Method Summary collapse
-
.add_viewers(number:, user_ids:, repo: nil) ⇒ Object
Adds user IDs to the visibility allowlist of an issue’s metadata.
-
.create!(title:, body:, repo: nil, labels: [], user: nil, metadata: {}, add_to_project: nil, visibility_allowlist: []) ⇒ PlanMyStuff::Issue
Creates a GitHub issue with PMS metadata embedded in the body.
-
.find(number, repo: nil) ⇒ PlanMyStuff::Issue
Finds a single GitHub issue by number and parses its PMS metadata.
-
.list(repo: nil, state: :open, labels: [], page: 1, per_page: 25) ⇒ Array<PlanMyStuff::Issue>
Lists GitHub issues with optional filters and pagination.
-
.remove_viewers(number:, user_ids:, repo: nil) ⇒ Object
Removes user IDs from the visibility allowlist of an issue’s metadata.
-
.update!(number:, repo: nil, title: nil, body: nil, metadata: nil, labels: nil, state: nil, assignees: nil) ⇒ Object
Updates an existing GitHub issue.
Instance Method Summary collapse
-
#body_comment ⇒ PlanMyStuff::Comment?
Returns the comment marked as the issue body, if any.
-
#comments ⇒ Array<PlanMyStuff::Comment>
Lazy-loads and memoizes comments from the GitHub API.
-
#initialize(**attrs) ⇒ Issue
constructor
A new instance of Issue.
-
#pms_comments ⇒ Array<PlanMyStuff::Comment>
Only comments created via PMS.
- #pms_issue? ⇒ Boolean
-
#reload ⇒ self
Re-fetches this issue from GitHub and updates all local attributes.
-
#save! ⇒ self
Persists the issue.
-
#update!(**attrs) ⇒ self
Updates this issue on GitHub.
-
#visible_to?(user) ⇒ Boolean
Delegates visibility check to metadata.
Methods inherited from ApplicationRecord
Constructor Details
#initialize(**attrs) ⇒ Issue
Returns a new instance of Issue.
280 281 282 283 284 285 286 |
# File 'lib/plan_my_stuff/issue.rb', line 280 def initialize(**attrs) @number = attrs.delete(:number) @raw_body = nil @metadata = IssueMetadata.new super @labels ||= [] end |
Instance Attribute Details
#body ⇒ String?
Returns the issue body content. For PMS issues, this is the body from the body comment (stripped of its header). Falls back to the parsed issue body for non-PMS issues.
373 374 375 376 377 378 379 380 381 382 |
# File 'lib/plan_my_stuff/issue.rb', line 373 def body return @body if new_record? return @body unless pms_issue? bc = body_comment return bc.body_without_header if bc.present? @body end |
#labels ⇒ Array<String>
Returns label names.
26 27 28 |
# File 'lib/plan_my_stuff/issue.rb', line 26 def labels @labels end |
#metadata ⇒ PlanMyStuff::IssueMetadata (readonly)
Returns parsed metadata (empty when no PMS metadata present).
17 18 19 |
# File 'lib/plan_my_stuff/issue.rb', line 17 def @metadata end |
#number ⇒ Integer (readonly)
Returns GitHub issue number.
13 14 15 |
# File 'lib/plan_my_stuff/issue.rb', line 13 def number @number end |
#raw_body ⇒ String (readonly)
Returns full body as stored on GitHub.
15 16 17 |
# File 'lib/plan_my_stuff/issue.rb', line 15 def raw_body @raw_body end |
#repo ⇒ String
Returns resolved repo path (e.g. “Org/Repo”).
28 29 30 |
# File 'lib/plan_my_stuff/issue.rb', line 28 def repo @repo end |
#state ⇒ String
Returns issue state (“open” or “closed”).
24 25 26 |
# File 'lib/plan_my_stuff/issue.rb', line 24 def state @state end |
#title ⇒ String
Returns issue title.
20 21 22 |
# File 'lib/plan_my_stuff/issue.rb', line 20 def title @title end |
Class Method Details
.add_viewers(number:, user_ids:, repo: nil) ⇒ Object
Adds user IDs to the visibility allowlist of an issue’s metadata.
185 186 187 188 189 |
# File 'lib/plan_my_stuff/issue.rb', line 185 def add_viewers(number:, user_ids:, repo: nil) modify_allowlist(number: number, repo: repo) do |allowlist| allowlist | Array.wrap(user_ids) end end |
.create!(title:, body:, repo: nil, labels: [], user: nil, metadata: {}, add_to_project: nil, visibility_allowlist: []) ⇒ PlanMyStuff::Issue
Creates a GitHub issue with PMS metadata embedded in the body.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/plan_my_stuff/issue.rb', line 44 def create!( title:, body:, repo: nil, labels: [], user: nil, metadata: {}, add_to_project: nil, visibility_allowlist: [] ) raise(ValidationError.new('body must be present', field: :body, expected_type: :string)) if body.blank? client = PlanMyStuff.client resolved_repo = client.resolve_repo(repo) = IssueMetadata.build( user: user, custom_fields: , ) .visibility_allowlist = Array.wrap(visibility_allowlist) serialized_body = MetadataParser.serialize(.to_h, '') = {} [:labels] = labels if labels.any? result = client.rest(:create_issue, resolved_repo, title, serialized_body, **) issue = build(result, repo: resolved_repo) if add_to_project.present? project_number = resolve_project_number(add_to_project) ProjectItem.create!(issue, project_number: project_number) end Comment.create!( issue: issue, body: body, user: user, visibility: .visibility.to_sym, skip_responded: true, issue_body: true, ) issue end |
.find(number, repo: nil) ⇒ PlanMyStuff::Issue
Finds a single GitHub issue by number and parses its PMS metadata.
139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/plan_my_stuff/issue.rb', line 139 def find(number, repo: nil) client = PlanMyStuff.client resolved_repo = client.resolve_repo(repo) github_issue = client.rest(:issue, resolved_repo, number) if github_issue.respond_to?(:pull_request) && github_issue.pull_request raise(Octokit::NotFound, { method: 'GET', url: "repos/#{resolved_repo}/issues/#{number}" }) end build(github_issue, repo: resolved_repo) end |
.list(repo: nil, state: :open, labels: [], page: 1, per_page: 25) ⇒ Array<PlanMyStuff::Issue>
Lists GitHub issues with optional filters and pagination.
162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/plan_my_stuff/issue.rb', line 162 def list(repo: nil, state: :open, labels: [], page: 1, per_page: 25) client = PlanMyStuff.client resolved_repo = client.resolve_repo(repo) = { state: state.to_s, page: page, per_page: per_page } [:labels] = labels.join(',') if labels.any? github_issues = client.rest(:list_issues, resolved_repo, **) github_issues.filter_map do |gi| next if gi.respond_to?(:pull_request) && gi.pull_request build(gi, repo: resolved_repo) end end |
.remove_viewers(number:, user_ids:, repo: nil) ⇒ Object
Removes user IDs from the visibility allowlist of an issue’s metadata.
199 200 201 202 203 |
# File 'lib/plan_my_stuff/issue.rb', line 199 def remove_viewers(number:, user_ids:, repo: nil) modify_allowlist(number: number, repo: repo) do |allowlist| allowlist - Array.wrap(user_ids) end end |
.update!(number:, repo: nil, title: nil, body: nil, metadata: nil, labels: nil, state: nil, assignees: nil) ⇒ Object
Updates an existing GitHub issue.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/plan_my_stuff/issue.rb', line 103 def update!(number:, repo: nil, title: nil, body: nil, metadata: nil, labels: nil, state: nil, assignees: nil) client = PlanMyStuff.client resolved_repo = client.resolve_repo(repo) = {} [:title] = title unless title.nil? [:labels] = labels unless labels.nil? [:state] = state.to_s unless state.nil? [:assignees] = Array.wrap(assignees) unless assignees.nil? if current = client.rest(:issue, resolved_repo, number) current_body = current.respond_to?(:body) ? current.body : current[:body] parsed = MetadataParser.parse(current_body) = parsed[:metadata] merged_custom_fields = ([:custom_fields] || {}).merge([:custom_fields] || {}) = .merge() [:custom_fields] = merged_custom_fields [:updated_at] = Time.now.utc.iso8601 [:body] = MetadataParser.serialize(, '') end update_body_comment(number, resolved_repo, body) if body client.rest(:update_issue, resolved_repo, number, **) if .any? end |
Instance Method Details
#body_comment ⇒ PlanMyStuff::Comment?
Returns the comment marked as the issue body, if any.
363 364 365 |
# File 'lib/plan_my_stuff/issue.rb', line 363 def body_comment pms_comments.find { |c| c..issue_body? } end |
#comments ⇒ Array<PlanMyStuff::Comment>
Lazy-loads and memoizes comments from the GitHub API.
345 346 347 |
# File 'lib/plan_my_stuff/issue.rb', line 345 def comments @comments ||= load_comments end |
#pms_comments ⇒ Array<PlanMyStuff::Comment>
Returns only comments created via PMS.
355 356 357 |
# File 'lib/plan_my_stuff/issue.rb', line 355 def pms_comments comments.select(&:pms_comment?) end |
#pms_issue? ⇒ Boolean
350 351 352 |
# File 'lib/plan_my_stuff/issue.rb', line 350 def pms_issue? .schema_version.present? end |
#reload ⇒ self
Re-fetches this issue from GitHub and updates all local attributes.
335 336 337 338 339 |
# File 'lib/plan_my_stuff/issue.rb', line 335 def reload fresh = self.class.find(number, repo: repo) hydrate_from_issue(fresh) self end |
#save! ⇒ self
Persists the issue. Creates if new, updates if persisted.
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/plan_my_stuff/issue.rb', line 294 def save! if new_record? created = self.class.create!( title: title, body: body, repo: repo, labels: labels || [], ) hydrate_from_issue(created) else update!(body: body, state: state, labels: labels) end self end |
#update!(**attrs) ⇒ self
Updates this issue on GitHub. Raises StaleObjectError if the remote has been modified since this instance was loaded.
319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/plan_my_stuff/issue.rb', line 319 def update!(**attrs) raise_if_stale! self.class.update!( number: number, repo: repo, **attrs, ) reload end |
#visible_to?(user) ⇒ Boolean
Delegates visibility check to metadata. Non-PMS issues are always visible.
391 392 393 394 395 396 397 |
# File 'lib/plan_my_stuff/issue.rb', line 391 def visible_to?(user) if pms_issue? .visible_to?(user) else UserResolver.support?(UserResolver.resolve(user)) end end |