Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/fresh-app-deduplication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'frog': patch
---

Matched labelled reports for GitHub App tokens and checked recent issues first during unlabelled fallback scans.
18 changes: 16 additions & 2 deletions src/Github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ describe('find', () => {
).toBeUndefined()
})

test('behavior: caps pagination at 50 pages', async () => {
test('behavior: scans the newest issues before reaching the 50-page cap', async () => {
const seeded = Array.from({ length: 5_001 }, (_, index) => ({
labels: [],
title: index === 5_000 ? title : `Unrelated ${index}`,
Expand All @@ -552,7 +552,7 @@ describe('find', () => {

expect(
await Github.find(client(instance.url), { hash: Github.hash(title), repo, title }),
).toBeUndefined()
).toMatchObject({ number: 5_001 })
expect(
instance.requests.filter(
(request) => request.method === 'GET' && request.path === '/repos/wevm/viem/issues',
Expand Down Expand Up @@ -629,6 +629,20 @@ describe('index', () => {
})

describe('matcher', () => {
test('behavior: indexes labelled issues without reported push access', async () => {
const instance = await github({ [repo]: [{ title }] }, { pushAccess: [] })

const matcher = await Github.matcher(client(instance.url), { label: 'friction', repo })

expect(matcher.labelled).toBe(false)
expect(
instance.requests.filter(
(request) => request.method === 'GET' && request.path === `/repos/${repo}/issues`,
),
).toHaveLength(1)
await expect(matcher.match(title)).resolves.toMatchObject({ number: 1 })
})

test('behavior: matches a report before a changed title', async () => {
const report = 'acme/app:entry-a'
const instance = await github(
Expand Down
10 changes: 5 additions & 5 deletions src/Github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1010,7 +1010,7 @@ async function listAll(client: Client, options: { repo: string }): Promise<reado
for (let page = 1; page <= 50; page++) {
const response = await client.issues.listForRepo({
...split(options.repo),
direction: 'asc',
direction: 'desc',
page,
per_page: 100,
sort: 'created',
Expand Down Expand Up @@ -1064,9 +1064,9 @@ export type Matcher = {
/**
* Prepares dedupe for a repository, choosing a strategy the token can actually use.
*
* With push access, issues are indexed by label first. A miss falls back to one unfiltered listing,
* which also covers labels removed by users or dropped during creation. Without push access, that
* fallback is the primary index.
* Issues are indexed by label first. A miss falls back to one unfiltered listing, which also covers
* labels removed by users or dropped during creation. Push access only determines whether labels are
* expected to stick on newly created issues.
*
* @param client - Authenticated client for the repository.
*/
Expand All @@ -1075,7 +1075,7 @@ export async function matcher(client: Client, options: matcher.Options): Promise
const accepts = (issue: Issue) =>
!options.exclude?.(issue) &&
(!options.expectedAuthor || issue.author === options.expectedAuthor)
const labelledIssues = push ? (await list(client, options)).filter(accepts) : []
const labelledIssues = (await list(client, options)).filter(accepts)
const titleOverrides = new Map<number, string>()
const updateIndex = (index: Map<string, Issue>, issues: readonly Issue[]) => {
index.clear()
Expand Down
2 changes: 2 additions & 0 deletions test/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ export async function github(seed: Seed = {}, options: Options = {}): Promise<In
const label = url.searchParams.get('labels')
const creator = url.searchParams.get('creator')
const state = url.searchParams.get('state') ?? 'open'
const direction = url.searchParams.get('direction') ?? 'desc'
const page = Number(url.searchParams.get('page') ?? '1')
const perPage = Number(url.searchParams.get('per_page') ?? '30')

Expand All @@ -381,6 +382,7 @@ export async function github(seed: Seed = {}, options: Options = {}): Promise<In
...issue,
...(pulls.has(`${repo}#${issue.number}`) ? { pull_request: { url: '' } } : {}),
}))
if (direction === 'desc') matching.reverse()

return json(response, 200, matching.slice((page - 1) * perPage, page * perPage))
}
Expand Down