Skip to content

Commit c019db3

Browse files
authored
Merge pull request #6 from ethos-link/ignore-resolved
feat: filter GitHub-native resolved comments via GraphQL
2 parents 54a633c + bfa202e commit c019db3

8 files changed

Lines changed: 181 additions & 9 deletions

File tree

lib/git/markdown/api/client.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ def get(path, params = {})
1717
Response.new(response)
1818
end
1919

20+
def post(path, body = {})
21+
uri = build_uri(path, {})
22+
request = Net::HTTP::Post.new(uri)
23+
set_headers(request)
24+
request["Content-Type"] = "application/json"
25+
request.body = JSON.generate(body)
26+
27+
response = http_request(uri, request)
28+
Response.new(response)
29+
end
30+
2031
private
2132

2233
def build_uri(path, params)

lib/git/markdown/configuration.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ class Configuration
77
XDG_CONFIG_HOME = ENV.fetch("XDG_CONFIG_HOME", File.expand_path("~/.config"))
88
DEFAULT_PROVIDER = :github
99

10-
attr_accessor :token, :provider, :api_url, :output_dir, :default_status
10+
attr_accessor :token, :provider, :api_url, :graphql_url, :output_dir, :default_status
1111

1212
def initialize
1313
@provider = DEFAULT_PROVIDER
1414
@output_dir = Dir.pwd
1515
@default_status = :unresolved
1616
@api_url = nil
17+
@graphql_url = nil
1718
end
1819

1920
def self.load
@@ -24,6 +25,7 @@ def load!
2425
load_from_file if config_file_exist?
2526
resolve_credentials
2627
resolve_api_url if api_url.nil?
28+
resolve_graphql_url if graphql_url.nil?
2729
self
2830
end
2931

@@ -67,6 +69,7 @@ def load_from_file
6769
)
6870
@provider = config[:provider] if config[:provider]
6971
@api_url = config[:api_url] if config[:api_url]
72+
@graphql_url = config[:graphql_url] if config[:graphql_url]
7073
@output_dir = config[:output_dir] if config[:output_dir]
7174
@default_status = config[:default_status].to_sym if config[:default_status]
7275
end
@@ -81,10 +84,17 @@ def resolve_api_url
8184
end
8285
end
8386

87+
def resolve_graphql_url
88+
@graphql_url = ENV.fetch("GITHUB_GRAPHQL_URL") do
89+
(@provider == :github) ? "https://api.github.com/graphql" : nil
90+
end
91+
end
92+
8493
def config_to_yaml
8594
{
8695
provider: @provider,
8796
api_url: @api_url,
97+
graphql_url: @graphql_url,
8898
output_dir: @output_dir,
8999
default_status: @default_status
90100
}.to_yaml

lib/git/markdown/markdown/generator.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ def filtered_general_comments
5050
def include_comment?(comment)
5151
case @status_filter
5252
when :unresolved
53-
!comment.body.include?("[resolved]") && !comment.body.include?("[done]")
53+
!comment.resolved? && !comment.body.include?("[resolved]") && !comment.body.include?("[done]")
5454
when :resolved
55-
comment.body.include?("[resolved]") || comment.body.include?("[done]")
55+
comment.resolved? || comment.body.include?("[resolved]") || comment.body.include?("[done]")
5656
else
5757
true
5858
end

lib/git/markdown/models/comment.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module GitMarkdown
44
module Models
55
class Comment
66
attr_reader :id, :body, :author, :path, :line, :html_url, :created_at, :updated_at, :in_reply_to_id
7+
attr_accessor :resolved
78

89
def initialize(attrs = {})
910
@id = attrs[:id]
@@ -15,6 +16,7 @@ def initialize(attrs = {})
1516
@created_at = attrs[:created_at]
1617
@updated_at = attrs[:updated_at]
1718
@in_reply_to_id = attrs[:in_reply_to_id]
19+
@resolved = attrs.fetch(:resolved, false)
1820
end
1921

2022
def self.from_api(data)
@@ -38,6 +40,10 @@ def inline?
3840
def reply?
3941
!@in_reply_to_id.nil?
4042
end
43+
44+
def resolved?
45+
@resolved == true
46+
end
4147
end
4248
end
4349
end

lib/git/markdown/providers/github.rb

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,36 @@ def fetch_comments(owner, repo, number)
2121
end
2222

2323
def fetch_reviews(owner, repo, number)
24+
resolved_ids = fetch_resolved_comment_ids(owner, repo, number)
25+
2426
path = "/repos/#{owner}/#{repo}/pulls/#{number}/reviews"
2527
reviews = fetch_all_pages(path)
2628

29+
all_pr_comments = fetch_all_pr_comments(owner, repo, number)
30+
2731
reviews.map do |review_data|
2832
review = Models::Review.from_api(review_data)
29-
review.comments = fetch_review_comments(owner, repo, review.id)
33+
review.comments = all_pr_comments
34+
.select { |c| c["pull_request_review_id"] == review.id }
35+
.map do |data|
36+
comment = Models::Comment.from_api(data)
37+
comment.resolved = resolved_ids.include?(comment.id)
38+
comment
39+
end
3040
review
3141
end
3242
end
3343

34-
def fetch_review_comments(owner, repo, review_id)
35-
path = "/repos/#{owner}/#{repo}/pulls/comments"
36-
all_comments = fetch_all_pages(path)
44+
def fetch_review_comments(owner, repo, review_id, resolved_ids = Set.new)
45+
all_pr_comments = fetch_all_pr_comments(owner, repo)
3746

38-
all_comments
47+
all_pr_comments
3948
.select { |c| c["pull_request_review_id"] == review_id }
40-
.map { |data| Models::Comment.from_api(data) }
49+
.map do |data|
50+
comment = Models::Comment.from_api(data)
51+
comment.resolved = resolved_ids.include?(comment.id)
52+
comment
53+
end
4154
end
4255

4356
private
@@ -49,6 +62,22 @@ def client
4962
)
5063
end
5164

65+
def graphql_client
66+
@graphql_client ||= Graphql.new(@config)
67+
end
68+
69+
def fetch_resolved_comment_ids(owner, repo, number)
70+
graphql_client.fetch_resolved_states(owner, repo, number)
71+
rescue ApiError
72+
Set.new
73+
end
74+
75+
def fetch_all_pr_comments(owner, repo, _number = nil)
76+
@all_pr_comments ||= {}
77+
key = "#{owner}/#{repo}"
78+
@all_pr_comments[key] ||= fetch_all_pages("/repos/#{owner}/#{repo}/pulls/comments")
79+
end
80+
5281
def fetch_all_pages(path, params = {})
5382
results = []
5483
page = 1
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# frozen_string_literal: true
2+
3+
module GitMarkdown
4+
module Providers
5+
class GitHub
6+
class Graphql
7+
RESOLVED_STATE_QUERY = <<~GRAPHQL
8+
query($owner: String!, $repo: String!, $number: Int!, $threadsAfter: String, $commentsAfter: String) {
9+
repository(owner: $owner, name: $repo) {
10+
pullRequest(number: $number) {
11+
reviewThreads(first: 100, after: $threadsAfter) {
12+
pageInfo {
13+
hasNextPage
14+
endCursor
15+
}
16+
nodes {
17+
isResolved
18+
comments(first: 100, after: $commentsAfter) {
19+
pageInfo {
20+
hasNextPage
21+
endCursor
22+
}
23+
nodes {
24+
databaseId
25+
body
26+
path
27+
line
28+
createdAt
29+
updatedAt
30+
author {
31+
login
32+
}
33+
replyTo {
34+
databaseId
35+
}
36+
url
37+
}
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}
44+
GRAPHQL
45+
46+
def initialize(config)
47+
@config = config
48+
end
49+
50+
def fetch_resolved_states(owner, repo, number)
51+
resolved_ids = Set.new
52+
threads_after = nil
53+
54+
loop do
55+
response = client.post("", {
56+
query: RESOLVED_STATE_QUERY,
57+
variables: {owner: owner, repo: repo, number: number,
58+
threadsAfter: threads_after}
59+
})
60+
61+
raise ApiError, "GraphQL request failed: #{response.error_message}" unless response.success?
62+
63+
data = response.data
64+
threads_data = data.dig("data", "repository", "pullRequest", "reviewThreads")
65+
break unless threads_data
66+
67+
threads_data["nodes"].each do |thread|
68+
next unless thread["isResolved"]
69+
70+
thread["comments"]["nodes"].each do |comment|
71+
resolved_ids.add(comment["databaseId"]) if comment["databaseId"]
72+
end
73+
end
74+
75+
threads_page = threads_data["pageInfo"]
76+
break unless threads_page["hasNextPage"]
77+
78+
threads_after = threads_page["endCursor"]
79+
end
80+
81+
resolved_ids
82+
end
83+
84+
private
85+
86+
def client
87+
@client ||= Api::Client.new(
88+
base_url: @config.graphql_url,
89+
token: @config.token
90+
)
91+
end
92+
end
93+
end
94+
end
95+
end

lib/git_markdown.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
require_relative "git/markdown/remote_parser"
1616
require_relative "git/markdown/providers/base"
1717
require_relative "git/markdown/providers/github"
18+
require_relative "git/markdown/providers/github/graphql"
1819
require_relative "git/markdown/api/client"
1920
require_relative "git/markdown/api/response"
2021
require_relative "git/markdown/models/pull_request"

test/git_markdown/markdown/generator_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ def test_filters_unresolved_comments_by_default
8080
refute_includes markdown, "[resolved] This is done"
8181
end
8282

83+
def test_filters_github_resolved_comments_by_default
84+
resolved_comment = GitMarkdown::Models::Comment.new(
85+
id: 3,
86+
body: "Fixed this issue",
87+
author: "reviewer3",
88+
created_at: "2024-01-15T13:00:00Z",
89+
resolved: true
90+
)
91+
92+
generator = GitMarkdown::Markdown::Generator.new(
93+
@pr,
94+
@comments + [resolved_comment],
95+
@reviews
96+
)
97+
98+
markdown = generator.generate
99+
assert_includes markdown, "Great work!"
100+
refute_includes markdown, "Fixed this issue"
101+
end
102+
83103
def test_includes_resolved_when_filtered
84104
resolved_comment = GitMarkdown::Models::Comment.new(
85105
id: 3,

0 commit comments

Comments
 (0)