Skip to content

Instantly share code, notes, and snippets.

@bruce
Last active October 27, 2021 20:09
Show Gist options
  • Save bruce/dd12e261bd1a39dde7669ced9376e73e to your computer and use it in GitHub Desktop.
Save bruce/dd12e261bd1a39dde7669ced9376e73e to your computer and use it in GitHub Desktop.
A rough script to update a memex project with all issues from a repository (requires faraday)
abort 'Usage: ruby fill.rb <PROJECT_NUMBER> <NWO> <PAT>' unless ARGV.length == 3
require 'bundler/setup'
require 'fileutils'
require 'json'
require 'faraday'
class ProjectSync
def self.run(*args)
new(*args).run
end
def initialize(project_number, nwo, pat)
@project_number = Integer(project_number)
@nwo = nwo
(@repo_owner, @repo_name) = @nwo.split('/')
@pat = pat
end
def issue_ids
@issue_ids ||= retrieve_issue_ids
end
def run
project_id = retrieve_project_id
issue_ids = []
after = nil
loop do
doc = retrieve_issues(after)
if errors = doc.dig('errors')
abort "Request responded with GraphQL errors: #{errors.inspect}"
end
issues = collapse_nodes(doc.dig('data', 'repository', 'issues', 'nodes'))
issue_ids.push(*issues.map { |i| i['id'] })
page_info = doc.dig('data', 'repository', 'issues', 'pageInfo')
if page_info.fetch('hasNextPage')
after = page_info.fetch('endCursor')
else
break
end
end
issue_ids.each_slice(20).each do |chunk|
add_issues(project_id, chunk)
$stderr << '.'
sleep 1
end
puts
end
private
def add_issues(project_id, issue_ids)
doc = "mutation {\n"
issue_ids.each.with_index do |issue_id, idx|
doc << " item#{idx}: addProjectNextItem(input: {projectId: \"#{project_id}\" contentId: \"#{issue_id}\"}) {\n"
doc << " projectNextItem {\n"
doc << " id\n"
doc << " }\n"
doc << " }\n"
end
doc << "}\n"
document = { query: doc, variables: {} }.to_json
resp = Faraday.post('https://api.github.com/graphql', &prepare(document))
if errors = JSON.parse(resp.body).dig('errors')
$stderr.puts errors.inspect
end
end
def retrieve_project_id
query = <<~EOQ.freeze
query {
organization(login: "github") {
projectNext(number: #{@project_number}) {
id
title
}
}
}
EOQ
document = { query: query, variables: {} }.to_json
resp = Faraday.post('https://api.github.com/graphql', &prepare(document))
data = JSON.parse(resp.body).dig('data', 'organization', 'projectNext')
$stderr << %Q<Fill project "#{data['title']}" [y/N]? >
unless $stdin.gets.chomp =~ /\Ay/i
abort 'Ok, bye!'
end
data['id']
end
def retrieve_issues(after = nil)
query = <<~EOQ.freeze
query ($after: String, $owner: String!, $name: String!) {
repository(name: $name, owner: $owner) {
issues(first: 100, after: $after, states: [OPEN]) {
pageInfo{
hasNextPage
endCursor
}
nodes {
id
}
}
}
}
EOQ
document = { query: query, variables: { after: after, owner: @repo_owner, name: @repo_name } }.to_json
resp = Faraday.post('https://api.github.com/graphql', &prepare(document))
JSON.parse(resp.body)
end
def prepare(document)
lambda { |req|
req.headers['Authorization'] = "bearer #{@pat}"
req.headers['Content-Type'] = 'application/json'
req.headers['GraphQL-Features'] = 'projects_next_graphql'
req.body = document
}
end
def collapse_nodes(node)
case node
when Hash
if node.key?('nodes')
node['nodes'].map { |n| collapse_nodes(n) }
else
node.each { |k, v| node[k] = collapse_nodes(v) }
end
when Array
node.map { |n| collapse_nodes(n) }
else
node
end
end
end
ProjectSync.run(*ARGV[0..2])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment