From 13178f01838ff39faa79e59b400414664a6a5c21 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 10 Dec 2025 08:07:04 -0800 Subject: [PATCH] Remove 'any' types from 14 files (#58716) --- src/codeql-cli/scripts/sync.ts | 2 +- .../lib/linting-rules/ctas-schema.ts | 7 +- .../lib/linting-rules/link-punctuation.ts | 20 +++-- src/content-render/liquid/data.ts | 11 ++- src/fixtures/tests/internal-links.ts | 29 ++++--- src/graphql/tests/build-changelog.ts | 8 +- .../components/ProductGuidesContext.tsx | 44 ++++++++--- .../scripts/count-translation-corruptions.ts | 4 +- src/learning-track/lib/types.ts | 24 ++---- src/observability/tests/failbot.ts | 2 +- src/rest/api/anchor-redirect.ts | 16 ++-- src/rest/scripts/openapi-check.ts | 8 +- src/rest/scripts/utils/get-body-params.ts | 78 +++++++++++-------- src/rest/scripts/utils/get-operations.ts | 2 +- src/webhooks/tests/oneof-handling.ts | 18 ++++- src/workflows/ready-for-docs-review.ts | 67 ++++++++++------ 16 files changed, 207 insertions(+), 133 deletions(-) diff --git a/src/codeql-cli/scripts/sync.ts b/src/codeql-cli/scripts/sync.ts index 7ca45f732e..d7a2739801 100755 --- a/src/codeql-cli/scripts/sync.ts +++ b/src/codeql-cli/scripts/sync.ts @@ -29,7 +29,7 @@ async function main() { includeBasePath: true, globs: ['**/*.md'], }) - const cliMarkdownContents: Record = {} + const cliMarkdownContents: Record; content: string }> = {} for (const file of markdownFiles) { const sourceContent = await readFile(file, 'utf8') diff --git a/src/content-linter/lib/linting-rules/ctas-schema.ts b/src/content-linter/lib/linting-rules/ctas-schema.ts index 00981e0fb5..72c58a70ec 100644 --- a/src/content-linter/lib/linting-rules/ctas-schema.ts +++ b/src/content-linter/lib/linting-rules/ctas-schema.ts @@ -105,15 +105,16 @@ export const ctasSchema: Rule = { for (const error of errors) { let message = '' if (error.keyword === 'required') { - message = `Missing required parameter: ${(error.params as any)?.missingProperty}` + message = `Missing required parameter: ${(error.params as { missingProperty?: string })?.missingProperty}` } else if (error.keyword === 'enum') { const paramName = error.instancePath.substring(1) // Get the actual invalid value from refParams and allowed values from params const invalidValue = refParams[paramName] - const allowedValues = (error.params as any)?.allowedValues || [] + const allowedValues = + (error.params as { allowedValues?: unknown[] })?.allowedValues || [] message = `Invalid value for ${paramName}: "${invalidValue}". Valid values are: ${allowedValues.join(', ')}` } else if (error.keyword === 'additionalProperties') { - message = `Unexpected parameter: ${(error.params as any)?.additionalProperty}` + message = `Unexpected parameter: ${(error.params as { additionalProperty?: string })?.additionalProperty}` } else { message = `CTA URL validation error: ${error.message}` } diff --git a/src/content-linter/lib/linting-rules/link-punctuation.ts b/src/content-linter/lib/linting-rules/link-punctuation.ts index 75045e2b2e..afeecb5656 100644 --- a/src/content-linter/lib/linting-rules/link-punctuation.ts +++ b/src/content-linter/lib/linting-rules/link-punctuation.ts @@ -3,31 +3,39 @@ import type { RuleParams, RuleErrorCallback, Rule } from '../../types' import { doesStringEndWithPeriod, getRange, isStringQuoted } from '../helpers/utils' +// Minimal type for markdownit tokens used in this rule +interface MarkdownToken { + children?: MarkdownToken[] + line?: string + type?: string + content?: string + lineNumber?: number +} + export const linkPunctuation: Rule = { names: ['GHD001', 'link-punctuation'], description: 'Internal link titles must not contain punctuation', tags: ['links', 'url'], parser: 'markdownit', function: (params: RuleParams, onError: RuleErrorCallback) => { - // Using 'any' type for token as markdownlint-rule-helpers doesn't provide TypeScript types - filterTokens(params, 'inline', (token: any) => { + filterTokens(params, 'inline', (token: MarkdownToken) => { const { children, line } = token let inLink = false - for (const child of children) { + for (const child of children || []) { if (child.type === 'link_open') { inLink = true } else if (child.type === 'link_close') { inLink = false - } else if (inLink && child.type === 'text') { + } else if (inLink && child.type === 'text' && child.content) { const content = child.content.trim() const hasPeriod = doesStringEndWithPeriod(content) const hasQuotes = isStringQuoted(content) if (hasPeriod || hasQuotes) { - const range = getRange(line, content) + const range = line ? getRange(line, content) : [] addError( onError, - child.lineNumber, + child.lineNumber || 1, 'Remove quotes and/or period punctuation from the link title.', child.content, range, diff --git a/src/content-render/liquid/data.ts b/src/content-render/liquid/data.ts index fce51925c7..28c8f58173 100644 --- a/src/content-render/liquid/data.ts +++ b/src/content-render/liquid/data.ts @@ -7,10 +7,13 @@ import { getDataByLanguage } from '@/data-directory/lib/get-data' const Syntax = /([a-z0-9/\\_.\-[\]]+)/i const SyntaxHelp = "Syntax Error in 'data' - Valid syntax: data [path]" -// Using any for scope because it has custom environments property not in Liquid's Scope type +// Using unknown for scope because it has custom environments property not in Liquid's Scope type interface CustomScope { - environments: any - [key: string]: any + environments: { + currentLanguage?: string + [key: string]: unknown + } + [key: string]: unknown } interface DataTag { @@ -32,7 +35,7 @@ export default { }, async render(scope: CustomScope) { - let text = getDataByLanguage(this.path, scope.environments.currentLanguage) + let text = getDataByLanguage(this.path, scope.environments.currentLanguage || '') if (text === undefined) { if (scope.environments.currentLanguage === 'en') { const message = `Can't find the key 'data ${this.path}' in the scope.` diff --git a/src/fixtures/tests/internal-links.ts b/src/fixtures/tests/internal-links.ts index d95dcc71d0..e0847dc239 100644 --- a/src/fixtures/tests/internal-links.ts +++ b/src/fixtures/tests/internal-links.ts @@ -9,7 +9,7 @@ describe('autotitle', () => { test('internal links with AUTOTITLE resolves', async () => { const $: cheerio.Root = await getDOM('/get-started/foo/autotitling') const links = $('#article-contents a[href]') - links.each((i: number, element: any) => { + links.each((i: number, element: cheerio.Element) => { if ($(element).attr('href')?.includes('/get-started/start-your-journey/hello-world')) { expect($(element).text()).toBe('Hello World') } @@ -49,13 +49,14 @@ describe('cross-version-links', () => { // Tests that the hardcoded prefix is always removed const firstLink = links.filter( - (i: number, element: any) => $(element).text() === 'Hello world always in free-pro-team', + (i: number, element: cheerio.Element) => + $(element).text() === 'Hello world always in free-pro-team', ) expect(firstLink.attr('href')).toBe('/en/get-started/start-your-journey/hello-world') // Tests that the second link always goes to enterprise-server@X.Y const secondLink = links.filter( - (i: number, element: any) => + (i: number, element: cheerio.Element) => $(element).text() === 'Autotitling page always in enterprise-server latest', ) expect(secondLink.attr('href')).toBe( @@ -72,7 +73,7 @@ describe('link-rewriting', () => { { const link = links.filter( - (i: number, element: any) => $(element).text() === 'Cross Version Linking', + (i: number, element: cheerio.Element) => $(element).text() === 'Cross Version Linking', ) expect(link.attr('href')).toMatch('/en/get-started/') } @@ -80,21 +81,25 @@ describe('link-rewriting', () => { // Some links are left untouched { - const link = links.filter((i: number, element: any) => + const link = links.filter((i: number, element: cheerio.Element) => $(element).text().includes('Enterprise 11.10'), ) expect(link.attr('href')).toMatch('/en/enterprise/') } { - const link = links.filter((i: number, element: any) => $(element).text().includes('peterbe')) + const link = links.filter((i: number, element: cheerio.Element) => + $(element).text().includes('peterbe'), + ) expect(link.attr('href')).toMatch(/^https:/) } { - const link = links.filter((i: number, element: any) => $(element).text().includes('Picture')) + const link = links.filter((i: number, element: cheerio.Element) => + $(element).text().includes('Picture'), + ) expect(link.attr('href')).toMatch(/^\/assets\//) } { - const link = links.filter((i: number, element: any) => + const link = links.filter((i: number, element: cheerio.Element) => $(element).text().includes('GraphQL Schema'), ) expect(link.attr('href')).toMatch(/^\/public\//) @@ -108,7 +113,7 @@ describe('link-rewriting', () => { const links = $('#article-contents a[href]') const link = links.filter( - (i: number, element: any) => $(element).text() === 'Cross Version Linking', + (i: number, element: cheerio.Element) => $(element).text() === 'Cross Version Linking', ) expect(link.attr('href')).toMatch('/en/enterprise-cloud@latest/get-started/') }) @@ -121,7 +126,7 @@ describe('link-rewriting', () => { const links = $('#article-contents a[href]') const link = links.filter( - (i: number, element: any) => $(element).text() === 'Cross Version Linking', + (i: number, element: cheerio.Element) => $(element).text() === 'Cross Version Linking', ) expect(link.attr('href')).toMatch( `/en/enterprise-server@${enterpriseServerReleases.latestStable}/get-started/`, @@ -133,14 +138,14 @@ describe('subcategory links', () => { test('no free-pro-team prefix', async () => { const $: cheerio.Root = await getDOM('/rest/actions') const links = $('[data-testid="table-of-contents"] a[href]') - links.each((i: number, element: any) => { + links.each((i: number, element: cheerio.Element) => { expect($(element).attr('href')).not.toContain('/free-pro-team@latest') }) }) test('enterprise-server prefix', async () => { const $: cheerio.Root = await getDOM('/enterprise-server@latest/rest/actions') const links = $('[data-testid="table-of-contents"] a[href]') - links.each((i: number, element: any) => { + links.each((i: number, element: cheerio.Element) => { expect($(element).attr('href')).toMatch(/\/enterprise-server@\d/) }) }) diff --git a/src/graphql/tests/build-changelog.ts b/src/graphql/tests/build-changelog.ts index cf6e34e0dc..f5c131468b 100644 --- a/src/graphql/tests/build-changelog.ts +++ b/src/graphql/tests/build-changelog.ts @@ -19,8 +19,8 @@ interface Preview { title: string description: string toggled_by: string - announcement: any - updates: any + announcement: unknown + updates: unknown toggled_on: string[] owning_teams: string[] } @@ -33,7 +33,7 @@ interface UpcomingChange { interface IgnoredChange { type: string - [key: string]: any + [key: string]: unknown } interface IgnoredChangesSummary { @@ -285,7 +285,7 @@ describe('ignored changes tracking', () => { // This should generate a TypeDescriptionAdded change type that gets ignored await createChangelogEntry(oldSchemaString, newSchemaString, [], [], []) - const ignoredChanges: IgnoredChange[] = getLastIgnoredChanges() + const ignoredChanges: IgnoredChange[] = getLastIgnoredChanges() as unknown as IgnoredChange[] expect(ignoredChanges.length).toBe(1) expect(ignoredChanges[0].type).toBe('TYPE_DESCRIPTION_ADDED') }) diff --git a/src/landings/components/ProductGuidesContext.tsx b/src/landings/components/ProductGuidesContext.tsx index 65de24591e..35a3c26158 100644 --- a/src/landings/components/ProductGuidesContext.tsx +++ b/src/landings/components/ProductGuidesContext.tsx @@ -1,5 +1,6 @@ import { createContext, useContext } from 'react' import pick from 'lodash/pick' +import type { ExtendedRequest } from '@/types' export type LearningTrack = { trackName: string @@ -38,24 +39,45 @@ export const useProductGuidesContext = (): ProductGuidesContextT => { return context } -export const getProductGuidesContextFromRequest = (req: any): ProductGuidesContextT => { - const page = req.context.page +export const getProductGuidesContextFromRequest = (req: ExtendedRequest): ProductGuidesContextT => { + if (!req.context || !req.context.page) { + throw new Error('Request context or page is missing') + } - const learningTracks: LearningTrack[] = (page.learningTracks || []).map((track: any) => ({ - ...pick(track, ['title', 'description', 'trackName', 'trackProduct']), - guides: (track.guides || []).map((guide: any) => { - return pick(guide, ['title', 'intro', 'href', 'page.type']) + const page = req.context.page as typeof req.context.page & { + learningTracks?: Array> + includeGuides?: Array> + } + + const learningTracks: LearningTrack[] = (page.learningTracks || []).map( + (track: Record) => ({ + title: (track.title as string) || '', + description: (track.description as string) || '', + trackName: (track.trackName as string) || '', + trackProduct: (track.trackProduct as string) || '', + guides: ((track.guides as Array>) || []).map( + (guide: Record) => ({ + title: (guide.title as string) || '', + intro: (guide.intro as string) || '', + href: (guide.href as string) || '', + page: guide.page as { type: string } | undefined, + }), + ), }), - })) + ) return { ...pick(page, ['title', 'intro']), + title: page.title || '', + intro: page.intro || '', learningTracks, - includeGuides: (page.includeGuides || []).map((guide: any) => { + includeGuides: (page.includeGuides || []).map((guide: Record) => { return { - ...pick(guide, ['href', 'title', 'intro']), - type: guide.type || '', - topics: guide.topics || [], + href: (guide.href as string) || '', + title: (guide.title as string) || '', + intro: (guide.intro as string) || '', + type: (guide.type as string) || '', + topics: (guide.topics as Array) || [], } }), } diff --git a/src/languages/scripts/count-translation-corruptions.ts b/src/languages/scripts/count-translation-corruptions.ts index 3f0bf17e40..a46a1bc2ed 100644 --- a/src/languages/scripts/count-translation-corruptions.ts +++ b/src/languages/scripts/count-translation-corruptions.ts @@ -77,10 +77,10 @@ function run(languageCode: string, site: Site, englishReusables: Reusables) { const illegalTags = new Map() function countError(error: TokenizationError, where: string) { - const originalError = (error as any).originalError + const originalError = (error as { originalError?: Error }).originalError const errorString = originalError ? originalError.message : error.message if (errorString.includes('illegal tag syntax')) { - const illegalTag = (error as any).token.content + const illegalTag = (error as unknown as { token: { content: string } }).token.content illegalTags.set(illegalTag, (illegalTags.get(illegalTag) || 0) + 1) } errors.set(errorString, (errors.get(errorString) || 0) + 1) diff --git a/src/learning-track/lib/types.ts b/src/learning-track/lib/types.ts index 2547d6db81..60ab19d011 100644 --- a/src/learning-track/lib/types.ts +++ b/src/learning-track/lib/types.ts @@ -2,18 +2,10 @@ * Common types used across learning track components */ -/** - * Basic context interface for rendering operations - */ -export interface Context { - currentProduct?: string - currentLanguage?: string - currentVersion?: string - pages?: any - redirects?: any - // Additional properties that may be needed for rendering - [key: string]: any -} +import type { Context, Page as MainPage } from '@/types' + +// Re-export Context from main types to avoid duplicate definitions +export type { Context } /** * Options for retrieving link data @@ -49,11 +41,9 @@ export interface PageFeaturedLinks { /** * Page interface for basic page properties + * Using the main Page type from @/types */ -export interface Page { - renderTitle: (context: Context, opts: any) => Promise - renderProp: (prop: string, context: Context, opts: any) => Promise -} +export type Page = MainPage /** * Guide in a learning track @@ -83,7 +73,7 @@ export interface LearningTrackMetadata { title: string description: string guides: string[] - versions?: any + versions?: unknown } /** diff --git a/src/observability/tests/failbot.ts b/src/observability/tests/failbot.ts index cbfc873483..8a3ddaaf61 100644 --- a/src/observability/tests/failbot.ts +++ b/src/observability/tests/failbot.ts @@ -4,7 +4,7 @@ import nock from 'nock' import FailBot from '../lib/failbot' describe('FailBot', () => { - const requestBodiesSent: any[] = [] + const requestBodiesSent: unknown[] = [] beforeEach(() => { delete process.env.HAYSTACK_URL diff --git a/src/rest/api/anchor-redirect.ts b/src/rest/api/anchor-redirect.ts index e29a72b056..59d1c8c288 100644 --- a/src/rest/api/anchor-redirect.ts +++ b/src/rest/api/anchor-redirect.ts @@ -1,4 +1,4 @@ -import express from 'express' +import express, { RequestHandler } from 'express' import path from 'path' import { readCompressedJsonFileFallbackLazily } from '@/frame/lib/read-json-file' @@ -12,20 +12,22 @@ const clientSideRestAPIRedirects = readCompressedJsonFileFallbackLazily( const router = express.Router() // Returns a client side redirect if one exists for the given path. -// Note: Using 'any' for req/res because Express types are complex and the -// function signature is constrained by the router.get() overloads -router.get('/', function redirects(req: any, res: any) { +const redirects: RequestHandler = (req, res) => { if (!req.query.path) { - return res.status(400).send("Missing 'path' query string") + res.status(400).send("Missing 'path' query string") + return } if (!req.query.hash) { - return res.status(400).send("Missing 'hash' query string") + res.status(400).send("Missing 'hash' query string") + return } defaultCacheControl(res) const redirectFrom: string = `${req.query.path}#${req.query.hash}` res.status(200).send({ to: clientSideRestAPIRedirects()[redirectFrom] }) -}) +} + +router.get('/', redirects) export default router diff --git a/src/rest/scripts/openapi-check.ts b/src/rest/scripts/openapi-check.ts index d9efa87ec3..6b08012f0d 100755 --- a/src/rest/scripts/openapi-check.ts +++ b/src/rest/scripts/openapi-check.ts @@ -8,7 +8,7 @@ import fs from 'fs' import path from 'path' import { globSync } from 'glob' import { program } from 'commander' -import { createOperations, processOperations } from './utils/get-operations' +import { createOperations, processOperations, type SchemaInput } from './utils/get-operations' interface ProgramOptions { files: string[] @@ -35,15 +35,15 @@ if (filesToCheck.length) { async function check(files: string[]): Promise { console.log('Verifying OpenAPI files are valid with decorator') - const documents: [string, any][] = files.map((filename: string) => [ + const documents: [string, unknown][] = files.map((filename: string) => [ filename, JSON.parse(fs.readFileSync(path.join(filename), 'utf8')), ]) - for (const [filename, schema] of documents as [string, any][]) { + for (const [filename, schema] of documents as [string, unknown][]) { try { // munge OpenAPI definitions object in an array of operations objects - const operations = await createOperations(schema) + const operations = await createOperations(schema as SchemaInput) // process each operation, asynchronously rendering markdown and stuff await processOperations(operations, {}) diff --git a/src/rest/scripts/utils/get-body-params.ts b/src/rest/scripts/utils/get-body-params.ts index e5ca3030b9..72528ad5f7 100644 --- a/src/rest/scripts/utils/get-body-params.ts +++ b/src/rest/scripts/utils/get-body-params.ts @@ -1,18 +1,18 @@ import { renderContent } from './render-content' -interface Schema { - oneOf?: any[] +export interface Schema { + oneOf?: Schema[] type?: string - items?: any - properties?: Record + items?: Schema + properties?: Record required?: string[] - additionalProperties?: any + additionalProperties?: Schema description?: string enum?: string[] nullable?: boolean - allOf?: any[] - anyOf?: any[] - [key: string]: any + allOf?: Schema[] + anyOf?: Schema[] + [key: string]: unknown } export interface TransformedParam { @@ -24,7 +24,7 @@ export interface TransformedParam { childParamsGroups?: TransformedParam[] enum?: string[] oneOfObject?: boolean - default?: any + default?: unknown } interface BodyParamProps { @@ -43,7 +43,7 @@ interface BodyParamProps { // operations have a top-level oneOf. async function getTopLevelOneOfProperty( schema: Schema, -): Promise<{ properties: Record; required: string[] }> { +): Promise<{ properties: Record; required: string[] }> { if (!schema.oneOf) { throw new Error('Schema does not have a requestBody oneOf property defined') } @@ -65,10 +65,14 @@ async function getTopLevelOneOfProperty( // This merges all of the properties and required values. if (allOneOfAreObjects) { for (const each of schema.oneOf.slice(1)) { - Object.assign(firstOneOfObject.properties, each.properties) - required = firstOneOfObject.required.concat(each.required) + if (firstOneOfObject.properties && each.properties) { + Object.assign(firstOneOfObject.properties, each.properties) + } + if (firstOneOfObject.required && each.required) { + required = firstOneOfObject.required.concat(each.required) + } } - properties = firstOneOfObject.properties + properties = firstOneOfObject.properties || {} } return { properties, required } } @@ -79,7 +83,7 @@ async function handleObjectOnlyOneOf( param: Schema, paramType: string[], ): Promise { - if (param.oneOf && param.oneOf.every((object: TransformedParam) => object.type === 'object')) { + if (param.oneOf && param.oneOf.every((object: Schema) => object.type === 'object')) { paramType.push('object') param.oneOfObject = true return await getOneOfChildParams(param) @@ -97,6 +101,9 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise t !== undefined, + ) const additionalPropertiesType = param.additionalProperties - ? Array.isArray(param.additionalProperties.type) - ? param.additionalProperties.type - : [param.additionalProperties.type] + ? (Array.isArray(param.additionalProperties.type) + ? param.additionalProperties.type + : [param.additionalProperties.type] + ).filter((t): t is string => t !== undefined) : [] const childParamsGroups: TransformedParam[] = [] @@ -142,7 +152,6 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise object.type === 'object')) { + if (param.items.oneOf.every((object: Schema) => object.type === 'object')) { paramType.splice(paramType.indexOf('array'), 1, `array of objects`) param.oneOfObject = true childParamsGroups.push(...(await getOneOfChildParams(param.items))) @@ -169,7 +178,7 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise `${lang}`).join(', ')}` + }Supported values are: ${((param.items.enum || []) as string[]).map((lang: string) => `${lang}`).join(', ')}` } } } else if (paramType.includes('object')) { @@ -183,7 +192,7 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise 0) { childParamsGroups.push(...oneOfChildren) } else { @@ -193,19 +202,25 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise { for (const oneOfParam of param.oneOf) { const objParam: TransformedParam = { type: 'object', - name: oneOfParam.title, - description: await renderContent(oneOfParam.description), - isRequired: oneOfParam.required, + name: (oneOfParam.title as string) || '', + description: await renderContent((oneOfParam.description as string) || ''), childParamsGroups: [], } if (objParam.childParamsGroups) { diff --git a/src/rest/scripts/utils/get-operations.ts b/src/rest/scripts/utils/get-operations.ts index 705e07832e..4edead3712 100644 --- a/src/rest/scripts/utils/get-operations.ts +++ b/src/rest/scripts/utils/get-operations.ts @@ -4,7 +4,7 @@ interface ProgAccessData { [key: string]: any } -interface SchemaInput { +export interface SchemaInput { paths?: { [requestPath: string]: { [verb: string]: any diff --git a/src/webhooks/tests/oneof-handling.ts b/src/webhooks/tests/oneof-handling.ts index baca4038f2..a04cf94482 100644 --- a/src/webhooks/tests/oneof-handling.ts +++ b/src/webhooks/tests/oneof-handling.ts @@ -1,5 +1,9 @@ import { describe, expect, test } from 'vitest' -import { getBodyParams, type TransformedParam } from '../../rest/scripts/utils/get-body-params' +import { + getBodyParams, + type TransformedParam, + type Schema, +} from '../../rest/scripts/utils/get-body-params' describe('oneOf handling in webhook parameters', () => { test('should handle oneOf fields correctly for secret_scanning_alert_location details', async () => { @@ -122,7 +126,7 @@ describe('oneOf handling in webhook parameters', () => { }, } - const result: TransformedParam[] = await getBodyParams(mockSchema, true) + const result: TransformedParam[] = await getBodyParams(mockSchema as unknown as Schema, true) // Find the location parameter const locationParam: TransformedParam | undefined = result.find( @@ -205,7 +209,10 @@ describe('oneOf handling in webhook parameters', () => { }, } - const result: TransformedParam[] = await getBodyParams(mockSchemaWithoutTitles, true) + const result: TransformedParam[] = await getBodyParams( + mockSchemaWithoutTitles as unknown as Schema, + true, + ) const detailsParam: TransformedParam | undefined = result.find( (param) => param.name === 'details', @@ -260,7 +267,10 @@ describe('oneOf handling in webhook parameters', () => { }, } - const result: TransformedParam[] = await getBodyParams(mockNestedOneOfSchema, true) + const result: TransformedParam[] = await getBodyParams( + mockNestedOneOfSchema as unknown as Schema, + true, + ) const wrapperParam: TransformedParam | undefined = result.find( (param) => param.name === 'wrapper', diff --git a/src/workflows/ready-for-docs-review.ts b/src/workflows/ready-for-docs-review.ts index 3fa626f748..b3d39b60ac 100644 --- a/src/workflows/ready-for-docs-review.ts +++ b/src/workflows/ready-for-docs-review.ts @@ -17,28 +17,34 @@ import { * @param data GraphQL response data containing PR information * @returns Object with isCopilotAuthor boolean and copilotAssignee string */ -function getCopilotAuthorInfo(data: Record): { +function getCopilotAuthorInfo(data: Record): { isCopilotAuthor: boolean copilotAssignee: string } { + const item = data.item as Record + const author = item.author as Record | undefined + const assigneesObj = item.assignees as Record | undefined + // Check if this is a Copilot-authored PR - const isCopilotAuthor = - data.item.__typename === 'PullRequest' && - data.item.author && - data.item.author.login === 'copilot-swe-agent' + const isCopilotAuthor = !!( + item.__typename === 'PullRequest' && + author && + author.login === 'copilot-swe-agent' + ) // For Copilot PRs, find the appropriate assignee (excluding Copilot itself) let copilotAssignee = '' - if (isCopilotAuthor && data.item.assignees && data.item.assignees.nodes) { - const assignees = data.item.assignees.nodes - .map((assignee: Record) => assignee.login) + if (isCopilotAuthor && assigneesObj && assigneesObj.nodes) { + const nodes = assigneesObj.nodes as Array> + const assigneeLogins = nodes + .map((assignee: Record) => assignee.login as string) .filter((login: string) => login !== 'copilot-swe-agent') // Use the first non-Copilot assignee - copilotAssignee = assignees.length > 0 ? assignees[0] : '' + copilotAssignee = assigneeLogins.length > 0 ? assigneeLogins[0] : '' } - return { isCopilotAuthor, copilotAssignee } + return { isCopilotAuthor, copilotAssignee: copilotAssignee || '' } } /** @@ -66,7 +72,7 @@ function getAuthorFieldValue( async function run() { // Get info about the docs-content review board project - const data: Record = await graphql( + const data: Record = await graphql( ` query ($organization: String!, $projectNumber: Int!, $id: ID!) { organization(login: $organization) { @@ -123,7 +129,9 @@ async function run() { ) // Get the project ID - const projectID = data.organization.projectV2.id + const organization = data.organization as Record + const projectV2 = organization.projectV2 as Record + const projectID = projectV2.id as string // Get the ID of the fields that we want to populate const datePostedID = findFieldID('Date posted', data) @@ -152,7 +160,7 @@ async function run() { // If yes, set the author to 'first time contributor' instead of to the author login let firstTimeContributor if (process.env.REPO === 'github/docs') { - const contributorData: Record = await graphql( + const contributorData: Record = await graphql( ` query ($author: String!) { user(login: $author) { @@ -184,17 +192,30 @@ async function run() { }, }, ) - const docsPRData = - contributorData.user.contributionsCollection.pullRequestContributionsByRepository.filter( - (item: Record) => item.repository.nameWithOwner === 'github/docs', - )[0] - const prCount = docsPRData ? docsPRData.contributions.totalCount : 0 + const user = contributorData.user as Record + const contributionsCollection = user.contributionsCollection as Record + const pullRequestContributions = + contributionsCollection.pullRequestContributionsByRepository as Array> + const docsPRData = pullRequestContributions.filter((item: Record) => { + const repository = item.repository as Record + return repository.nameWithOwner === 'github/docs' + })[0] + const prContributions = docsPRData + ? (docsPRData.contributions as Record) + : undefined + const prCount = prContributions ? (prContributions.totalCount as number) : 0 - const docsIssueData = - contributorData.user.contributionsCollection.issueContributionsByRepository.filter( - (item: Record) => item.repository.nameWithOwner === 'github/docs', - )[0] - const issueCount = docsIssueData ? docsIssueData.contributions.totalCount : 0 + const issueContributions = contributionsCollection.issueContributionsByRepository as Array< + Record + > + const docsIssueData = issueContributions.filter((item: Record) => { + const repository = item.repository as Record + return repository.nameWithOwner === 'github/docs' + })[0] + const issueContributionsObj = docsIssueData + ? (docsIssueData.contributions as Record) + : undefined + const issueCount = issueContributionsObj ? (issueContributionsObj.totalCount as number) : 0 if (prCount + issueCount <= 1) { firstTimeContributor = true