Last active
October 27, 2021 20:09
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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