Skip to content

Commit

Permalink
Qiskit docs generation only changes release notes for its own version (
Browse files Browse the repository at this point in the history
…Qiskit#860)

### Summary 

Part of Qiskit#755

This PR changes the logic of the API generation script to only modify
the release notes file of the version we are regenerating at that
moment. Before, the release notes files contained more than one version,
and we needed to update several files, independently of what version we
were regenerating. After Qiskit/qiskit#11840, we
can assume that the release notes files will only contain their own
versions, and we can simply our script by removing some functions.

### New logic

The API generation script will transform every link of the release notes
files to point to its version folder instead of the top level. Once we
have the correct links, we will directly write the release notes file,
if we didn't have them before, or otherwise, we will create a new file
containing the header of the old file we had and the version sections of
the new one downloaded from Box. That way, we can make manual changes
like the table added in the release notes of qiskit 0.44 without losing
them in the next regeneration.

### Functions removed

This change allows us to remove the following two functions:

The `extractMarkdownReleaseNotesPatches` function extracted all the
versions in a release notes file and stored the markdown of each patch
to posteriorly merge them under their minor version file. Now we have
one minor version per file, so we don't need to break the release notes
into pieces anymore. The new logic treats all patch versions as a block.

The `sortReleaseNotesVersions` were used to sort the patch versions.
Given that the file will have the correct order, we don't need to worry
about it either.

Removing these two functions allows us to remove the test file, given
that they composed the entire file.
  • Loading branch information
arnaucasau authored Feb 23, 2024
1 parent 138a8a0 commit cfa6334
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 590 deletions.
518 changes: 240 additions & 278 deletions docs/api/qiskit/release-notes/0.45.md

Large diffs are not rendered by default.

175 changes: 84 additions & 91 deletions docs/api/qiskit/release-notes/0.46.md

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions scripts/commands/convertApiDocsToHistorical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ async function copyApiDocsAndUpdateLinks(
),
);
}

const releaseNotePath = `${getRoot()}/docs/api/${pkgName}/release-notes/${versionWithoutPatch}.md`;
if (await pathExists(releaseNotePath)) {
updateLinksFile(
pkgName,
versionWithoutPatch,
releaseNotePath,
releaseNotePath,
);
}
}

async function generateJsonFiles(
Expand Down
10 changes: 7 additions & 3 deletions scripts/commands/updateApiDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
addNewReleaseNotes,
generateReleaseNotesIndex,
updateHistoricalTocFiles,
writeSeparateReleaseNotes,
writeReleaseNoteForVersion,
} from "../lib/api/releaseNotes";

interface Arguments {
Expand Down Expand Up @@ -241,14 +241,18 @@ async function convertHtmlToMarkdown(
}

if (pkg.hasSeparateReleaseNotes && path.endsWith("release-notes.md")) {
const baseUrl = pkg.isHistorical()
? `/api/${pkg.name}/${pkg.versionWithoutPatch}`
: `/api/${pkg.name}`;

// Convert the relative links to absolute links
result.markdown = transformLinks(result.markdown, (link, _) =>
link.startsWith("http") || link.startsWith("#") || link.startsWith("/")
? link
: `/api/${pkg.name}/${link}`,
: `${baseUrl}/${link}`,
);

await writeSeparateReleaseNotes(pkg, result.markdown);
await writeReleaseNoteForVersion(pkg, result.markdown);
continue;
}

Expand Down
90 changes: 0 additions & 90 deletions scripts/lib/api/releaseNotes.test.ts

This file was deleted.

147 changes: 19 additions & 128 deletions scripts/lib/api/releaseNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,83 +145,10 @@ function addNewReleaseNoteToc(releaseNotesNode: any, newVersion: string) {
}

/**
* Sorts the release notes dictionary by the patch version
* in descending order, e.g. 0.25.2 then 0.25.1.
*
* Returns a dictionary with the corresponding markdown for
* each patch with the entries sorted by release.
* Creates the release note file for the minor version found in `pkg`.If the file
* already exists, it will keep the initial header and overwrite the rest of the content.
*/
export function sortReleaseNotesVersions(markdownByPatchVersion: {
[id: string]: string;
}): { [id: string]: string } {
// Sorts the entries of markdownByPathVersion by patch in descending order,
// returning an array with elements corresponding to the dictionary entries
// as arrays of two elements (key and value).
// e.g.
// makrdownByPathVersion = {"0.45.0": "test 1", "0.46.0": "test 2"}
// =>
// markdownByPatchSorted = [["0.46.0", "test 2"],["0.45.0", "test 1"]]
const markdownByPatchSorted = Object.entries(markdownByPatchVersion).sort(
([version1], [version2]) => {
const versionPatch1 = version1.split("rc").slice(0, 1)[0];
const versionPatch2 = version2.split("rc").slice(0, 1)[0];

if (versionPatch1 == versionPatch2) {
// The release candidates within the same patch should appear last.
// e.g. version 0.45.1rc1 should appear after version 0.45.1.
if (version1.length < version2.length) {
return -1;
} else if (version1.length > version2.length) {
return 1;
}
}

return version2.localeCompare(version1);
},
);

return Object.fromEntries(markdownByPatchSorted);
}

/**
* Process the markdown dividing it into small sections for each patch version
*
* Returns a tuple:
* 1. A Set of strings representing all the minor versions we found in the
* markdown.
* 2. A dictionary with the corresponding markdown for each patch version
* found.
*/
export function extractMarkdownReleaseNotesPatches(
markdown: string,
): [Set<string>, { [id: string]: string }] {
const sectionsSplit = markdown.split(/\n## (?=[0-9])/);
const sections: string[] = sectionsSplit.slice(1, sectionsSplit.length);

const minorVersionsFound = new Set<string>();
const markdownByPatchVersion: { [id: string]: string } = {};

for (let section of sections) {
const versionPatch = section.split("\n").slice(0, 1)[0];
const versionMinor = versionPatch.split(".").slice(0, 2).join(".");

minorVersionsFound.add(versionMinor);

const content = section.split("\n");
content.shift();
markdownByPatchVersion[versionPatch] = `## ${versionPatch}\n${content.join(
"\n",
)}`;
}

return [minorVersionsFound, markdownByPatchVersion];
}

/**
* Updates the release notes folder by adding the notes to their corresponding version
* file.
*/
export async function writeSeparateReleaseNotes(
export async function writeReleaseNoteForVersion(
pkg: Pkg,
releaseNoteMarkdown: string,
): Promise<void> {
Expand All @@ -231,62 +158,26 @@ export async function writeSeparateReleaseNotes(
);
}

// Dictionary to store the file header in case we need to reconstruct a file from a
// previous version
const filesToInitialHeaders: { [id: string]: string } = {};
const basePath = `${getRoot()}/docs/api/${pkg.name}/release-notes`;

const [minorVersionsFound, markdownByPatchVersion] =
extractMarkdownReleaseNotesPatches(releaseNoteMarkdown);

// Read the current release notes for each version found
for (let version of minorVersionsFound) {
const versionPath = `${basePath}/${version}.md`;

// When we're adding a new version, its release note file will not yet exist, so we
// cannot read it here to determine the prior contents. Instead, skip the release
// note since we are going to add it at the end of this function.
if (!(await pathExists(versionPath))) {
continue;
}

const versionPath = `${basePath}/${pkg.versionWithoutPatch}.md`;

const versionsMarkdown = releaseNoteMarkdown
.split(/\n## (?=[0-9])/)
.slice(1)
.join("\n## ");

if (!(await pathExists(versionPath))) {
await writeFile(versionPath, releaseNoteMarkdown);
} else if (versionsMarkdown) {
// The if statement prevents us from modifying versions of Qiskit < 0.45.
// Those versions have a different structure, such as a different section
// title (`## Qiskit 0.44.0` instead of `## 0.44.0`), and they contain
// more than one version in the same file.
const currentMarkdown = await readFile(versionPath, "utf-8");
filesToInitialHeaders[version] = currentMarkdown
const initialHeader = currentMarkdown
.split(/\n## (?=[0-9])/)
.slice(0, 1)[0];
}

const markdownByPatchVersionSorted = sortReleaseNotesVersions(
markdownByPatchVersion,
);

// Generate the modified release notes files
const markdownByMinorVersion: { [id: string]: string } = {};
Object.entries(markdownByPatchVersionSorted).forEach(
([versionPatch, markdown]) => {
const versionMinor = versionPatch.split(".").slice(0, 2).join(".");

if (!markdownByMinorVersion.hasOwnProperty(versionMinor)) {
markdownByMinorVersion[versionMinor] = markdown;
} else {
markdownByMinorVersion[versionMinor] += `\n${markdown}`;
}
},
);

// Write all the modified files
for (let [versionMinor, markdown] of Object.entries(markdownByMinorVersion)) {
let fileInitialHeader = filesToInitialHeaders[versionMinor];
if (fileInitialHeader == undefined) {
fileInitialHeader = `---
title: Qiskit ${versionMinor} release notes
description: New features and bug fixes
---
# Qiskit ${versionMinor} release notes
`;
}

const versionPath = `${basePath}/${versionMinor}.md`;
await writeFile(versionPath, `${fileInitialHeader}\n${markdown}`);
await writeFile(versionPath, `${initialHeader}\n## ${versionsMarkdown}`);
}
}

0 comments on commit cfa6334

Please sign in to comment.