Add historical redirects file (#1780)

Adds a file (and generation script) that links pages in historical API
docs to pages in the latest API docs. The banner we show on historical
docs pages can use this to link users to a more relevant page in the
latest docs, rather than always linking to the top level.

The file has the following form.

```json
{
  "package-name": {
    "version": {
      "old.page.name": "new.page.name",
    }
  }
}
```

If a page has no entry in the redirects file, we default to redirecting
to the same page in the latest API docs. E.g.
`/api/qiskit/0.33/qiskit.dagcircuit.DAGCircuit` will link to
`/api/qiskit/qiskit.dagcircuit.DAGCircuit`.

This PR only checks if a page with the same name exists in the latest
API docs and redirects to the top level if it does not. A follow up PR
could include more complicated logic to track moved classes or point to
module names when the class is removed.

<details><summary><b>Stats</b></summary>

Here are some stats to give an idea of how many pages this affects.

In total, 35% of historical API pages have a corresponding page in the
latest API versions. Looking at the breakdown per version, the majority
of recent pages will link to a more helpful page. The rest will behave
the same as before.

| Package | Page matches |
|---------|--------------|
| qiskit-ibm-provider/0.10 | 100% |
| qiskit-ibm-provider/0.7 | 100% |
| qiskit-ibm-provider/0.8 | 100% |
| qiskit-ibm-provider/0.9 | 100% |
| qiskit-ibm-runtime/0.14 | 90% |
| qiskit-ibm-runtime/0.15 | 90% |
| qiskit-ibm-runtime/0.16 | 100% |
| qiskit-ibm-runtime/0.17 | 100% |
| qiskit-ibm-runtime/0.18 | 98% |
| qiskit-ibm-runtime/0.19 | 98% |
| qiskit-ibm-runtime/0.20 | 98% |
| qiskit-ibm-runtime/0.21 | 99% |
| qiskit-ibm-runtime/0.22 | 99% |
| qiskit-ibm-runtime/0.23 | 100% |
| qiskit-ibm-runtime/0.24 | 100% |
| qiskit-ibm-runtime/dev | 100% |
| qiskit/0.19 | 23% |
| qiskit/0.24 | 23% |
| qiskit/0.25 | 20% |
| qiskit/0.26 | 20% |
| qiskit/0.27 | 20% |
| qiskit/0.28 | 21% |
| qiskit/0.29 | 21% |
| qiskit/0.30 | 21% |
| qiskit/0.31 | 21% |
| qiskit/0.32 | 21% |
| qiskit/0.33 | 33% |
| qiskit/0.35 | 34% |
| qiskit/0.36 | 34% |
| qiskit/0.37 | 34% |
| qiskit/0.38 | 34% |
| qiskit/0.39 | 34% |
| qiskit/0.40 | 33% |
| qiskit/0.41 | 33% |
| qiskit/0.42 | 33% |
| qiskit/0.43 | 39% |
| qiskit/0.44 | 53% |
| qiskit/0.45 | 56% |
| qiskit/0.46 | 57% |
| qiskit/1.0 | 97% |
| qiskit/dev | 100% |

</details>

---------

Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
This commit is contained in:
Frank Harkins 2024-07-30 18:19:21 +01:00 committed by GitHub
parent 95e358537e
commit 60c437d9e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 18644 additions and 0 deletions

View File

@ -29,6 +29,7 @@
"regen-apis": "tsx scripts/js/commands/api/regenerateApiDocs.ts",
"gen-api": "tsx scripts/js/commands/api/updateApiDocs.ts",
"make-historical": "tsx scripts/js/commands/api/convertApiDocsToHistorical.ts",
"generate-historical-redirects": "tsx scripts/js/commands/api/generateHistoricalRedirects.ts",
"save-internal-links": "tsx scripts/js/commands/saveInternalLinks.ts"
},
"dependencies": {

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ import transformLinks from "transform-markdown-links";
import { pathExists } from "../../lib/fs.js";
import { zxMain } from "../../lib/zx.js";
import { Pkg } from "../../lib/api/Pkg.js";
import { generateHistoricalRedirects } from "./generateHistoricalRedirects.js";
interface Arguments {
[x: string]: unknown;
@ -75,6 +76,7 @@ zxMain(async () => {
);
await copyImages(pkgName, versionWithoutPatch);
await copyObjectsInv(pkgName, versionWithoutPatch);
await generateHistoricalRedirects();
});
async function copyApiDocsAndUpdateLinks(

View File

@ -0,0 +1,101 @@
// This code is a Qiskit project.
//
// (C) Copyright IBM 2024.
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.
import { readdir, writeFile } from "fs/promises";
import { basename, join } from "path";
import { Pkg } from "../../lib/api/Pkg.js";
import { zxMain } from "../../lib/zx.js";
import { removeSuffix } from "../../lib/stringUtils.js";
const OUTPUT_FILE = "./scripts/config/historical-pages-to-latest.json";
zxMain(async () => {
await generateHistoricalRedirects();
});
export async function generateHistoricalRedirects(): Promise<void> {
console.log(`Generating ${OUTPUT_FILE}`);
const redirectData: HistoricalRedirectData = {};
for (const packageName of Pkg.VALID_NAMES) {
redirectData[packageName] = await getRedirectsForPackage(
join("docs/api", packageName),
);
}
await writeFile(OUTPUT_FILE, JSON.stringify(redirectData, null, 2) + "\n");
}
/**
* E.g.
* {
* "qiskit": {
* "0.44": {
* "qiskit.opflow.evolutions.TrotterizationBase": "/",
* ...
* }
* ...
* }
* }
*/
type HistoricalRedirectData = {
[packageName: string]: {
[version: string]: Redirects;
};
};
type Redirects = {
// Key ("from") is page relative to old version (e.g. qiskit.opflow.evolutions.TrotterizationBase).
// Value is new page relative to latest version (e.g. "/")
// If redirect does not exist, we assume "to" and "from" are the same
[from: string]: string;
};
async function getRedirectsForPackage(
packagePath: string,
): Promise<{ [version: string]: Redirects }> {
const latestPages: string[] = [];
const versionPaths: string[] = [];
for (const entry of await readdir(packagePath, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (entry.name.endsWith("release-notes")) continue;
versionPaths.push(entry.name);
} else {
latestPages.push(entry.name);
}
}
const redirectsByVersion: { [api: string]: Redirects } = {};
for (const path of versionPaths) {
const version = basename(path);
redirectsByVersion[version] = await getRedirectsForVersion(
join(packagePath, path),
latestPages,
);
}
return redirectsByVersion;
}
async function getRedirectsForVersion(
versionPath: string,
latestPages: string[],
): Promise<Redirects> {
const mdPaths = await readdir(versionPath);
const redirects: { [from: string]: string } = {};
for (const path of mdPaths) {
if (latestPages.includes(basename(path))) {
continue;
}
// For now, we always redirect to the top level. Future improvements could
// try to identify the module name and redirect there if it exists.
const pageName = removeSuffix(basename(path), ".mdx");
redirects[pageName] = "/";
}
return redirects;
}

View File

@ -19,6 +19,7 @@ import { $ } from "zx";
import { Pkg } from "../../lib/api/Pkg.js";
import { zxMain } from "../../lib/zx.js";
import { pathExists } from "../../lib/fs.js";
import { generateHistoricalRedirects } from "./generateHistoricalRedirects.js";
interface Arguments {
[x: string]: unknown;
@ -82,6 +83,7 @@ zxMain(async () => {
result.forEach((msg) => console.error(msg));
console.log("");
});
await generateHistoricalRedirects();
console.log(`Each regenerated version has been saved as a distinct commit. If the changes are
too large for one single PR, consider splitting it up into multiple PRs by using

View File

@ -18,6 +18,7 @@ import { zxMain } from "../../lib/zx.js";
import { pathExists, rmFilesInFolder } from "../../lib/fs.js";
import { downloadSphinxArtifact } from "../../lib/api/sphinxArtifacts.js";
import { runConversionPipeline } from "../../lib/api/conversionPipeline.js";
import { generateHistoricalRedirects } from "./generateHistoricalRedirects.js";
interface Arguments {
[x: string]: unknown;
@ -95,6 +96,7 @@ zxMain(async () => {
console.log(`Run pipeline for ${pkg.name}:${pkg.versionWithoutPatch}`);
await runConversionPipeline(sphinxArtifactFolder, "docs", "public", pkg);
await generateHistoricalRedirects();
});
function determineMinorVersion(args: Arguments): string {