Skip to content

Commit

Permalink
Merge pull request community#69192 from community/mgriffin/stalebot
Browse files Browse the repository at this point in the history
Add scripts and related libraries for Discussions Stalebot
  • Loading branch information
ettaboyle committed Oct 5, 2023
2 parents baa5cd9 + 140b69b commit 71c829d
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/actions/comment
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "./lib/github"

stale_label_id = "LA_kwDOEfmk4M8AAAABYVCU-g"
owner = "community"
repo = "community"
only_these_categories = ["Copilot", "Projects and Issues", "Accessibility"]

categories = Category.all(owner:, repo:).select { |c| only_these_categories.include?(c.name) }

categories.map do |category|
category.discussions = Discussion.all(owner:, repo:, category:)
end

categories.each do |c|
puts "#{c.name} has #{c.discussions.count} eligible discussion(s)"
end

# for initial testing, don't modify any discussions
#categories.each do |category|
# category.discussions.each do |discussion|
# discussion.add_comment(body: "This is automated")
# discussion.add_label(label_id: stale_label_id)
# end
#end
35 changes: 35 additions & 0 deletions .github/lib/categories.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

Category = Struct.new(
:id,
:name,
:answerable,
:discussions
) do
def self.all(owner: nil, repo: nil)
return [] if owner.nil? || repo.nil?

query = <<~QUERY
{
repository(owner: "#{owner}", name: "#{repo}"){
discussionCategories(first: 100) {
nodes {
id
name
isAnswerable
}
}
}
}
QUERY

GitHub.new.post(graphql: query).first.dig("discussionCategories", "nodes")
.map do |c|
Category.new(
c["id"],
c["name"],
c["isAnswerable"]
)
end
end
end
104 changes: 104 additions & 0 deletions .github/lib/discussions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# frozen_string_literal: true

require "active_support/core_ext/date_and_time/calculations"
require "active_support/core_ext/numeric/time"

Discussion = Struct.new(
:id,
) do
def self.all(owner: nil, repo: nil, category: nil)
return [] if owner.nil? || repo.nil? || category.nil?

query = <<~QUERY
{
repository(owner: "#{owner}", name: "#{repo}"){
discussions(
first: 100,
after: "%ENDCURSOR%"
#{"answered: false," if category.answerable}
categoryId: "#{category.id}"
orderBy: { field: CREATED_AT, direction: ASC }
) {
nodes {
id
closed
locked
updatedAt
comments(last: 1) {
totalCount
nodes {
createdAt
}
}
labels(first: 100) {
nodes {
name
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
QUERY

cutoff_date = Time.now.advance(days: -60)
GitHub.new.post(graphql: query).map! { |r| r.dig('discussions', 'nodes') }
.flatten
.reject { |r| Date.parse(r["updatedAt"]).after?(cutoff_date) }
.reject { |r| r["closed"] }
.reject { |r| r["locked"] }
.reject { |r| r.dig("comments", "totalCount") > 0 && Date.parse(r.dig("comments", "nodes", 0, "createdAt")).after?(cutoff_date) }
.reject { |r| r.dig("labels", "nodes").map { |l| l["name"] }.include?("stale") }
.map do |c|
Discussion.new(
c["id"]
)
end
end

def add_comment(body: nil)
query = <<~QUERY
mutation {
addDiscussionComment(
input: {
body: "#{body}",
discussionId: "#{self.id}",
clientMutationId: "rubyGraphQL"
}
) {
clientMutationId
comment {
id
body
}
}
}
QUERY

GitHub.new.mutate(graphql: query)
end

def add_label(label_id: nil)
return if label_id.nil?

query = <<~QUERY
mutation {
addLabelsToLabelable(
input: {
labelIds: ["#{label_id}"],
labelableId: "#{self.id}",
clientMutationId: "rubyGraphQL"
}
) {
clientMutationId
}
}
QUERY

GitHub.new.mutate(graphql: query)
end
end
49 changes: 49 additions & 0 deletions .github/lib/github.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require "faraday"
require "json"

require "./lib/categories"
require "./lib/discussions"

# A class to make it easier to send requests to the GitHub GraphQL endpoint
class GitHub
def initialize
@conn = Faraday.new(
url: "https://api.github.com",
headers: {
Authorization: "bearer #{ENV['GITHUB_TOKEN']}"
}
)
end

def post(graphql:)
end_cursor = nil
nodes = []

loop do
query = end_cursor.nil? ? graphql.sub(/after.*\n/, "") : graphql.sub("%ENDCURSOR%", end_cursor)

response = @conn.post("/graphql") do |req|
req.body = { query: }.to_json
end

node = JSON.parse(response.body).dig("data", "repository")
nodes << node

break unless node.dig("discussions", "pageInfo", "hasNextPage")

end_cursor = node.dig("discussions", "pageInfo", "endCursor")
end

nodes.flatten
end

def mutate(graphql:)
response = @conn.post("/graphql") do |req|
req.body = { query: graphql }.to_json
end

JSON.parse(response.body)
end
end
24 changes: 24 additions & 0 deletions .github/workflows/comment-on-stale-discussions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Comment on stale discussions

on: workflow_dispatch

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
discussions: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Set up Ruby
uses: ruby/setup-ruby@ec02537da5712d66d4d50a0f33b7eb52773b5ed1

- name: Bundle install
run: bundle install

- name: Comment on discussions
run: .github/actions/comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6 changes: 6 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
AllCops:
NewCops: enable
inherit_gem:
rubocop-github:
- config/default.yml
- config/rails.yml
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.1.2
9 changes: 9 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

source "https://rubygems.org"

gem "faraday"

group :test, :development do
gem "rubocop-github"
end
72 changes: 72 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
GEM
remote: https://rubygems.org/
specs:
activesupport (7.0.8)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
ast (2.4.2)
base64 (0.1.1)
concurrent-ruby (1.2.2)
dotenv (2.8.1)
faraday (2.7.11)
base64
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
json (2.6.3)
language_server-protocol (3.17.0.3)
minitest (5.20.0)
parallel (1.23.0)
parser (3.2.2.3)
ast (~> 2.4.1)
racc
racc (1.7.1)
rack (3.0.8)
rainbow (3.1.1)
regexp_parser (2.8.1)
rexml (3.2.6)
rubocop (1.56.3)
base64 (~> 0.1.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.2.2.3)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
rubocop-github (0.20.0)
rubocop (>= 1.37)
rubocop-performance (>= 1.15)
rubocop-rails (>= 2.17)
rubocop-performance (1.19.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.21.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2)

PLATFORMS
arm64-darwin-21

DEPENDENCIES
dotenv
faraday
rubocop-github

BUNDLED WITH
2.4.19

0 comments on commit 71c829d

Please sign in to comment.