Skip to content

Instantly share code, notes, and snippets.

@elfassy
Last active June 3, 2024 13:39
Show Gist options
  • Save elfassy/6321b124911e5f1859eee7b0aab73d9b to your computer and use it in GitHub Desktop.
Save elfassy/6321b124911e5f1859eee7b0aab73d9b to your computer and use it in GitHub Desktop.
Github Project Issues
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'csv'
gem 'graphql-client'
gem 'progressbar'
gem 'pry'
gem 'pry-byebug'
end
require 'graphql/client/http'
module App
extend self
PROJECT = ENV['PROJECT'] || 6659 #id of the project board
GITHUB_ACCESS_TOKEN = ENV['GH_TOKEN']
LABEL = ENV['LABEL']
PER_PAGE = 99
HTTP = GraphQL::Client::HTTP.new("https://api.github.com/graphql") do
def headers(_context)
{
"Authorization" => "Bearer #{GITHUB_ACCESS_TOKEN}",
"Accept" => nil,
"Content-Type" => "application/json"
}
end
end
# Fetch latest schema on init, this will make a network request
Client = GraphQL::Client.new(schema: GraphQL::Client.load_schema(HTTP), execute: HTTP)
# GraphQL query
QUERY = Client.parse <<-GRAPHQL
query($cursor: String, $perPage: Int, $project: Int!) {
organization(login: "shopify") {
projectV2(number: $project) {
items(first: $perPage, after: $cursor) {
totalCount
pageInfo {
endCursor
startCursor
hasNextPage
}
nodes {
content {
... on Issue {
title
url
state
createdAt
updatedAt
labels(first: 20) {
nodes {
name
}
}
}
}
fieldValues(first: 40) {
nodes {
... on ProjectV2ItemFieldSingleSelectValue {
field {
... on ProjectV2Field {
name
}
... on ProjectV2SingleSelectField {
name
}
... on ProjectV2IterationField {
name
}
}
name
}
... on ProjectV2ItemFieldDateValue {
field {
... on ProjectV2Field {
name
}
... on ProjectV2SingleSelectField {
name
}
... on ProjectV2IterationField {
name
}
}
date
}
... on ProjectV2ItemFieldIterationValue {
field {
... on ProjectV2Field {
name
}
... on ProjectV2SingleSelectField {
name
}
... on ProjectV2IterationField {
name
}
}
title
}
... on ProjectV2ItemFieldMilestoneValue {
field {
... on ProjectV2Field {
name
}
... on ProjectV2SingleSelectField {
name
}
... on ProjectV2IterationField {
name
}
}
milestone {
title
}
}
... on ProjectV2ItemFieldNumberValue {
field {
... on ProjectV2Field {
name
}
... on ProjectV2SingleSelectField {
name
}
... on ProjectV2IterationField {
name
}
}
number
}
... on ProjectV2ItemFieldTextValue {
field {
... on ProjectV2Field {
name
id
}
... on ProjectV2SingleSelectField {
name
}
... on ProjectV2IterationField {
name
}
}
text
}
}
}
}
}
}
}
}
GRAPHQL
def run
generate_csv(fetch_issues.flatten.compact)
puts 'CSV file has been successfully generated and saved as output.csv'
end
private
def fetch_issues(cursor = nil, page_count = 1)
result = Client.query(QUERY, variables: { cursor: cursor, perPage: PER_PAGE, project: PROJECT })
raise "GraphQL error: #{result.errors[:data].join(' ')}" if result.errors && result.errors[:data]&.any?
data = result.to_h.dig('data', 'organization', 'projectV2', 'items')
has_next_page = data&.dig('pageInfo', 'hasNextPage')
cursor = data&.dig('pageInfo', 'endCursor')
issues = data&.dig('nodes')
raise "No issues found: Make sure you have `read:projects, read:org, read:user, repo` permissions and configured SSO for your API key" unless issues
@progress_bar ||= ProgressBar.create(total: data.dig('totalCount'), format: '%t: |%B| %c / %C (%p%%)')
@progress_bar.progress += issues.size
filtered_issues = filter_issues(issues).compact
filtered_issues += fetch_issues(cursor, page_count + 1) if has_next_page
filtered_issues
end
def filter_issues(data)
data.map do |issue|
state = issue.dig('content','state')
next unless state != "CLOSED"
labels = issue.dig('content','labels','nodes')&.map { |l| l['name'] }
if LABEL
next unless labels&.include?(LABEL)
end
issue_data = issue["content"].each_with_object({}) do |(key, value), hash|
case key
when "labels"
hash["issue_#{key}"] = labels.join(',')
when "__typename"
hash[key] = value
else
hash["issue_#{key}"] = value
end
end
issue['fieldValues']['nodes'].each_with_object(issue_data) do |field_value, fields_hash|
field_name = field_value.dig('field','name')
next unless field_name
field_type = field_value.keys.last
case field_type
when 'milestone'
fields_hash['milestone'] = field_value['milestone']['title']
when 'name'
fields_hash[field_name] = field_value['name']
when 'date'
fields_hash[field_name] = field_value['date']
when 'title'
fields_hash['title'] = field_value['title']
when 'number'
fields_hash[field_name] = field_value['number']
when 'text'
fields_hash[field_name] = field_value['text']
else
raise "Unknown field type: #{field_type}"
end
end
end
end
def generate_csv(filtered_issues)
CSV.open('output.csv', 'w') do |csv|
header_keys = filtered_issues.map(&:keys).flatten.uniq
csv << header_keys
filtered_issues.each do |issue|
csv << header_keys.map { |key| issue[key] }
end
end
end
end
App.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment