Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions integrations/validate-data-type-anchors.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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).`
);
}
},
Expand Down
47 changes: 47 additions & 0 deletions src/components/Tables/BatchStatusCodes.tsx
Original file line number Diff line number Diff line change
@@ -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<string, { properties?: Record<string, unknown> }> };
}
).components?.schemas?.BatchStatus?.properties?.code;

export default function BatchStatusCodes() {
const choices = readEnumChoices(apiSchema, codeProp);

return (
<div className="table-wrap">
<table>
<thead>
<tr>
<th>Status</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{choices.map((choice) => (
<tr key={choice.value}>
<td>
<code>{choice.value}</code>
</td>
<td dangerouslySetInnerHTML={{ __html: renderMarkdown(choice.description) }} />
</tr>
))}
</tbody>
</table>
</div>
);
}
15 changes: 15 additions & 0 deletions src/content/docs/configuration/data-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -494,6 +495,20 @@ The following reasons can be reported:

<QueueDequeueReasons />

## 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:

<BatchStatusCodes />

## Report Mode

Report modes allow you to choose the type of report you want for your actions.
Expand Down
5 changes: 5 additions & 0 deletions src/content/docs/merge-queue/batches.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/content/docs/merge-queue/monitoring.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/util/dataType.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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',
Expand All @@ -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([]);
});
});
35 changes: 35 additions & 0 deletions src/util/enumChoices.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<string, unknown> | 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([]);
});
});
Loading