1
0
mirror of synced 2025-12-19 09:57:42 -05:00

Remove 'any' types from 14 files (#58716)

This commit is contained in:
Kevin Heis
2025-12-10 08:07:04 -08:00
committed by GitHub
parent 21eb8177cd
commit 13178f0183
16 changed files with 207 additions and 133 deletions

View File

@@ -29,7 +29,7 @@ async function main() {
includeBasePath: true,
globs: ['**/*.md'],
})
const cliMarkdownContents: Record<string, { data: any; content: string }> = {}
const cliMarkdownContents: Record<string, { data: Record<string, unknown>; content: string }> = {}
for (const file of markdownFiles) {
const sourceContent = await readFile(file, 'utf8')

View File

@@ -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}`
}

View File

@@ -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,

View File

@@ -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.`

View File

@@ -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/)
})
})

View File

@@ -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')
})

View File

@@ -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<Record<string, unknown>>
includeGuides?: Array<Record<string, unknown>>
}
const learningTracks: LearningTrack[] = (page.learningTracks || []).map(
(track: Record<string, unknown>) => ({
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<Record<string, unknown>>) || []).map(
(guide: Record<string, unknown>) => ({
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<string, unknown>) => {
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<string>) || [],
}
}),
}

View File

@@ -77,10 +77,10 @@ function run(languageCode: string, site: Site, englishReusables: Reusables) {
const illegalTags = new Map<string, number>()
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)

View File

@@ -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<string>
renderProp: (prop: string, context: Context, opts: any) => Promise<string>
}
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
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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<void> {
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, {})

View File

@@ -1,18 +1,18 @@
import { renderContent } from './render-content'
interface Schema {
oneOf?: any[]
export interface Schema {
oneOf?: Schema[]
type?: string
items?: any
properties?: Record<string, any>
items?: Schema
properties?: Record<string, Schema>
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<string, any>; required: string[] }> {
): Promise<{ properties: Record<string, Schema>; 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<TransformedParam[]> {
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
// there will not be properties on the `schema` object.
if (topLevel && schema.type === 'array') {
const childParamsGroups: TransformedParam[] = []
if (!schema.items) {
throw new Error('Array schema must have items property')
}
const arrayType = schema.items.type
const paramType = [schema.type]
if (arrayType === 'object') {
@@ -118,11 +125,14 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise<T
// This makes type an array regardless of how many values the array
// includes. This allows us to support 3.1 while remaining backwards
// compatible with 3.0.
const paramType = Array.isArray(param.type) ? param.type : [param.type]
const paramType = (Array.isArray(param.type) ? param.type : [param.type]).filter(
(t): t is string => 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<T
description: await renderContent(
`A user-defined key to represent an item in \`${paramKey}\`.`,
),
isRequired: param.required,
enum: param.enum,
default: param.default,
childParamsGroups: [],
@@ -153,7 +162,7 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise<T
childParamsGroups.push(keyParam)
} else if (paramType.includes('array') && param.items) {
if (param.items.oneOf) {
if (param.items.oneOf.every((object: TransformedParam) => 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<T
if (arrayType === 'string' && param.items.enum) {
param.description += `${
param.description ? '\n' : ''
}Supported values are: ${param.items.enum.map((lang: string) => `<code>${lang}</code>`).join(', ')}`
}Supported values are: ${((param.items.enum || []) as string[]).map((lang: string) => `<code>${lang}</code>`).join(', ')}`
}
}
} else if (paramType.includes('object')) {
@@ -183,7 +192,7 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise<T
}
} else if (param.oneOf) {
// Check if all oneOf items are objects - if so, treat this as a oneOfObject case
const oneOfChildren = await handleObjectOnlyOneOf(param, paramType)
const oneOfChildren = await handleObjectOnlyOneOf(param as Schema, paramType)
if (oneOfChildren.length > 0) {
childParamsGroups.push(...oneOfChildren)
} else {
@@ -193,19 +202,25 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise<T
paramType.push(childParam.type)
if (!param.description) {
if (childParam.type === 'array') {
if (childParam.items.description) {
if (childParam.items && childParam.items.description) {
descriptions.push({
type: childParam.type,
description: childParam.items.description,
type: (childParam.type as string) || '',
description: (childParam.items?.description as string) || '',
})
}
} else {
if (childParam.description) {
descriptions.push({ type: childParam.type, description: childParam.description })
descriptions.push({
type: (childParam.type as string) || '',
description: (childParam.description as string) || '',
})
}
}
} else {
descriptions.push({ type: param.type, description: param.description })
descriptions.push({
type: (param.type as string) || '',
description: (param.description as string) || '',
})
}
}
// Occasionally, there is no parent description and the description
@@ -226,12 +241,10 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise<T
if (firstObject) {
paramType.push('object')
param.description = firstObject.description
param.isRequired = firstObject.required
childParamsGroups.push(...(await getBodyParams(firstObject, false)))
} else {
paramType.push(param.anyOf[0].type)
paramType.push(param.anyOf[0].type as string)
param.description = param.anyOf[0].description
param.isRequired = param.anyOf[0].required
}
// Used only for webhooks handling allOf
} else if (param.allOf) {
@@ -313,9 +326,8 @@ async function getOneOfChildParams(param: Schema): Promise<TransformedParam[]> {
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) {

View File

@@ -4,7 +4,7 @@ interface ProgAccessData {
[key: string]: any
}
interface SchemaInput {
export interface SchemaInput {
paths?: {
[requestPath: string]: {
[verb: string]: any

View File

@@ -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',

View File

@@ -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<string, any>): {
function getCopilotAuthorInfo(data: Record<string, unknown>): {
isCopilotAuthor: boolean
copilotAssignee: string
} {
const item = data.item as Record<string, unknown>
const author = item.author as Record<string, unknown> | undefined
const assigneesObj = item.assignees as Record<string, unknown> | 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<string, any>) => assignee.login)
if (isCopilotAuthor && assigneesObj && assigneesObj.nodes) {
const nodes = assigneesObj.nodes as Array<Record<string, unknown>>
const assigneeLogins = nodes
.map((assignee: Record<string, unknown>) => 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<string, any> = await graphql(
const data: Record<string, unknown> = 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<string, unknown>
const projectV2 = organization.projectV2 as Record<string, unknown>
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<string, any> = await graphql(
const contributorData: Record<string, unknown> = await graphql(
`
query ($author: String!) {
user(login: $author) {
@@ -184,17 +192,30 @@ async function run() {
},
},
)
const docsPRData =
contributorData.user.contributionsCollection.pullRequestContributionsByRepository.filter(
(item: Record<string, any>) => item.repository.nameWithOwner === 'github/docs',
)[0]
const prCount = docsPRData ? docsPRData.contributions.totalCount : 0
const user = contributorData.user as Record<string, unknown>
const contributionsCollection = user.contributionsCollection as Record<string, unknown>
const pullRequestContributions =
contributionsCollection.pullRequestContributionsByRepository as Array<Record<string, unknown>>
const docsPRData = pullRequestContributions.filter((item: Record<string, unknown>) => {
const repository = item.repository as Record<string, unknown>
return repository.nameWithOwner === 'github/docs'
})[0]
const prContributions = docsPRData
? (docsPRData.contributions as Record<string, unknown>)
: undefined
const prCount = prContributions ? (prContributions.totalCount as number) : 0
const docsIssueData =
contributorData.user.contributionsCollection.issueContributionsByRepository.filter(
(item: Record<string, any>) => item.repository.nameWithOwner === 'github/docs',
)[0]
const issueCount = docsIssueData ? docsIssueData.contributions.totalCount : 0
const issueContributions = contributionsCollection.issueContributionsByRepository as Array<
Record<string, unknown>
>
const docsIssueData = issueContributions.filter((item: Record<string, unknown>) => {
const repository = item.repository as Record<string, unknown>
return repository.nameWithOwner === 'github/docs'
})[0]
const issueContributionsObj = docsIssueData
? (docsIssueData.contributions as Record<string, unknown>)
: undefined
const issueCount = issueContributionsObj ? (issueContributionsObj.totalCount as number) : 0
if (prCount + issueCount <= 1) {
firstTimeContributor = true