Module: ActiveProject::Adapters::GithubProject::Issues

Includes:
Helpers
Included in:
ActiveProject::Adapters::GithubProjectAdapter
Defined in:
lib/active_project/adapters/github_project/issues.rb

Overview

Project-item CRUD operations for GitHub Projects v2.

This module exists because GitHub decided that “issues,” “drafts,” and “project items” are three different species, and we have to *unify the galaxy.*

Constant Summary collapse

DEFAULT_ITEM_PAGE_SIZE =

Like everything on GitHub: decent default, weird edge cases. Why not 25 like a normal person?

50

Instance Method Summary collapse

Methods included from Helpers

#fetch_all_pages, #map_user, #owner_node_id, #project_field_ids

Instance Method Details

#create_issue(project_id, attrs) ⇒ Object

Create a new issue in the project.

Choose your destiny:

- Pass :content_id → links an existing GitHub Issue or PR into the project.


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/active_project/adapters/github_project/issues.rb', line 98

def create_issue(project_id, attrs)
  content_id = attrs[:content_id] or
    raise ArgumentError, "DraftIssues not supported—pass :content_id of a real Issue or PR"

  mutation = <<~GQL
    mutation($project:ID!, $content:ID!) {
      addProjectV2ItemById(input:{projectId:$project, contentId:$content}) {
        item { id }
      }
    }
  GQL

  data = request_gql(
    query: mutation,
    variables: { project: project_id, content: content_id }
  ).dig("addProjectV2ItemById", "item")

  find_issue(data["id"])
end

#delete_issue_original(project_id, item_id) ⇒ Object

Delete a ProjectV2Item from a project. No soft delete, no grace period — just *execute Order 66*.



174
175
176
177
178
179
180
181
182
183
# File 'lib/active_project/adapters/github_project/issues.rb', line 174

def delete_issue_original(project_id, item_id)
  mutation = <<~GQL
    mutation($proj:ID!, $item:ID!){
      deleteProjectV2Item(input:{projectId:$proj, itemId:$item}){deletedItemId}
    }
  GQL
  request_gql(query: mutation,
              variables: { proj: project_id, item: item_id })
  true
end

#find_issue(item_id, _ctx = {}) ⇒ Object

Fetch a single issue or draft item by its mysterious GraphQL node ID. If it’s missing, you get a 404 so you can take a day off.

Raises:



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
90
# File 'lib/active_project/adapters/github_project/issues.rb', line 56

def find_issue(item_id, _ctx = {})
  query = <<~GQL
    query($id:ID!){
      node(id:$id){
        ... on ProjectV2Item{
          id
          type
          fieldValues(first:20){
            nodes{
              ... on ProjectV2ItemFieldTextValue{
                text
                field { ... on ProjectV2FieldCommon { name } }
              }
            }
          }
          content{
            __typename
            ... on Issue{
              id number title body state
              assignees(first:10){nodes{login id}}
              reporter:author{login}
            }
          }
          createdAt
          updatedAt
          project { id }
        }
      }
    }
  GQL
  node = request_gql(query: query, variables: { id: item_id })["node"]
  raise NotFoundError, "Project item #{item_id} not found" unless node

  map_item_to_issue(node, node.dig("project", "id"))
end

#list_issues(project_id, options = {}) ⇒ Object

Lists every issue or draft in a GitHub Project. Handles pagination the GitHub way: manually, painfully, endlessly.

options → control how much data you want per jump into hyperspace.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/active_project/adapters/github_project/issues.rb', line 23

def list_issues(project_id, options = {})
  page_size = options.fetch(:page_size, DEFAULT_ITEM_PAGE_SIZE)
  query = <<~GQL
    query($id:ID!, $first:Int!, $after:String){
      node(id:$id){
        ... on ProjectV2{
          items(first:$first, after:$after){
            nodes{
              id type content{__typename ... on Issue{ id number title body state
                assignees(first:10){nodes{login id}}
                reporter:author{login} } }
              createdAt updatedAt
            }
            pageInfo{hasNextPage endCursor}
          }
        }
      }
    }
  GQL

  nodes = fetch_all_pages(
    query,
    variables: { id: project_id, first: page_size },
    connection_path: %w[node items]
  ) { |vars| request_gql(query: query, variables: vars) }

  nodes.map { |n| map_item_to_issue(n, project_id) }
end

#status_known?(project_id, sym) ⇒ Boolean

Check if a status symbol like :in_progress is known for a project. Avoids exploding like fukushima reactor if you try to set a status that doesn’t exist.

Returns:

  • (Boolean)


189
190
191
# File 'lib/active_project/adapters/github_project/issues.rb', line 189

def status_known?(project_id, sym)
  (@status_cache && @status_cache[project_id] || {}).key?(sym)
end

#update_issue_original(project_id, item_id, attrs = {}) ⇒ Object

Update fields on an existing ProjectV2Item.

You can adjust:

- Title (text field)
- Status (single-select nightmare field)

NOTE: Requires you to preload field mappings, because GitHub’s GraphQL API refuses to help unless you memorize all their withcrafts.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/active_project/adapters/github_project/issues.rb', line 128

def update_issue_original(project_id, item_id, attrs = {})
  field_ids = project_field_ids(project_id)

  # -- Update Title (basic) --
  if attrs[:title]
    mutation = <<~GQL
      mutation($proj:ID!, $item:ID!, $field:ID!, $title:String!) {
        updateProjectV2ItemFieldValue(input:{
          projectId:$proj, itemId:$item,
          fieldId:$field, value:{text:$title}
        }) { projectV2Item { id } }
      }
    GQL
    request_gql(query: mutation,
                variables: {
                  proj: project_id,
                  item: item_id,
                  field: field_ids.fetch("Title"),
                  title: attrs[:title]
                })
  end

  # -- Update Status (dark side difficulty) --
  if attrs[:status]
    status_field_id = field_ids.fetch("Status")
    option_id = status_option_id(project_id, attrs[:status])
    mutation = <<~GQL
      mutation($proj:ID!, $item:ID!, $field:ID!, $opt:String!) {
        updateProjectV2ItemFieldValue(input:{
          projectId:$proj, itemId:$item,
          fieldId:$field, value:{singleSelectOptionId:$opt}
        }) { projectV2Item { id } }
      }
    GQL
    request_gql(query: mutation,
                variables: { proj: project_id, item: item_id,
                             field: status_field_id, opt: option_id })
  end

  find_issue(item_id)
end