From 5132c39da0c2a9bfb7b6a1c630af80138994185f Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Thu, 7 Mar 2024 15:35:48 -0300 Subject: [PATCH 1/2] feat(github): Use schema for issue objects --- lib/modules/platform/github/index.spec.ts | 110 ++++++++++++++++++++-- lib/modules/platform/github/index.ts | 52 +++++----- lib/modules/platform/github/issue.ts | 28 ++++++ lib/modules/platform/github/types.ts | 10 +- 4 files changed, 159 insertions(+), 41 deletions(-) create mode 100644 lib/modules/platform/github/issue.ts diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index d887628eb921d0..d0a33bb4ea4681 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -1632,11 +1632,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1665,11 +1669,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1677,9 +1685,21 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/undefined/issues/2') - .reply(200, { body: 'new-content' }); + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.findIssue('title-2'); - expect(res).not.toBeNull(); + expect(res).toEqual({ + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + lastModified: '2023-01-01T00:00:00Z', + }); }); }); @@ -1704,11 +1724,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1744,11 +1768,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'closed', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1782,11 +1810,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'closed', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1852,16 +1884,22 @@ describe('modules/platform/github/index', () => { number: 3, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'closed', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1899,11 +1937,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1911,7 +1953,13 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'new-content' }) + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }) .patch('/repos/some/repo/issues/2') .reply(200); const res = await github.ensureIssue({ @@ -1942,11 +1990,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -1954,7 +2006,13 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'new-content' }) + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }) .patch('/repos/some/repo/issues/2') .reply(200); const res = await github.ensureIssue({ @@ -1986,11 +2044,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'newer-content', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'new-content', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, @@ -2026,21 +2088,31 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, }, }, }) - .patch('/repos/some/repo/issues/1') - .reply(200) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'newer-content' }); + .reply(200, { + number: 2, + state: 'open', + title: 'title-1', + body: 'newer-content', + updated_at: '2021-01-01T00:00:00Z', + }) + .patch('/repos/some/repo/issues/1') + .reply(200); const res = await github.ensureIssue({ title: 'title-1', body: 'newer-content', @@ -2068,6 +2140,8 @@ describe('modules/platform/github/index', () => { number: 2, state: 'close', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, ], }, @@ -2075,7 +2149,13 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'new-content' }) + .reply(200, { + number: 2, + state: 'closed', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }) .post('/repos/some/repo/issues') .reply(200); const res = await github.ensureIssue({ @@ -2107,6 +2187,8 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, ], }, @@ -2114,7 +2196,13 @@ describe('modules/platform/github/index', () => { }, }) .get('/repos/some/repo/issues/2') - .reply(200, { body: 'new-content' }); + .reply(200, { + number: 2, + state: 'open', + title: 'title-2', + body: 'new-content', + updated_at: '2023-01-01T00:00:00Z', + }); const res = await github.ensureIssue({ title: 'title-2', body: 'new-content', @@ -2144,11 +2232,15 @@ describe('modules/platform/github/index', () => { number: 2, state: 'open', title: 'title-2', + body: 'body-2', + updatedAt: '2022-01-01T00:00:00Z', }, { number: 1, state: 'open', title: 'title-1', + body: 'body-1', + updatedAt: '2021-01-01T00:00:00Z', }, ], }, diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index b5c6948fe4430c..e629dd6e6e6b43 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -56,7 +56,6 @@ import type { EnsureIssueConfig, EnsureIssueResult, FindPRConfig, - Issue, MergePRConfig, PlatformParams, PlatformPrOptions, @@ -75,6 +74,7 @@ import { repoInfoQuery, vulnerabilityAlertsQuery, } from './graphql'; +import { GithubIssue as Issue } from './issue'; import { massageMarkdownLinks } from './massage-markdown-links'; import { getPrCache, updatePrCache } from './pr'; import type { @@ -1197,9 +1197,8 @@ export async function setBranchStatus({ // Issue -/* istanbul ignore next */ async function getIssues(): Promise { - const result = await githubApi.queryRepoField( + const result = await githubApi.queryRepoField( getIssuesQuery, 'issues', { @@ -1212,10 +1211,7 @@ async function getIssues(): Promise { ); logger.debug(`Retrieved ${result.length} issues`); - return result.map((issue) => ({ - ...issue, - state: issue.state?.toLowerCase(), - })); + return Issue.array().parse(result); } export async function getIssueList(): Promise { @@ -1239,16 +1235,13 @@ export async function getIssue( return null; } try { - const issueBody = ( - await githubApi.getJson<{ body: string }>( - `repos/${config.parentRepo ?? config.repository}/issues/${number}`, - { memCache: useCache, repoCache: true }, - ) - ).body.body; - return { - number, - body: issueBody, - }; + const repo = config.parentRepo ?? config.repository; + const { body: issue } = await githubApi.getJson( + `repos/${repo}/issues/${number}`, + { memCache: useCache, repoCache: true }, + Issue, + ); + return issue; } catch (err) /* istanbul ignore next */ { logger.debug({ err, number }, 'Error getting issue'); return null; @@ -1264,8 +1257,7 @@ export async function findIssue(title: string): Promise { return null; } logger.debug(`Found issue ${issue.number}`); - // TODO: can number be required? (#22198) - return getIssue(issue.number!); + return getIssue(issue.number); } async function closeIssue(issueNumber: number): Promise { @@ -1319,18 +1311,17 @@ export async function ensureIssue({ for (const i of issues) { if (i.state === 'open' && i.number !== issue.number) { logger.warn({ issueNo: i.number }, 'Closing duplicate issue'); - // TODO #22198 - await closeIssue(i.number!); + await closeIssue(i.number); } } - const issueBody = ( - await githubApi.getJson<{ body: string }>( - `repos/${config.parentRepo ?? config.repository}/issues/${ - issue.number - }`, - { repoCache: true }, - ) - ).body.body; + const repo = config.parentRepo ?? config.repository; + const { + body: { body: issueBody }, + } = await githubApi.getJson( + `repos/${repo}/issues/${issue.number}`, + { repoCache: true }, + Issue, + ); if ( issue.title === title && issueBody === body && @@ -1393,8 +1384,7 @@ export async function ensureIssueClosing(title: string): Promise { const issueList = await getIssueList(); for (const issue of issueList) { if (issue.state === 'open' && issue.title === title) { - // TODO #22198 - await closeIssue(issue.number!); + await closeIssue(issue.number); logger.debug(`Issue closed, issueNo: ${issue.number}`); } } diff --git a/lib/modules/platform/github/issue.ts b/lib/modules/platform/github/issue.ts new file mode 100644 index 00000000000000..295b8c11e9f28f --- /dev/null +++ b/lib/modules/platform/github/issue.ts @@ -0,0 +1,28 @@ +import { z } from 'zod'; +import type { GithubIssue as Issue } from './types'; + +const GithubIssueBase = z.object({ + number: z.number(), + state: z.string().transform((val) => val.toLowerCase()), + title: z.string(), + body: z.string(), +}); + +const GithubGraphqlIssue = GithubIssueBase.extend({ + updatedAt: z.string(), +}).transform((issue): Issue => { + const lastModified = issue.updatedAt; + const { number, state, title, body } = issue; + return { number, state, title, body, lastModified }; +}); + +const GithubRestIssue = GithubIssueBase.extend({ + updated_at: z.string(), +}).transform((issue): Issue => { + const lastModified = issue.updated_at; + const { number, state, title, body } = issue; + return { number, state, title, body, lastModified }; +}); + +export const GithubIssue = z.union([GithubGraphqlIssue, GithubRestIssue]); +export type GithubIssue = z.infer; diff --git a/lib/modules/platform/github/types.ts b/lib/modules/platform/github/types.ts index 48c41ef007a1ce..986154717f9213 100644 --- a/lib/modules/platform/github/types.ts +++ b/lib/modules/platform/github/types.ts @@ -97,7 +97,7 @@ export interface LocalRepoConfig { forkToken?: string; forkCreation?: boolean; prList: GhPr[] | null; - issueList: any[] | null; + issueList: GithubIssue[] | null; mergeMethod: 'rebase' | 'squash' | 'merge'; defaultBranch: string; repositoryOwner: string; @@ -153,3 +153,11 @@ export interface ApiPageCache { items: Record; lastModified?: string; } + +export interface GithubIssue { + body: string; + number: number; + state: string; + title: string; + lastModified: string; +} From 0c9218f9045433624bd3bb82b45558bc4f7bed6c Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Thu, 7 Mar 2024 15:42:47 -0300 Subject: [PATCH 2/2] Add `updatedAt` field --- lib/modules/platform/github/graphql.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/platform/github/graphql.ts b/lib/modules/platform/github/graphql.ts index 9e5f0155a2933c..2f0890af74b994 100644 --- a/lib/modules/platform/github/graphql.ts +++ b/lib/modules/platform/github/graphql.ts @@ -48,6 +48,7 @@ query( state title body + updatedAt } } }