From 36516198c9a07b1c58eee5ffd7380ced8921735a Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Mon, 26 Sep 2022 07:51:13 +0300 Subject: [PATCH] fix(github): Don't store raw PR data in long-term cache (#17967) --- lib/modules/platform/github/common.ts | 10 +-- lib/modules/platform/github/index.spec.ts | 85 +---------------------- lib/modules/platform/github/index.ts | 7 +- lib/modules/platform/github/pr.ts | 79 +++++++-------------- lib/modules/platform/github/types.ts | 3 +- 5 files changed, 37 insertions(+), 147 deletions(-) diff --git a/lib/modules/platform/github/common.ts b/lib/modules/platform/github/common.ts index 8a039c4629..2b9e146bab 100644 --- a/lib/modules/platform/github/common.ts +++ b/lib/modules/platform/github/common.ts @@ -6,12 +6,7 @@ import type { GhPr, GhRestPr } from './types'; /** * @see https://docs.github.com/en/rest/reference/pulls#list-pull-requests */ -export function coerceRestPr(pr: GhRestPr | null | undefined): GhPr | null { - // istanbul ignore if - if (!pr) { - return null; - } - +export function coerceRestPr(pr: GhRestPr): GhPr { const bodyStruct = pr.bodyStruct ?? getPrBodyStruct(pr.body); const result: GhPr = { displayNumber: `Pull Request #${pr.number}`, @@ -23,7 +18,8 @@ export function coerceRestPr(pr: GhRestPr | null | undefined): GhPr | null { ? PrState.Merged : pr.state, bodyStruct, - updatedAt: pr.updated_at, + updated_at: pr.updated_at, + node_id: pr.node_id, }; if (pr.head?.sha) { diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 07a109f2d3..3518e86708 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -679,64 +679,6 @@ describe('modules/platform/github/index', () => { ]); }); - describe('Url cleanup', () => { - type GhRestPrWithUrls = GhRestPr & { - url: string; - example_url: string; - repo: { - example_url: string; - }; - }; - - type PrCache = ApiPageCache<GhRestPrWithUrls>; - - const prWithUrls = (): GhRestPrWithUrls => ({ - ...pr1, - url: 'https://example.com', - example_url: 'https://example.com', - _links: { foo: { href: 'https://example.com' } }, - repo: { example_url: 'https://example.com' }, - }); - - it('removes url data from response', async () => { - const scope = httpMock.scope(githubApiHost); - initRepoMock(scope, 'some/repo'); - scope.get(pagePath(1)).reply(200, [prWithUrls()]); - await github.initRepo({ repository: 'some/repo' }); - - await github.getPrList(); - - const repoCache = repository.getCache(); - const prCache = repoCache.platform?.github?.prCache as PrCache; - expect(prCache).toMatchObject({ items: {} }); - - const item = prCache.items[1]; - expect(item).toBeDefined(); - expect(item._links).toBeUndefined(); - expect(item.url).toBeUndefined(); - expect(item.example_url).toBeUndefined(); - expect(item.repo.example_url).toBeUndefined(); - }); - - it('removes url data from existing cache', async () => { - const scope = httpMock.scope(githubApiHost); - initRepoMock(scope, 'some/repo'); - scope.get(pagePath(1, 20)).reply(200, []); - await github.initRepo({ repository: 'some/repo' }); - const repoCache = repository.getCache(); - const prCache: PrCache = { items: { 1: prWithUrls() } }; - repoCache.platform = { github: { prCache } }; - - await github.getPrList(); - - const item = prCache.items[1]; - expect(item._links).toBeUndefined(); - expect(item.url).toBeUndefined(); - expect(item.example_url).toBeUndefined(); - expect(item.repo.example_url).toBeUndefined(); - }); - }); - describe('Body compaction', () => { type PrCache = ApiPageCache<GhRestPr>; @@ -762,27 +704,6 @@ describe('modules/platform/github/index', () => { expect(item.body).toBeUndefined(); expect(item.bodyStruct).toEqual({ hash: hashBody('foo') }); }); - - it('removes url data from existing cache', async () => { - const scope = httpMock.scope(githubApiHost); - initRepoMock(scope, 'some/repo'); - scope.get(pagePath(1)).reply(200, [prWithBody('foo')]); - await github.initRepo({ repository: 'some/repo' }); - const repoCache = repository.getCache(); - const prCache: PrCache = { - items: { 1: prWithBody('bar'), 2: prWithBody('baz') }, - }; - repoCache.platform = { github: { prCache } }; - - await github.getPrList(); - - expect(prCache.items[2]).toBeUndefined(); - - const item = prCache.items[1]; - expect(item).toBeDefined(); - expect(item.body).toBeUndefined(); - expect(item.bodyStruct).toEqual({ hash: hashBody('foo') }); - }); }); }); @@ -2456,7 +2377,7 @@ describe('modules/platform/github/index', () => { sourceRepo: 'some/repo', state: 'open', title: 'chore(deps): update dependency jest to v23.6.0', - updatedAt: '01-09-2022', + updated_at: '01-09-2022', }); }); @@ -2586,7 +2507,7 @@ describe('modules/platform/github/index', () => { sourceBranch: 'some/branch', state: 'open', title: 'Some title', - updatedAt: '01-09-2022', + updated_at: '01-09-2022', }); }); @@ -2619,7 +2540,7 @@ describe('modules/platform/github/index', () => { sourceBranch: 'some/branch', state: 'open', title: 'Some title', - updatedAt: '01-09-2022', + updated_at: '01-09-2022', }); }); }); diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 082fc58b5e..5a3bcab757 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1443,10 +1443,13 @@ export async function createPr({ { branch: sourceBranch, pr: ghPr.number, draft: draftPR }, 'PR created' ); - const { number, node_id } = ghPr; + + const result = coerceRestPr(ghPr); + const { number, node_id } = result; + await addLabels(number, labels); await tryPrAutomerge(number, node_id, platformOptions); - const result = coerceRestPr(ghPr); + cachePr(result); return result; } diff --git a/lib/modules/platform/github/pr.ts b/lib/modules/platform/github/pr.ts index 76a8bff0db..f1064b875f 100644 --- a/lib/modules/platform/github/pr.ts +++ b/lib/modules/platform/github/pr.ts @@ -5,63 +5,43 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import { getCache } from '../../../util/cache/repository'; import type { GithubHttp, GithubHttpOptions } from '../../../util/http/github'; import { parseLinkHeader } from '../../../util/url'; -import { getPrBodyStruct } from '../pr-body'; import { ApiCache } from './api-cache'; import { coerceRestPr } from './common'; import type { ApiPageCache, GhPr, GhRestPr } from './types'; -function removeUrlFields(input: unknown): void { - if (is.plainObject(input)) { - for (const [key, val] of Object.entries(input)) { - if ((key === 'url' || key.endsWith('_url')) && is.string(val)) { - delete input[key]; - } else { - removeUrlFields(val); - } +function isOldCache(prCache: unknown): prCache is ApiPageCache<GhRestPr> { + if ( + is.plainObject(prCache) && + is.plainObject(prCache.items) && + !is.emptyObject(prCache.items) + ) { + const [item] = Object.values(prCache.items); + if (is.plainObject(item) && is.string(item.node_id)) { + return true; } } + + return false; } -function compactPrBodyStructure(input: unknown): void { - if (is.plainObject(input)) { - if (!input.bodyStruct && is.string(input.body)) { - input.bodyStruct = getPrBodyStruct(input.body); - delete input.body; +function migrateCache(cache: unknown): void { + const items: ApiPageCache<GhPr>['items'] = {}; + if (isOldCache(cache)) { + for (const item of Object.values(cache.items)) { + items[item.number] = coerceRestPr(item); } + cache.items = items as never; } } -function massageGhRestPr(ghPr: GhRestPr): GhRestPr { - removeUrlFields(ghPr); - delete ghPr.head?.repo?.pushed_at; - delete ghPr.base?.repo?.pushed_at; - delete ghPr._links; - - compactPrBodyStructure(ghPr); - - return ghPr; -} - -function getPrApiCache(): ApiCache<GhRestPr> { +function getPrApiCache(): ApiCache<GhPr> { const repoCache = getCache(); repoCache.platform ??= {}; repoCache.platform.github ??= {}; repoCache.platform.github.prCache ??= { items: {} }; - const apiPageCache = repoCache.platform.github - .prCache as ApiPageCache<GhRestPr>; - - const items = Object.values(apiPageCache.items); - - const firstItem = items?.[0]; - if (firstItem?.body) { - apiPageCache.items = {}; - } else if (firstItem?._links) { - for (const ghPr of items) { - massageGhRestPr(ghPr); - } - } - - const prCache = new ApiCache(apiPageCache); + const cache = repoCache.platform.github.prCache; + migrateCache(cache); + const prCache = new ApiCache<GhPr>(cache as ApiPageCache<GhPr>); return prCache; } @@ -97,7 +77,6 @@ export async function getPrCache( repo: string, username: string | null ): Promise<Record<number, GhPr>> { - const prCache: Record<number, GhPr> = {}; const prApiCache = getPrApiCache(); const isInitial = is.emptyArray(prApiCache.getItems()); @@ -134,11 +113,9 @@ export async function getPrCache( ); } - for (const ghPr of page) { - massageGhRestPr(ghPr); - } + const items = page.map(coerceRestPr); - needNextPageSync = prApiCache.reconcile(page); + needNextPageSync = prApiCache.reconcile(items); needNextPageFetch = !!parseLinkHeader(linkHeader)?.next; if (pageIdx === 1) { @@ -161,13 +138,5 @@ export async function getPrCache( throw new ExternalHostError(err, PlatformId.Github); } - const cacheItems = prApiCache.getItems(); - for (const ghPr of cacheItems) { - const pr = coerceRestPr(ghPr); - if (pr) { - prCache[ghPr.number] = pr; - } - } - - return prCache; + return prApiCache.getItems(); } diff --git a/lib/modules/platform/github/types.ts b/lib/modules/platform/github/types.ts index e1d66645fc..11e56db517 100644 --- a/lib/modules/platform/github/types.ts +++ b/lib/modules/platform/github/types.ts @@ -54,7 +54,8 @@ export interface GhRestPr { } export interface GhPr extends Pr { - updatedAt: string; + updated_at: string; + node_id: string; } export interface UserDetails { -- GitLab