Repository navigation
Read changelog entries in other formats, defined as data - #4528
theletterf wants to merge 6 commits into
Conversation
elastic/elasticsearch and elastic/elasticsearch-serverless write entries with summary, area, their own type names, and breaking, deprecation and highlight blocks. docs-builder rejected all 715 Elasticsearch files with "Property 'area' not found", and range bundling failed on the 772 such files already in the Elasticsearch entry pool. NormalizeYaml and DeserializeEntry now convert that format to the docs-builder schema. The files have no products: validation accepts them when products.default is set, and range bundling resolves their products from the pull request labels, then products.default, like a synthesized entry. Fixes elastic/docs-eng-team#1106 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TjZFCvQhL3DdrcjFKU6jDT
An Elasticsearch-format entry with source_repo now puts the qualified reference in prs, so validation still compares pr with the file name. The docs say that only commit-range bundles fill products for these entries. Related to elastic/docs-eng-team#1106 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TjZFCvQhL3DdrcjFKU6jDT
Docs preview (local build)Handbook preview: https://docs-v3-preview.elastic.dev/elastic/docs-builder/pull/4528/ Sample changed pages: |
Replace the hardcoded Elasticsearch conversion with entry formats defined in config/changelog-entry-formats.yml. A repository selects one with entry_format in its changelog.yml, and docs-builder converts its entry files where it reads them with that configuration: changelog validate, changelog upload (the pool then holds docs-builder entries), and commit-range bundling (for pool entries uploaded before the repository set it). Ships two formats: elasticsearch and elastic-agent-changelog-tool (beats, elastic-agent, fleet-server). Adding a format is a change to the YAML file. Related to elastic/docs-eng-team#1106 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TjZFCvQhL3DdrcjFKU6jDT
An entry format with files: any has file names that don't carry the PR, as with elastic-agent-changelog-tool fragments. changelog validate assigns such a file to the pull request that adds it. changelog upload looks up the pull request that added each file that names none and uploads the entry under that PR. Commit-range bundling matches a pool entry that names no PR to the PR in the range that added a file with the same name. Translate the Elasticsearch schema area names that the elasticsearch docs/changelog.yml spells differently. Related to elastic/docs-eng-team#1106 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TjZFCvQhL3DdrcjFKU6jDT
- Upload keeps the file name as the key for formats with files: any, so several fragments of one PR don't overwrite each other. - Upload reads entry_format even when --directory overrides the directory. - Converted entries no longer count as PR-alias markers in conflict messages. - Range bundling attributes a fragment to the PR that added it, not one that edited it, keeps looking while fragments remain unowned, and keeps file-name matching for docs-builder entries named <pr>.yaml. Related to elastic/docs-eng-team#1106 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TjZFCvQhL3DdrcjFKU6jDT
|
@Mpdreamz Ready to review |
|
@Mpdreamz Let's mothball this one till repos decide on whether to use a changelog file without following our standard, I'd say. This still means we might have to rewrite all existing .yml files though. |
ChangelogEntryValidator.cs conflicted in the Validate doc comment: this branch documents the new productsFromDefaults parameter, main adds a remarks block on products.available. Kept both; the code itself merged cleanly. Two tests added by this branch read the bundle from the top level of the output directory. Main now writes bundles in a subfolder, so they look in all directories, as the other tests in that file already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| var orphans = candidates | ||
| .Select((candidate, index) => (candidate, index)) | ||
| .Where(c => c.candidate.Entry != null && c.candidate.FileNameNumbers.Count == 0 && c.candidate.Prs.Count == 0) |
There was a problem hiding this comment.
Severity: P3 (Low)
Timestamped entries that fail parsing also need attribution by their added-file identity. ParseCandidate records a conversion failure as Entry = null, but this filter excludes it from attribution, and its timestamp cannot match the actual PR number. A PR that added an invalid pool fragment is therefore treated as having no entry and gets a synthesized note, potentially returning success instead of reporting the malformed authoritative entry.
Generated by Libra. React with 👍 or 👎 to give feedback on this comment.
| foreach (var pr in resolution.PullRequests) | ||
| { | ||
| if (orphans.Count == 0) | ||
| break; | ||
|
|
||
| // A pull request that already has a pool entry can still have added another fragment that names no PR. | ||
| var files = await addedFiles!(options.Owner, options.Repo, pr.Number, ctx); |
There was a problem hiding this comment.
Severity: P3 (Low)
If even one pool entry anywhere in the repository's pool names no PR, this loop makes a pulls/{n}/files call for every PR in the range, one after another. Orphans from older releases never match a PR in the current range, so the early break rarely fires. A release range of several hundred PRs then means several hundred sequential API calls. The PR description says this costs one call per PR without a pool entry, but the code calls for every PR.
Generated by Libra. Specialist: libra-complexity. React with 👍 or 👎 to give feedback on this comment.
| var files = await addedFiles!(options.Owner, options.Repo, pr.Number, ctx); | ||
| foreach (var name in (files ?? []).Select(f => f[(f.LastIndexOf('/') + 1)..])) |
There was a problem hiding this comment.
Severity: P3 (Low)
If addedFiles fails, it returns null, and this code treats that as "no files" without any diagnostic. The PR's orphan fragment then stays unattributed and is left out of the bundle. The PR gets an entry synthesized from its title instead, and the bundle succeeds silently with the wrong release-note text. Consider emitting a warning or error when the lookup fails.
Generated by Libra. Specialist: libra-complexity. React with 👍 or 👎 to give feedback on this comment.
| return (Deserialize(content), false); | ||
| } | ||
| catch (YamlException) when (entryFormat != null) | ||
| { |
There was a problem hiding this comment.
Severity: P3 (Low)
When a repository sets entry_format, any docs-builder pool entry with a genuine YAML error, such as a typo'd key, is retried in the repository's format. The original error is thrown away. The "could not be parsed" diagnostic then reports the format's errors instead, for example "Unknown key 'title'; Unknown key 'products' … for the 'elasticsearch' entry format", which hides the real problem. Consider keeping the first exception and including it in the message when the fallback also fails.
Generated by Libra. Specialist: libra-complexity. React with 👍 or 👎 to give feedback on this comment.
| if (config.EntryFormat is { FileNamesCarryPr: false }) | ||
| { | ||
| // The format's file names don't carry the PR (for example timestamped fragments): a file this PR adds | ||
| // belongs to this PR. | ||
| filenamePrNumbers[relPath] = input.PrNumber; |
There was a problem hiding this comment.
Severity: P2 (Medium)
Only attribute a fragment to the current PR when that PR added it, rather than assigning every changed fragment to it. File discovery includes modified files, so a PR that merely edits an older fragment—even one whose pr explicitly references another PR—now satisfies RequireChangelogFile. The presence check uses this fabricated filename-to-PR mapping, while upload and bundling retain the older PR reference, leaving the current PR without the entry that validation claimed it had.
Generated by Libra. React with 👍 or 👎 to give feedback on this comment.
| // A repository with its own entry format (entry_format in changelog.yml) has its files converted first. | ||
| var entryYaml = config.EntryFormat is { } format | ||
| ? format.ToEntryYaml(rawYaml, relPath, $"https://github.com/{input.Owner}/{input.Repo}/pull/{input.PrNumber}") | ||
| : rawYaml; |
There was a problem hiding this comment.
Severity: P2 (Medium)
Accept docs-builder entries before falling back to the configured source format, as the pool reader does. changelog add and the documented evaluate-pr/add automation still generate canonical fields such as title, products, and prs, regardless of entry_format. This unconditional conversion rejects those fields as unknown; upload does the same. Consequently, opting into an alternate format makes successfully generated changelogs impossible to validate or upload.
Generated by Libra. React with 👍 or 👎 to give feedback on this comment.
| // A format whose file names don't carry the PR keeps them as keys: one pull request can add several fragments, | ||
| // and keying them by PR number would make them overwrite each other. They match by their prs instead. | ||
| var (canonicalFileName, markerEntries) = entryFormat is { FileNamesCarryPr: false } | ||
| ? (fileName, []) | ||
| : DeriveCanonicalFileNameAndMarkers(fileName, entry, _logger); | ||
| var primaryKey = ChangelogKeys.ChangelogFileKey(org, repo, branch, canonicalFileName); | ||
| targets.Add(new UploadTarget(filePath, primaryKey)); | ||
| targets.Add(new UploadTarget(filePath, primaryKey, converted)); |
There was a problem hiding this comment.
Severity: P1 (High)
Preserving fragment filenames here does not preserve them through publication. ChangelogContentScrubber.BuildCanonicalKeyAndMarkers still renames every non-note entry with PR references to <minimum-pr>.yaml, and ScrubberProcessor overwrites that public key. Two timestamped fragments attributed to PR 4242 therefore overwrite each other at 4242.yaml, despite having distinct private upload keys. Carry the fragment identity through the scrubber as well, and cover the upload-to-publication path with a regression test.
Generated by Libra. React with 👍 or 👎 to give feedback on this comment.
| var number = await _prService.FetchPullRequestAddingFileAsync(org, repo, repoPath, branch, ctx); | ||
| if (number is { } pr) | ||
| result[filePath] = $"https://github.com/{org}/{repo}/pull/{pr}"; | ||
| else | ||
| _logger.LogWarning("No pull request found that added {File}; uploading it without one", filePath); |
There was a problem hiding this comment.
Severity: P2 (Medium)
Every upload run looks up the PR again for each fragment that names none, at two GitHub calls per file. When a lookup fails, the fragment is converted without prs. A rate limit or transient error is enough to cause this, and so is the unauthenticated upload step in docs-actions mentioned in the PR, which is limited to 60 requests an hour. Because the converted content then differs from what was uploaded before, the incremental uploader replaces the correctly attributed pool entry with one that has no PR. On a failed lookup, consider skipping the file, or keeping the existing remote object, rather than downgrading it.
Generated by Libra. Specialist: libra-complexity. React with 👍 or 👎 to give feedback on this comment.
While working out what Elasticsearch still needs for unified release notes, I found that docs-builder can't read the changelog entries Elasticsearch already writes. Rather than teach it that one format in code, this makes entry formats data that a repository opts into.
Why
elastic/elasticsearchkeeps one file per PR indocs/changelog/withpr,summary,area,typeandissues, plus optionalbreaking,deprecationandhighlightblocks. docs-builder rejected all 715 of them (Property 'area' not foundorProperty 'summary' not found). That has two effects today:release-notes.ymlworkflow to Elasticsearch would failrelease-notes / validateon every PR that adds an entry, about a quarter of recent PRs.release unified bundle,release serverless) fails with "matches PR #N but could not be parsed" when a PR in the range has one.Beats, elastic-agent and fleet-server have the same problem with a third format (elastic-agent-changelog-tool fragments), so a one-off conversion wouldn't last.
What
config/changelog-entry-formats.yml(embedded) defines each format as a mapping from docs-builder fields to source fields:from(first path with a value wins, nested paths likebreaking.details),split,unless,template,present, per-fieldvaluestranslations, andignore. Any other key in a source file is an error, so typos still surface. It ships two formats:elasticsearch:summary→title,area→areas,bug/breaking/breaking-java/new-aggregation/upgrade→bug-fix/breaking-change/breaking-change/feature/other, blockdetails/impact/body→description/impact,highlight→highlight,source_repo→prs: elastic/<repo>#<pr>.elastic-agent-changelog-tool:kind,summary,description,component,pr,issue,impact,action.entry_format: <name>inchangelog.yml. An unknown name is a configuration error that lists the known ones. Nothing changes for a repository that doesn't set it.changelog validateconverts before validating. Converted entries have noproducts, so a missingproductsis accepted whenproducts.defaultis set.changelog uploaduploads the converted entry, so the pool holds docs-builder entries from then on.entry_format. A pool entry without products takes them like a synthesized entry:pivot.productslabels, thenproducts.default. Those PRs now have their metadata fetched for that.files: any(elastic-agent-changelog-tool) has file names that don't carry the PR.changelog validateassigns such a file to the PR that adds it;changelog uploadlooks up the PR that added each file that names none (commits?path=thencommits/{sha}/pulls) and uploads the entry under that PR's number; commit-range bundling matches a pool entry that still names no PR to the PR in the range that added a file with that name (onepulls/{n}/filescall per PR without a pool entry, only when such entries exist).create.md,entry_formatinconfigure-ref.md, and a commented line inchangelog.example.yml.Checks
Elastic.Changelog.Tests: 1670/1670 pass. New tests cover both shipped formats, a format defined in the test, unknown source keys and target fields, the config key, upload conversion, validation, and a commit-range bundle with a mixed pool (two Elasticsearch-format entries, one resolved byproducts.defaultand one by a label, plus one docs-builder entry). Withoutentry_format, an Elasticsearch-format pool entry still fails to parse, as before.docs-builder --path docs --strict: 0 errors.changelog validateover all 715 files inelastic/elasticsearchwith itsdocs/changelog.ymlplusentry_format: elasticsearch: 697 pass. The other 18 used the schema areaSnapshot/Restore; the format now translates it andExtract&Transformto the names the Elasticsearch config uses, and with [DOCS] Read changelog entries with the elasticsearch entry format elasticsearch#162667 all 718 files onmainpass.elastic-agent-changelog-tool.Not covered
changelog bundlewith a product filter, outside a commit range) don't add products to converted entries.elastic/docs-actions(changelog/upload) doesn't passGITHUB_TOKENto docs-builder yet, so the upload-time PR lookup would run unauthenticated until it does. That's a one-line follow-up there.Fixes elastic/docs-eng-team#1106
🤖 Generated with Claude Code
https://claude.ai/code/session_01TjZFCvQhL3DdrcjFKU6jDT