Skip to content

Commit 1c38d5a

Browse files
authored
Ignore reporting "Do Not Merge" PRs in Discord (#293)
1 parent 0ac8d89 commit 1c38d5a

File tree

10 files changed

+616
-28
lines changed

10 files changed

+616
-28
lines changed

.sourcekit-lsp/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"backgroundIndexing": true,
3-
"backgroundPreparationMode": "build",
3+
"backgroundPreparationMode": "enabled",
44
"maxCoresPercentageToUseForBackgroundIndexing": 0.7,
55
"experimentalFeatures": ["on-type-formatting"]
66
}

Lambdas/GHHooks/Constants.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ enum Constants {
55
static let guildID: GuildSnowflake = "431917998102675485"
66
static let botDevUserID: UserSnowflake = "290483761559240704"
77

8+
static let trustedGitHubUserIds: Set<Int64> = [
9+
69_189_821, // Paul
10+
9_938_337, // Tim
11+
1_130_717, // Gwynne
12+
54_685_446, // Mahdi
13+
]
14+
815
enum GitHub {
916
/// The user id of Penny.
1017
static let userID = 139_480_971

Lambdas/GHHooks/EventHandler/Handlers/PRCoinGiver.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ struct PRCoinGiver {
3030
repoFullName: repo.fullName,
3131
branch: branch
3232
)
33-
for pr in try await getPRsRelatedToCommit() {
34-
guard pr.mergedAt != nil else { continue }
33+
for pr in prs {
34+
if pr.mergedAt == nil {
35+
logger.debug("PR is not merged yet", metadata: ["pr": "\(pr)"])
36+
continue
37+
}
3538
let user = try pr.user.requireValue()
3639
var usersToReceiveCoins = codeOwners.contains(user: user) ? [] : [user.id]
3740

@@ -88,6 +91,8 @@ struct PRCoinGiver {
8891
}
8992

9093
func giveCoin(userId: Int64, pr: SimplePullRequest) async throws {
94+
logger.trace("Giving a coin", metadata: ["userId": .stringConvertible(userId)])
95+
9196
guard
9297
let member = try await context.requester.getDiscordMember(
9398
githubID: "\(userId)"

Lambdas/GHHooks/EventHandler/Handlers/PRHandler.swift

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
import AsyncHTTPClient
21
import DiscordBM
32
import GitHubAPI
4-
import Markdown
5-
import NIOCore
6-
import NIOFoundationCompat
7-
import SwiftSemver
3+
import Logging
84

95
#if canImport(FoundationEssentials)
106
import FoundationEssentials
@@ -19,6 +15,9 @@ struct PRHandler {
1915
var event: GHEvent {
2016
self.context.event
2117
}
18+
var logger: Logger {
19+
context.logger
20+
}
2221

2322
var repo: Repository {
2423
get throws {
@@ -49,17 +48,21 @@ struct PRHandler {
4948
}
5049

5150
func onEdited() async throws {
52-
try await self.editPRReport()
51+
if self.isIgnorableDoNotMergePR() { return }
52+
try await self.makeReporter().reportEdition(
53+
requiresPreexistingReport: self.action == .labeled
54+
)
5355
}
5456

5557
func onOpened() async throws {
58+
if self.isIgnorableDoNotMergePR() { return }
5659
try await self.makeReporter().reportCreation()
5760
}
5861

5962
func onClosed() async throws {
6063
try await withThrowingAccumulatingVoidTaskGroup(tasks: [
6164
{ try await self.makeRelease() },
62-
{ try await self.editPRReport() },
65+
{ try await self.onEdited() },
6366
])
6467
}
6568

@@ -71,12 +74,6 @@ struct PRHandler {
7174
).handle()
7275
}
7376

74-
func editPRReport() async throws {
75-
try await self.makeReporter().reportEdition(
76-
requiresPreexistingReport: self.action == .labeled
77-
)
78-
}
79-
8077
func makeReporter() async throws -> TicketReporter {
8178
try TicketReporter(
8279
context: self.context,
@@ -125,6 +122,19 @@ struct PRHandler {
125122

126123
return embed
127124
}
125+
126+
func isIgnorableDoNotMergePR() -> Bool {
127+
let isIgnorable = self.pr.isIgnorableDoNotMergePR
128+
if isIgnorable {
129+
logger.info(
130+
"PR is ignorable",
131+
metadata: [
132+
"pr": "\(self.pr)"
133+
]
134+
)
135+
}
136+
return isIgnorable
137+
}
128138
}
129139

130140
private enum Status: String {

Lambdas/GHHooks/EventHandler/Handlers/ReleaseMaker.swift

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,9 @@ struct ReleaseMaker {
290290
}
291291

292292
func isNewContributor(codeOwners: CodeOwners, existingContributors: Set<Int64>) -> Bool {
293-
pr.authorAssociation != .owner && !pr.user.isBot && !codeOwners.contains(user: pr.user)
293+
pr.authorAssociation != .owner
294+
&& !pr.user.isBot
295+
&& !codeOwners.contains(user: pr.user)
294296
&& !existingContributors.contains(pr.user.id)
295297
}
296298

@@ -305,19 +307,18 @@ struct ReleaseMaker {
305307
)
306308
)
307309

308-
guard case let .ok(ok) = response,
309-
case let .json(json) = ok.body
310-
else {
310+
do {
311+
return try response.ok.body.json
312+
} catch {
311313
logger.warning(
312314
"Could not find reviews",
313315
metadata: [
314-
"response": "\(response)"
316+
"response": "\(response)",
317+
"error": "\(String(reflecting: error))",
315318
]
316319
)
317320
return []
318321
}
319-
320-
return json
321322
}
322323

323324
func getExistingContributorIDs() async throws -> Set<Int64> {
@@ -357,9 +358,9 @@ struct ReleaseMaker {
357358
)
358359
)
359360

360-
if case let .ok(ok) = response,
361-
case let .json(json) = ok.body
362-
{
361+
do {
362+
let ok = try response.ok
363+
let json = try ok.body.json
363364
/// Example of a `link` header: `<https://api.github.com/repositories/49910095/contributors?page=6>; rel="prev", <https://api.github.com/repositories/49910095/contributors?page=8>; rel="next", <https://api.github.com/repositories/49910095/contributors?page=8>; rel="last", <https://api.github.com/repositories/49910095/contributors?page=1>; rel="first"`
364365
/// If the header contains `rel="next"` then we'll have a next page to fetch.
365366
let hasNext =
@@ -378,12 +379,13 @@ struct ReleaseMaker {
378379
]
379380
)
380381
return (ids, hasNext)
381-
} else {
382+
} catch {
382383
logger.error(
383384
"Error when fetching contributors but will continue",
384385
metadata: [
385386
"page": .stringConvertible(page),
386387
"response": "\(response)",
388+
"error": "\(String(reflecting: error))",
387389
]
388390
)
389391
return ([], false)

Lambdas/GHHooks/EventHandler/Handlers/TicketReporter.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ struct TicketReporter {
128128
)
129129
case let .some(error):
130130
throw error
131-
default: break
131+
case .none:
132+
break
132133
}
133134
}
134135
}

Lambdas/GHHooks/Extensions/+PR.Label.swift renamed to Lambdas/GHHooks/Extensions/+PR.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import GitHubAPI
22

3+
#if canImport(FoundationEssentials)
4+
import FoundationEssentials
5+
#else
6+
import Foundation
7+
#endif
8+
39
extension PullRequest {
410

511
enum KnownLabel: String {
@@ -28,6 +34,14 @@ extension PullRequest {
2834
KnownLabel(rawValue: $0.name)
2935
}
3036
}
37+
38+
var isIgnorableDoNotMergePR: Bool {
39+
let isTrustedUser = Constants.trustedGitHubUserIds.contains(self.user.id)
40+
guard isTrustedUser || self.authorAssociation.isContributorOrHigher else {
41+
return false
42+
}
43+
return self.title.hasDoNotMergePrefix
44+
}
3145
}
3246

3347
extension SimplePullRequest {
@@ -37,3 +51,22 @@ extension SimplePullRequest {
3751
}
3852
}
3953
}
54+
55+
extension AuthorAssociation {
56+
fileprivate var isContributorOrHigher: Bool {
57+
switch self {
58+
case .collaborator, .contributor, .owner: return true
59+
case .member, .firstTimer, .firstTimeContributor, .mannequin, .none: return false
60+
}
61+
}
62+
}
63+
64+
extension String {
65+
fileprivate var hasDoNotMergePrefix: Bool {
66+
let folded = self.lowercased()
67+
.filter { !$0.isPunctuation }
68+
.folding(options: .caseInsensitive, locale: nil)
69+
.folding(options: .diacriticInsensitive, locale: nil)
70+
return folded.hasPrefix("dnm") || folded.hasPrefix("do not merge")
71+
}
72+
}

Lambdas/GitHubAPI/Aliases.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package typealias Repository = Components.Schemas.Repository
22
package typealias User = Components.Schemas.SimpleUser
33
package typealias PullRequest = Components.Schemas.PullRequest
44
package typealias SimplePullRequest = Components.Schemas.PullRequestSimple
5+
package typealias AuthorAssociation = Components.Schemas.AuthorAssociation
56
package typealias Issue = Components.Schemas.Issue
67
package typealias Label = Components.Schemas.Label
78
package typealias Release = Components.Schemas.Release

Tests/PennyTests/Tests/GHHooksTests.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,15 @@ actor GHHooksTests {
864864
)
865865
}
866866

867+
@Test("PR with [DNM] prefix where author_association is CONTRIBUTOR should not be posted to Discord")
868+
func handlePREvent14() async throws {
869+
try await handleEvent(
870+
key: "pr14",
871+
eventName: .pull_request,
872+
expect: .noResponse
873+
)
874+
}
875+
867876
@Test
868877
func handlePushEvent1() async throws {
869878
try await handleEvent(

0 commit comments

Comments
 (0)