From bffccf502cafa02cdf18f9d987dac52586b2620c Mon Sep 17 00:00:00 2001 From: Quentin Pradet Date: Fri, 26 Sep 2025 10:58:42 +0400 Subject: [PATCH] Validate all APIs in PRs (#5341) * Validate all APIs in PRs The current code tries to guess which APIs could be affected by the PR, but it does miss things. Now that full validation takes two minutes, we can always use it. * Fix lint * Fix working directory before calling glob * Fix detection of global APIs (cherry picked from commit 4516b791deed36817c2081f44ea621ae84fcc557) # Conflicts: # .github/validate-pr/index.js --- .github/validate-pr/index.js | 85 ++++++++++++------------------------ specification/_types/Base.ts | 5 +++ 2 files changed, 34 insertions(+), 56 deletions(-) diff --git a/.github/validate-pr/index.js b/.github/validate-pr/index.js index 331d5bc8a5..1ce3dd5562 100644 --- a/.github/validate-pr/index.js +++ b/.github/validate-pr/index.js @@ -22,10 +22,8 @@ import { dirname } from 'path' import { fileURLToPath } from 'url' import 'zx/globals' -import assert from 'assert' import * as core from '@actions/core' import { copyFile } from 'fs/promises' -import * as github from '@actions/github' import specification from '../../output/schema/schema.json' with { type: 'json' } import baselineValidation from '../../../clients-flight-recorder/recordings/types-validation/types-validation.json' with { type: 'json' } import { run as getReport } from '../../../clients-flight-recorder/scripts/types-validator/index.js' @@ -36,7 +34,6 @@ import { const __dirname = dirname(fileURLToPath(import.meta.url)) -const octokit = github.getOctokit(argv.token) const privateNames = ['_global'] const tick = '`' @@ -55,46 +52,16 @@ async function run() { path.join(__dirname, '..', '..', 'output', 'typescript', 'types.ts'), path.join(tsValidationPath, 'types.ts') ) - const context = github.context - assert(context.payload.pull_request, 'We should be in a PR context') - const files = [] - let page = 1 - while (true) { - const { data } = await octokit.rest.pulls.listFiles({ - owner: 'elastic', - repo: 'elasticsearch-specification', - pull_number: context.payload.pull_request.number, - page, - per_page: 100 - }) - if (data.length > 0) { - files.push( - ...data - .filter((entry) => entry.status !== 'deleted') - .map((entry) => entry.filename) - ) - page += 1 - } else { - break - } - } - - const specFiles = files.filter( - (file) => file.includes('specification') && !file.includes('compiler/test') - ) const reports = new Map() - cd(tsValidationPath) // Collect all APIs to validate const apisToValidate = new Set() - for (const file of specFiles) { + cd(path.join(__dirname, '..', '..')) + for (const file of await glob('specification/**/*.ts')) { if (file.startsWith('specification/_types')) continue - if (file.startsWith('specification/_spec_utils')) continue - if (file.startsWith('specification/_doc_ids')) continue - if (file.startsWith('specification/_json_spec')) continue - if (file === 'specification/tsconfig.json') continue + if (file.startsWith('specification/node_modules')) continue if (getApi(file).endsWith('_types')) { const apis = specification.endpoints .filter(endpoint => endpoint.name.split('.').filter(s => !privateNames.includes(s))[0] === getApi(file).split('.')[0]) @@ -108,26 +75,27 @@ async function run() { } } + cd(tsValidationPath) + console.log(`Validating ${apisToValidate.size} APIs...`) + // Call getReport once with all APIs - if (apisToValidate.size > 0) { - const allApis = Array.from(apisToValidate).join(',') - const report = await getReport({ - api: allApis, - 'generate-report': false, - request: true, - response: true, - ci: false, - verbose: false - }) - - // Extract individual API reports from the combined result - for (const api of apisToValidate) { - const namespace = getNamespace(api) - if (report.has(namespace)) { - const namespaceReport = report.get(namespace).find(r => r.api === getName(api)) - if (namespaceReport) { - reports.set(api, namespaceReport) - } + const allApis = Array.from(apisToValidate).join(',') + const report = await getReport({ + api: allApis, + 'generate-report': false, + request: true, + response: true, + ci: false, + verbose: false + }) + + // Extract individual API reports from the combined result + for (const api of apisToValidate) { + const namespace = getNamespace(api) + if (report.has(namespace)) { + const namespaceReport = report.get(namespace).find(r => r.api === getName(api)) + if (namespaceReport) { + reports.set(api, namespaceReport) } } } @@ -165,7 +133,12 @@ function getApi (file) { } function findBaselineReport(apiName, baselineValidation) { - const [namespace, method] = apiName.split('.') + let namespace, method = [null, null] + if (!apiName.includes('.')) { + [namespace, method] = ['global', apiName] + } else { + [namespace, method] = apiName.split('.') + } if (!baselineValidation.namespaces[namespace]) { return null diff --git a/specification/_types/Base.ts b/specification/_types/Base.ts index fd67514614..912913d689 100644 --- a/specification/_types/Base.ts +++ b/specification/_types/Base.ts @@ -17,6 +17,7 @@ * under the License. */ +import { FailureStoreStatus } from '@global/bulk/types' import { CommonQueryParameters } from '@spec_utils/behaviors' import { Id, @@ -63,6 +64,10 @@ export class WriteResponseBase { * The document version, which is incremented each time the document is updated. */ _version: VersionNumber + /** + * The role of the failure store in this document response + */ + failure_store?: FailureStoreStatus forced_refresh?: boolean }