Class: PlanMyStuff::ProjectItem

Inherits:
ApplicationRecord show all
Defined in:
lib/plan_my_stuff/project_item.rb

Overview

Wraps a single item within a GitHub Projects V2 project. Stores a reference to its parent Project for delegation.

Class methods:

ProjectItem.create!(issue)                             -- add existing issue
ProjectItem.create!(title, draft: true, body: "...")  -- create draft item
ProjectItem.move_item(item_id:, status:)
ProjectItem.assign(item_id:, assignee:)

Instance methods:

item.move_to!("Done")
item.assign!("octocat")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

#new_record?, #persisted?

Constructor Details

#initialize(**attrs) ⇒ ProjectItem

Returns a new instance of ProjectItem.

See Also:

  • super


362
363
364
365
366
367
368
# File 'lib/plan_my_stuff/project_item.rb', line 362

def initialize(**attrs)
  @id = attrs.delete(:id)
  @type = attrs.delete(:type)
  @content_node_id = attrs.delete(:content_node_id)
  super
  @field_values ||= {}
end

Instance Attribute Details

#content_node_idString? (readonly)

Returns node ID of the underlying content (Issue, PR, or DraftIssue).

Returns:

  • (String, nil)

    node ID of the underlying content (Issue, PR, or DraftIssue)



21
22
23
# File 'lib/plan_my_stuff/project_item.rb', line 21

def content_node_id
  @content_node_id
end

#field_valuesHash

Returns:

  • (Hash)


37
38
39
# File 'lib/plan_my_stuff/project_item.rb', line 37

def field_values
  @field_values
end

#idString (readonly)

Returns GitHub node ID (e.g. “PVTI_…”).

Returns:

  • (String)

    GitHub node ID (e.g. “PVTI_…”)



19
20
21
# File 'lib/plan_my_stuff/project_item.rb', line 19

def id
  @id
end

#issuePlanMyStuff::Issue?

Returns linked issue (nil for draft items).

Returns:



41
42
43
# File 'lib/plan_my_stuff/project_item.rb', line 41

def issue
  @issue
end

#numberInteger?

Returns:

  • (Integer, nil)


29
30
31
# File 'lib/plan_my_stuff/project_item.rb', line 29

def number
  @number
end

#projectPlanMyStuff::Project?

Returns:



39
40
41
# File 'lib/plan_my_stuff/project_item.rb', line 39

def project
  @project
end

#stateString?

Returns:

  • (String, nil)


33
34
35
# File 'lib/plan_my_stuff/project_item.rb', line 33

def state
  @state
end

#statusString?

Returns:

  • (String, nil)


35
36
37
# File 'lib/plan_my_stuff/project_item.rb', line 35

def status
  @status
end

#titleString?

Returns:

  • (String, nil)


27
28
29
# File 'lib/plan_my_stuff/project_item.rb', line 27

def title
  @title
end

#typeString? (readonly)

Returns GitHub item type (e.g. “DRAFT_ISSUE”, “ISSUE”, “PULL_REQUEST”).

Returns:

  • (String, nil)

    GitHub item type (e.g. “DRAFT_ISSUE”, “ISSUE”, “PULL_REQUEST”)



24
25
26
# File 'lib/plan_my_stuff/project_item.rb', line 24

def type
  @type
end

#urlString?

Returns:

  • (String, nil)


31
32
33
# File 'lib/plan_my_stuff/project_item.rb', line 31

def url
  @url
end

Class Method Details

.assign(number:, content_node_id:, assignees:, draft: false, repo: nil) ⇒ void

This method returns an undefined value.

Assigns users to a project item. Issues/PRs use REST via Issue.update!, drafts use GraphQL.

Parameters:

  • number (Integer, nil)

    issue number (nil for drafts)

  • content_node_id (String)

    node ID of the underlying content

  • assignees (Array<String>)

    GitHub usernames

  • draft (Boolean) (defaults to: false)

    whether the item is a draft issue

  • repo (Symbol, String, nil) (defaults to: nil)

    repo key (for issues)



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/plan_my_stuff/project_item.rb', line 122

def assign(number:, content_node_id:, assignees:, draft: false, repo: nil)
  if draft
    client = PlanMyStuff.client
    user_ids = assignees.map do |assignee|
      user_data = client.graphql(user_node_id_query, variables: { login: assignee })
      user_id = user_data.dig(:user, :id)

      raise(APIError, "GitHub user not found: #{assignee}") if user_id.nil?

      user_id
    end

    client.graphql(
      assign_draft_mutation,
      variables: { draftIssueId: content_node_id, assigneeIds: user_ids },
    )
  else
    Issue.update!(number: number, repo: repo, assignees: assignees)
  end
end

.build(item_hash, project:) ⇒ PlanMyStuff::ProjectItem

Builds a persisted ProjectItem from parsed item data.

Parameters:

  • item_hash (Hash)

    parsed item data (from Project.parse_project_item)

  • project (PlanMyStuff::Project)

    parent project

Returns:



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/plan_my_stuff/project_item.rb', line 51

def build(item_hash, project:)
  item = new(
    id: item_hash[:id],
    type: item_hash[:type],
    content_node_id: item_hash[:content_node_id],
    title: item_hash[:title],
    number: item_hash[:number],
    url: item_hash[:url],
    state: item_hash[:state],
    status: item_hash[:status],
    field_values: item_hash[:field_values] || {},
    project: project,
  )
  item.__send__(:persisted!)
  item
end

.create!(issue_or_title, draft: false, body: nil, project_number: nil) ⇒ PlanMyStuff::ProjectItem

Creates a project item by adding an existing issue or creating a draft.

Parameters:

  • issue_or_title (PlanMyStuff::Issue, String)

    Issue instance (non-draft) or title string (draft)

  • draft (Boolean) (defaults to: false)

    when true, creates a draft item from a title string

  • body (String, nil) (defaults to: nil)

    body for draft items (ignored for non-draft)

  • project_number (Integer, nil) (defaults to: nil)

    defaults to config.default_project_number

Returns:



77
78
79
80
81
82
83
# File 'lib/plan_my_stuff/project_item.rb', line 77

def create!(issue_or_title, draft: false, body: nil, project_number: nil)
  if draft
    add_draft_item(title: issue_or_title, body: body, project_number: project_number)
  else
    add_item(issue: issue_or_title, project_number: project_number)
  end
end

.move_item(item_id:, status:, project_number: nil) ⇒ Hash

Moves a project item to a new status column.

Parameters:

  • item_id (String)

    project item ID (e.g. “PVTI_…”)

  • status (String)

    status name, resolved to option ID internally

  • project_number (Integer, nil) (defaults to: nil)

    defaults to config.default_project_number

Returns:

  • (Hash)

    the updated item



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/plan_my_stuff/project_item.rb', line 93

def move_item(item_id:, status:, project_number: nil)
  project_number = resolve_default_project_number(project_number)
  project = Project.find(project_number)

  status_field = project.status_field
  option_id = resolve_status_option_id(status_field, status)

  PlanMyStuff.client.graphql(
    update_single_select_mutation,
    variables: {
      projectId: project.id,
      itemId: item_id,
      fieldId: status_field[:id],
      optionId: option_id,
    },
  )
end

Instance Method Details

#assign!(assignees) ⇒ void

This method returns an undefined value.

Assigns users to this item on its parent project.

Parameters:

  • assignees (String, Array<String>)

    GitHub username(s)



390
391
392
393
394
395
396
397
# File 'lib/plan_my_stuff/project_item.rb', line 390

def assign!(assignees)
  self.class.assign(
    number: number,
    content_node_id: content_node_id,
    assignees: Array.wrap(assignees),
    draft: draft?,
  )
end

#draft?Boolean

Returns:

  • (Boolean)


400
401
402
# File 'lib/plan_my_stuff/project_item.rb', line 400

def draft?
  type == 'DRAFT_ISSUE'
end

#move_to!(status) ⇒ Hash

Moves this item to a new status column on its parent project.

Parameters:

  • status (String)

    status name (e.g. “In Progress”, “Done”)

Returns:

  • (Hash)

    mutation result



376
377
378
379
380
381
382
# File 'lib/plan_my_stuff/project_item.rb', line 376

def move_to!(status)
  self.class.move_item(
    project_number: project.number,
    item_id: id,
    status: status,
  )
end