From 62175a10fcc0ea7682136faf29e4c7a91af1c848 Mon Sep 17 00:00:00 2001 From: Thomas Berdy Date: Tue, 4 Aug 2026 19:08:46 +0200 Subject: [PATCH] feat(docs): document the batch status data type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A batch's `status.code` appeared on the API reference as a bare list of values with no explanation, and nowhere else in the docs. Readers seeing a status in an API response, in the CLI, or on a dashboard chip had no way to find out what it meant. Add a Batch Status section to the data-types page, rendering a table generated from the OpenAPI spec so the codes and their descriptions come from the engine rather than being hand-maintained, and cross-reference it from the monitoring and batches pages. The table goes through the shared reader added earlier, so it already handles the shapes a synced schema can arrive in — a `$ref` to a hoisted component in particular, which the engine's enum deduplication produces. Extend the anchor build gate to scan the API spec alongside the configuration schema. This is the first data type marked in the API spec, and schema syncs land as direct pushes to main, so the deploy build is the only gate they pass through. The bundled schemas are what the sync bot will deliver once the engine side merges. They carry the per-value documentation as `x-mergify-enum`, a positional array aligned with `enum` holding a description, an optional display title and a deprecation flag, and the data-type marker under its namespaced spelling. `x-enum-descriptions` is gone: that name is an established openapi-generator convention for a positional array of strings, and publishing a map under it would mislead the SDK generators customers run against our spec. Part of MRGFY-8330 Co-Authored-By: Claude Opus 5 (1M context) Change-Id: Ic5cd08016af10408a80c0d9f72b86dd61abc0a84 --- integrations/validate-data-type-anchors.ts | 30 ++++++++---- src/components/Tables/BatchStatusCodes.tsx | 47 +++++++++++++++++++ src/content/docs/configuration/data-types.mdx | 15 ++++++ src/content/docs/merge-queue/batches.mdx | 5 ++ src/content/docs/merge-queue/monitoring.mdx | 4 ++ src/util/dataType.test.ts | 11 +++++ src/util/enumChoices.test.ts | 35 ++++++++++++++ 7 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 src/components/Tables/BatchStatusCodes.tsx diff --git a/integrations/validate-data-type-anchors.ts b/integrations/validate-data-type-anchors.ts index 0de789aa11..e61dd0d6ad 100644 --- a/integrations/validate-data-type-anchors.ts +++ b/integrations/validate-data-type-anchors.ts @@ -1,9 +1,19 @@ import type { AstroIntegration } from 'astro'; +import rawApiSchema from '../public/api-schemas.json'; import rawConfigSchema from '../public/mergify-configuration-schema.json'; import { missingDataTypeAnchors } from '../src/util/dataTypeAnchors'; +// Both synced schemas can carry the engine's `x-has-data-type` marker: the +// configuration schema for types you write in `.mergify.yml`, the OpenAPI spec +// for types the API only reports (a batch status, say). Both arrive by the same +// bot sync, so both need the same gate. +const SCHEMAS: { file: string; schema: unknown }[] = [ + { file: 'public/mergify-configuration-schema.json', schema: rawConfigSchema }, + { file: 'public/api-schemas.json', schema: rawApiSchema }, +]; + /** - * Fail the build when the config schema flags a documented data type whose + * Fail the build when a synced schema flags a documented data type whose * derived anchor (slugified `title`) has no matching heading on the * data-types page. This is the enforcement point that actually guards the * drift path: schema syncs land as direct bot pushes to main (no PR, so no @@ -16,14 +26,18 @@ export function validateDataTypeAnchors(): AstroIntegration { name: 'validate-data-type-anchors', hooks: { 'astro:build:start': () => { - const missing = missingDataTypeAnchors(rawConfigSchema); - if (missing.length > 0) { + const problems = SCHEMAS.flatMap(({ file, schema }) => { + const missing = missingDataTypeAnchors(schema); + return missing.length > 0 ? [`${file}: ${missing.join(', ')}`] : []; + }); + + if (problems.length > 0) { throw new Error( - `Documented data type(s) in public/mergify-configuration-schema.json have no ` + - `matching heading anchor on src/content/docs/configuration/data-types.mdx: ` + - `${missing.join(', ')}. A marked node's slugified title must equal the anchor ` + - `of its section heading (add the missing section, or fix the title next to the ` + - `engine's DocsDataType annotation).` + `Documented data type(s) in a synced schema have no matching heading anchor on ` + + `src/content/docs/configuration/data-types.mdx — ${problems.join('; ')}. ` + + `A marked node's slugified title must equal the anchor of its section heading ` + + `(add the missing section, or fix the title next to the engine's DocsDataType ` + + `annotation).` ); } }, diff --git a/src/components/Tables/BatchStatusCodes.tsx b/src/components/Tables/BatchStatusCodes.tsx new file mode 100644 index 0000000000..72b4b861d8 --- /dev/null +++ b/src/components/Tables/BatchStatusCodes.tsx @@ -0,0 +1,47 @@ +import apiSchema from '../../../public/api-schemas.json'; +import { readEnumChoices } from '../../util/enumChoices'; + +import { renderMarkdown } from './utils'; + +// A batch's `status.code` in the merge queue API. The engine is the single +// source of truth: the codes and their one-line descriptions are published +// alongside the schema, so this table can't drift from what the API returns. +// Sourced from the OpenAPI spec rather than the configuration schema because a +// batch status is something the API reports, never something you write in +// `.mergify.yml`. +// +// `readEnumChoices` resolves a `$ref` and understands every documentation +// shape the engine currently publishes, so this keeps rendering whether the +// property is inline or hoisted into a shared component. +const codeProp: unknown = ( + apiSchema as { + components?: { schemas?: Record }> }; + } +).components?.schemas?.BatchStatus?.properties?.code; + +export default function BatchStatusCodes() { + const choices = readEnumChoices(apiSchema, codeProp); + + return ( +
+ + + + + + + + + {choices.map((choice) => ( + + + + ))} + +
StatusDescription
+ {choice.value} + +
+
+ ); +} diff --git a/src/content/docs/configuration/data-types.mdx b/src/content/docs/configuration/data-types.mdx index 27fc2d1564..267a28bc1c 100644 --- a/src/content/docs/configuration/data-types.mdx +++ b/src/content/docs/configuration/data-types.mdx @@ -3,6 +3,7 @@ title: Configuration Data Types description: The different data types you can find in Mergify configuration file --- +import BatchStatusCodes from '../../../components/Tables/BatchStatusCodes'; import OptionsTable from '../../../components/Tables/OptionsTable'; import QueueDequeueReasons from '../../../components/Tables/QueueDequeueReasons'; import TemplateVariablesTable from '../../../components/Tables/TemplateVariablesTable'; @@ -494,6 +495,20 @@ The following reasons can be reported: +## Batch Status + +This describes what a [batch](/merge-queue/batches) is currently doing in the +merge queue. It is reported as the `status.code` field of each batch returned by +the [merge queue status API](/api/merge-queue), and the dashboard shows it on +each batch. + +A status describes the batch as a whole, not an individual pull request: a batch +that is running its checks reports `running` for every pull request it carries. + +The following statuses can be reported: + + + ## Report Mode Report modes allow you to choose the type of report you want for your actions. diff --git a/src/content/docs/merge-queue/batches.mdx b/src/content/docs/merge-queue/batches.mdx index 75df5e21cf..cfa087d6bb 100644 --- a/src/content/docs/merge-queue/batches.mdx +++ b/src/content/docs/merge-queue/batches.mdx @@ -379,6 +379,11 @@ Note that this system is completely automatic and there is no need to intervene. The number of maximum splits can be controlled by [`batch_max_failure_resolution_attempts`](/configuration/file-format#queue-rules). +While this runs, the batches involved report a +[batch status](/configuration/data-types#batch-status) such as `bisecting`, +`waiting_for_previous_batches`, or `waiting_for_requeue`, so you can follow the +resolution from [the dashboard or the CLI](/merge-queue/monitoring). + :::tip Each split carries metadata about the batches it came from. You can use it to [re-run only the tests that failed in the parent diff --git a/src/content/docs/merge-queue/monitoring.mdx b/src/content/docs/merge-queue/monitoring.mdx index 67e3600084..05a3f02312 100644 --- a/src/content/docs/merge-queue/monitoring.mdx +++ b/src/content/docs/merge-queue/monitoring.mdx @@ -126,6 +126,10 @@ This displays: - **Waiting PRs**: queued pull requests with priority, queue time, and estimated merge time +Each batch reports a [batch status](/configuration/data-types#batch-status) +describing what it is doing, such as running its checks or waiting for a +schedule. + To filter results to a specific branch: ```bash diff --git a/src/util/dataType.test.ts b/src/util/dataType.test.ts index 8120b11e67..4eb7cc7e1d 100644 --- a/src/util/dataType.test.ts +++ b/src/util/dataType.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; +import apiSchema from '../../public/api-schemas.json'; import configSchema from '../../public/mergify-configuration-schema.json'; import { collectDataTypeTitles, getDataTypeHref, isDataType } from './dataType'; import { dataTypesHeadingAnchors, missingDataTypeAnchors } from './dataTypeAnchors'; @@ -69,6 +70,8 @@ describe('data-types page anchors', () => { 'priority', 'report-mode', 'schedule', + // marked in the OpenAPI spec rather than the configuration schema + 'batch-status', // anchors hardcoded in ConfigOptions.tsx link maps 'commit', 'commit-author', @@ -95,4 +98,12 @@ describe('data-types page anchors', () => { it('covers every documented data type flagged in the config schema', () => { expect(missingDataTypeAnchors(configSchema)).toEqual([]); }); + + // The same convention, for types the engine marks in the OpenAPI spec + // instead — a data type the API reports but you never write in + // `.mergify.yml`. Both schemas arrive by the same bot sync, so both are + // gated in integrations/validate-data-type-anchors.ts. + it('covers every documented data type flagged in the API schema', () => { + expect(missingDataTypeAnchors(apiSchema)).toEqual([]); + }); }); diff --git a/src/util/enumChoices.test.ts b/src/util/enumChoices.test.ts index e3a6f57973..28071ffc75 100644 --- a/src/util/enumChoices.test.ts +++ b/src/util/enumChoices.test.ts @@ -1,4 +1,6 @@ import { describe, expect, it } from 'vitest'; +import apiSchema from '../../public/api-schemas.json'; +import configSchema from '../../public/mergify-configuration-schema.json'; import { readEnumChoices, resolveRef } from './enumChoices'; // This reader spans an engine-side migration, so each shape it has to survive @@ -171,3 +173,36 @@ describe('resolveRef', () => { expect(resolveRef({}, { enum: ['x'] })).toEqual({ enum: ['x'] }); }); }); + +// The cases above are synthetic. These bind the reader to the two schemas the +// site actually renders, so a sync that changes shape — or reverts one — fails +// here instead of silently publishing a table of blank cells. +function at(root: unknown, ...path: string[]): unknown { + let current = root; + for (const key of path) { + current = (current as Record | undefined)?.[key]; + } + return current; +} + +describe('the real synced schemas', () => { + it('documents every batch status code in the API schema', () => { + const code = at(apiSchema, 'components', 'schemas', 'BatchStatus', 'properties', 'code'); + const choices = readEnumChoices(apiSchema, code); + expect(choices.length).toBeGreaterThan(0); + expect(choices.filter((c) => c.description.trim() === '')).toEqual([]); + }); + + it('documents every dequeue reason in the configuration schema', () => { + const reason = at( + configSchema, + '$defs', + 'PullRequestAttributes', + 'properties', + 'queue-dequeue-reason' + ); + const choices = readEnumChoices(configSchema, reason); + expect(choices.length).toBeGreaterThan(0); + expect(choices.filter((c) => c.description.trim() === '')).toEqual([]); + }); +});