Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/git/markdown/api/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ def get(path, params = {})
Response.new(response)
end

def post(path, body = {})
uri = build_uri(path, {})
request = Net::HTTP::Post.new(uri)
set_headers(request)
request["Content-Type"] = "application/json"
request.body = JSON.generate(body)

response = http_request(uri, request)
Response.new(response)
end

private

def build_uri(path, params)
Expand Down
12 changes: 11 additions & 1 deletion lib/git/markdown/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ class Configuration
XDG_CONFIG_HOME = ENV.fetch("XDG_CONFIG_HOME", File.expand_path("~/.config"))
DEFAULT_PROVIDER = :github

attr_accessor :token, :provider, :api_url, :output_dir, :default_status
attr_accessor :token, :provider, :api_url, :graphql_url, :output_dir, :default_status

def initialize
@provider = DEFAULT_PROVIDER
@output_dir = Dir.pwd
@default_status = :unresolved
@api_url = nil
@graphql_url = nil
end

def self.load
Expand All @@ -24,6 +25,7 @@ def load!
load_from_file if config_file_exist?
resolve_credentials
resolve_api_url if api_url.nil?
resolve_graphql_url if graphql_url.nil?
self
end

Expand Down Expand Up @@ -67,6 +69,7 @@ def load_from_file
)
@provider = config[:provider] if config[:provider]
@api_url = config[:api_url] if config[:api_url]
@graphql_url = config[:graphql_url] if config[:graphql_url]
@output_dir = config[:output_dir] if config[:output_dir]
@default_status = config[:default_status].to_sym if config[:default_status]
end
Expand All @@ -81,10 +84,17 @@ def resolve_api_url
end
end

def resolve_graphql_url
@graphql_url = ENV.fetch("GITHUB_GRAPHQL_URL") do
(@provider == :github) ? "https://api.github.com/graphql" : nil
end
end

def config_to_yaml
{
provider: @provider,
api_url: @api_url,
graphql_url: @graphql_url,
output_dir: @output_dir,
default_status: @default_status
}.to_yaml
Expand Down
4 changes: 2 additions & 2 deletions lib/git/markdown/markdown/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ def filtered_general_comments
def include_comment?(comment)
case @status_filter
when :unresolved
!comment.body.include?("[resolved]") && !comment.body.include?("[done]")
!comment.resolved? && !comment.body.include?("[resolved]") && !comment.body.include?("[done]")
when :resolved
comment.body.include?("[resolved]") || comment.body.include?("[done]")
comment.resolved? || comment.body.include?("[resolved]") || comment.body.include?("[done]")
else
true
end
Expand Down
6 changes: 6 additions & 0 deletions lib/git/markdown/models/comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module GitMarkdown
module Models
class Comment
attr_reader :id, :body, :author, :path, :line, :html_url, :created_at, :updated_at, :in_reply_to_id
attr_accessor :resolved

def initialize(attrs = {})
@id = attrs[:id]
Expand All @@ -15,6 +16,7 @@ def initialize(attrs = {})
@created_at = attrs[:created_at]
@updated_at = attrs[:updated_at]
@in_reply_to_id = attrs[:in_reply_to_id]
@resolved = attrs.fetch(:resolved, false)
end

def self.from_api(data)
Expand All @@ -38,6 +40,10 @@ def inline?
def reply?
!@in_reply_to_id.nil?
end

def resolved?
@resolved == true
end
end
end
end
41 changes: 35 additions & 6 deletions lib/git/markdown/providers/github.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,36 @@ def fetch_comments(owner, repo, number)
end

def fetch_reviews(owner, repo, number)
resolved_ids = fetch_resolved_comment_ids(owner, repo, number)

path = "/repos/#{owner}/#{repo}/pulls/#{number}/reviews"
reviews = fetch_all_pages(path)

all_pr_comments = fetch_all_pr_comments(owner, repo, number)

reviews.map do |review_data|
review = Models::Review.from_api(review_data)
review.comments = fetch_review_comments(owner, repo, review.id)
review.comments = all_pr_comments
.select { |c| c["pull_request_review_id"] == review.id }
.map do |data|
comment = Models::Comment.from_api(data)
comment.resolved = resolved_ids.include?(comment.id)
comment
end
review
end
end

def fetch_review_comments(owner, repo, review_id)
path = "/repos/#{owner}/#{repo}/pulls/comments"
all_comments = fetch_all_pages(path)
def fetch_review_comments(owner, repo, review_id, resolved_ids = Set.new)
all_pr_comments = fetch_all_pr_comments(owner, repo)

all_comments
all_pr_comments
.select { |c| c["pull_request_review_id"] == review_id }
.map { |data| Models::Comment.from_api(data) }
.map do |data|
comment = Models::Comment.from_api(data)
comment.resolved = resolved_ids.include?(comment.id)
comment
end
end

private
Expand All @@ -49,6 +62,22 @@ def client
)
end

def graphql_client
@graphql_client ||= Graphql.new(@config)
end

def fetch_resolved_comment_ids(owner, repo, number)
graphql_client.fetch_resolved_states(owner, repo, number)
rescue ApiError
Set.new
end

def fetch_all_pr_comments(owner, repo, _number = nil)
@all_pr_comments ||= {}
key = "#{owner}/#{repo}"
@all_pr_comments[key] ||= fetch_all_pages("/repos/#{owner}/#{repo}/pulls/comments")
end

def fetch_all_pages(path, params = {})
results = []
page = 1
Expand Down
95 changes: 95 additions & 0 deletions lib/git/markdown/providers/github/graphql.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# frozen_string_literal: true

module GitMarkdown
module Providers
class GitHub
class Graphql
RESOLVED_STATE_QUERY = <<~GRAPHQL
query($owner: String!, $repo: String!, $number: Int!, $threadsAfter: String, $commentsAfter: String) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
reviewThreads(first: 100, after: $threadsAfter) {
pageInfo {
hasNextPage
endCursor
}
nodes {
isResolved
comments(first: 100, after: $commentsAfter) {
pageInfo {
hasNextPage
endCursor
}
nodes {
databaseId
body
path
line
createdAt
updatedAt
author {
login
}
replyTo {
databaseId
}
url
}
}
}
}
}
}
}
GRAPHQL

def initialize(config)
@config = config
end

def fetch_resolved_states(owner, repo, number)
resolved_ids = Set.new
threads_after = nil

loop do
response = client.post("", {
query: RESOLVED_STATE_QUERY,
variables: {owner: owner, repo: repo, number: number,
threadsAfter: threads_after}
})

raise ApiError, "GraphQL request failed: #{response.error_message}" unless response.success?

data = response.data
threads_data = data.dig("data", "repository", "pullRequest", "reviewThreads")
break unless threads_data

threads_data["nodes"].each do |thread|
next unless thread["isResolved"]

thread["comments"]["nodes"].each do |comment|
resolved_ids.add(comment["databaseId"]) if comment["databaseId"]
end
end

threads_page = threads_data["pageInfo"]
break unless threads_page["hasNextPage"]

threads_after = threads_page["endCursor"]
end

resolved_ids
end

private

def client
@client ||= Api::Client.new(
base_url: @config.graphql_url,
token: @config.token
)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/git_markdown.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require_relative "git/markdown/remote_parser"
require_relative "git/markdown/providers/base"
require_relative "git/markdown/providers/github"
require_relative "git/markdown/providers/github/graphql"
require_relative "git/markdown/api/client"
require_relative "git/markdown/api/response"
require_relative "git/markdown/models/pull_request"
Expand Down
20 changes: 20 additions & 0 deletions test/git_markdown/markdown/generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,26 @@ def test_filters_unresolved_comments_by_default
refute_includes markdown, "[resolved] This is done"
end

def test_filters_github_resolved_comments_by_default
resolved_comment = GitMarkdown::Models::Comment.new(
id: 3,
body: "Fixed this issue",
author: "reviewer3",
created_at: "2024-01-15T13:00:00Z",
resolved: true
)

generator = GitMarkdown::Markdown::Generator.new(
@pr,
@comments + [resolved_comment],
@reviews
)

markdown = generator.generate
assert_includes markdown, "Great work!"
refute_includes markdown, "Fixed this issue"
end

def test_includes_resolved_when_filtered
resolved_comment = GitMarkdown::Models::Comment.new(
id: 3,
Expand Down
Loading