From 9319edfe72fce92577b16b06be41248b4ac4d132 Mon Sep 17 00:00:00 2001 From: Adelina Simion <43963729+addetz@users.noreply.github.com> Date: Wed, 12 Jun 2024 19:24:41 +0200 Subject: [PATCH] docs: add versioning logic to partials DOC-1198 (#3035) --- .gitignore | 1 + Makefile | 2 +- README.md | 11 +++-- scripts/generate-partials.sh | 6 ++- scripts/versions.sh | 10 ++++ .../PartialsComponent/GetAllVersions.tsx | 16 ++++++ .../PartialsComponent/GetVersion.tsx | 10 ++++ .../PartialsComponent-Versioned.test.tsx | 47 ++++++++++++++++++ .../PartialsComponent.test.tsx | 16 ++++-- .../PartialsComponent/PartialsComponent.tsx | 31 ++++++++++-- .../PartialsComponent/PartialsImporter.tsx | 49 ++++++++++++++++--- 11 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 src/components/PartialsComponent/GetAllVersions.tsx create mode 100644 src/components/PartialsComponent/GetVersion.tsx create mode 100644 src/components/PartialsComponent/PartialsComponent-Versioned.test.tsx diff --git a/.gitignore b/.gitignore index 0b62a66222..2ca1efff5e 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ docs/api-content/api-docs/edge-v1/sidebar.* versions.json versioned_docs/ versioned_sidebars/ +versioned_partials/ api_versions.json api_versioned_docs/ api_versioned_sidebars/ diff --git a/Makefile b/Makefile index 53ee1da193..89160dcfac 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ deep-clean: ## Clean all artifacts clean-versions: ## Clean Docusarus content versions @echo "cleaning versions" - rm -rf api_versions.json versions.json versioned_docs versioned_sidebars api_versioned_sidebars api_versioned_docs + rm -rf api_versions.json versions.json versioned_docs versioned_sidebars api_versioned_sidebars api_versioned_docs versioned_partials git checkout -- docusaurus.config.js static/robots.txt clean-api: ## Clean API docs diff --git a/README.md b/README.md index 0cc8232b57..7fe0feb179 100644 --- a/README.md +++ b/README.md @@ -585,13 +585,16 @@ category and name of the partial: ```md ``` -Note that the `cloud` field corresponds to the `{props.cloud}` reference in the `*.mdx` file. +The snippet above will work with the example partial we have in our repository, so you can use it for testing. + +Note that the `message` field corresponds to the `{props.message}` reference in the `_partials/_partial_example.mdx` +file. ### Internal Links diff --git a/scripts/generate-partials.sh b/scripts/generate-partials.sh index a2e14dc9ee..12968d7c84 100755 --- a/scripts/generate-partials.sh +++ b/scripts/generate-partials.sh @@ -12,6 +12,9 @@ rm -f _partials/index.ts mkdir -p _partials touch _partials/index.ts +# Make the versioned partials folder to satisfy compiler. +mkdir -p versioned_partials + # Create the file and add the generated warning. echo "// This file is generated. DO NOT EDIT!" >> _partials/index.ts @@ -20,7 +23,8 @@ echo "// This file is generated. DO NOT EDIT!" >> _partials/index.ts find _partials -name "*.mdx" -print0 | while read -d $'\0' path do module_name=$(basename ${path} .mdx | tr -d '_' | tr -d '-') - echo "export * as ${module_name}${RANDOM} from '@site/${path}';" >> _partials/index.ts + file_name=$(basename ${path}) + echo "export * as ${module_name}${RANDOM} from './${file_name}';" >> _partials/index.ts done echo "Completed generation of _partials/index.ts." diff --git a/scripts/versions.sh b/scripts/versions.sh index b03dae2712..f6070c0f5c 100755 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -53,12 +53,15 @@ for b in $branches; do git fetch origin $b:$b done +# Make sure we are in a clean state for repeatable runs. +make clean-versions # Remove the existing versioned directories in the temp directory. rm -rf $tempdir/staging_docs rm -rf $tempdir/staging_api_docs rm -rf $tempdir/staging_sidebars rm -rf $tempdir/staging_api_docs_sidebars +rm -rf $tempdir/staging_partials rm -rf $tempdir/temp_versions.json rm -rf $tempdir/temp_api_versions.json @@ -68,6 +71,7 @@ mkdir -p $tempdir/staging_docs mkdir -p $tempdir/staging_api_docs mkdir -p $tempdir/staging_sidebars mkdir -p $tempdir/staging_api_docs_sidebars +mkdir -p $tempdir/staging_partials touch $tempdir/temp_versions.json touch $tempdir/temp_api_versions.json echo '[]' > $tempdir/temp_versions.json # Initialize as an empty array if it doesn't exist @@ -119,6 +123,9 @@ for item in $(git branch --format '%(refname:short)'); do # Pull the latest changes git pull origin $item + # Generate the partials once we are on the version branch + make generate-partials + # Run the npm command echo "Running: npm run docusaurus docs:version $extracted_versionX" npm run docusaurus docs:version $extracted_versionX @@ -139,6 +146,8 @@ for item in $(git branch --format '%(refname:short)'); do cp -R api_versioned_docs/version-$extracted_versionX $tempdir/staging_api_docs/ cp -R api_versioned_sidebars/version-$extracted_versionX $tempdir/staging_api_docs_sidebars/ || true cp api_versioned_sidebars/version-$extracted_versionX-sidebars.json $tempdir/staging_api_docs_sidebars/version-$extracted_versionX-sidebars.json + # Copy the partials folder + cp -R _partials $tempdir/staging_partials/version-$extracted_versionX rm -rf versioned_docs/ @@ -163,6 +172,7 @@ cp -R $tempdir/staging_docs $baseDir/versioned_docs cp -R $tempdir/staging_sidebars $baseDir/versioned_sidebars cp -R $tempdir/staging_api_docs $baseDir/api_versioned_docs cp -R $tempdir/staging_api_docs_sidebars $baseDir/api_versioned_sidebars +cp -R $tempdir/staging_partials/. $baseDir/versioned_partials # Remove the existing versions.json if it exists [ -e versions.json ] && rm versions.json diff --git a/src/components/PartialsComponent/GetAllVersions.tsx b/src/components/PartialsComponent/GetAllVersions.tsx new file mode 100644 index 0000000000..b356171045 --- /dev/null +++ b/src/components/PartialsComponent/GetAllVersions.tsx @@ -0,0 +1,16 @@ +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { Config } from "@docusaurus/types"; + +export default function GetVersions(): string[] { + const { siteConfig } = useDocusaurusContext(); + const cfg = siteConfig as Config; + if (cfg.presets != undefined) { + // Linting is disabled for next line, as there is no type for docs config. + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument + const versions = cfg.presets[0][1]["docs"]["versions"] as object; + const versionNames: string[] = Object.keys(versions); + return versionNames; + } + // if no presets, then default to "current" + return ["current"]; +} diff --git a/src/components/PartialsComponent/GetVersion.tsx b/src/components/PartialsComponent/GetVersion.tsx new file mode 100644 index 0000000000..7087305f99 --- /dev/null +++ b/src/components/PartialsComponent/GetVersion.tsx @@ -0,0 +1,10 @@ +import { useActivePluginAndVersion } from "@docusaurus/plugin-content-docs/client"; + +export default function GetVersion(): string { + const activePlugin = useActivePluginAndVersion(); + if (activePlugin != undefined && activePlugin.activeVersion != undefined) { + return activePlugin.activeVersion.name; + } + + return ""; +} diff --git a/src/components/PartialsComponent/PartialsComponent-Versioned.test.tsx b/src/components/PartialsComponent/PartialsComponent-Versioned.test.tsx new file mode 100644 index 0000000000..35664bbdca --- /dev/null +++ b/src/components/PartialsComponent/PartialsComponent-Versioned.test.tsx @@ -0,0 +1,47 @@ +import React, { FunctionComponent } from "react"; +import { render, screen } from "@testing-library/react"; + +let category = "testCat1"; +let name = "nameCat1"; +let propValue = "testValue1"; + +const Partial: React.FunctionComponent<{}> = ({}) => ( +
+

{category}

+

{name}

+

{propValue}

+
+); + +jest.mock("./PartialsImporter", () => { + return jest.fn(() => { + const allPartials: PartialsMap = {}; + const mapKey = "version-4.3.x".concat("#").concat(category).concat("#").concat(name); + allPartials[mapKey] = Partial as FunctionComponent; + return allPartials; + }); +}); + +jest.mock("./GetVersion", () => { + return jest.fn(() => { + return "4.3.x"; + }); +}); + +import PartialsComponent from "./PartialsComponent"; +import { PartialsMap } from "./PartialsImporter"; + +describe("Partials Component", () => { + it("partial exists", () => { + render(); + expect(screen.getByText(category)).toBeInTheDocument(); + expect(screen.getByText(name)).toBeInTheDocument(); + expect(screen.getByText(propValue)).toBeInTheDocument(); + }); + + it("partial does not exist", () => { + expect(() => render()).toThrow( + "No partial found for name unknownName in category unknownCat for version 4.3.x." + ); + }); +}); diff --git a/src/components/PartialsComponent/PartialsComponent.test.tsx b/src/components/PartialsComponent/PartialsComponent.test.tsx index ca1588ab16..6f1d8ad8f6 100644 --- a/src/components/PartialsComponent/PartialsComponent.test.tsx +++ b/src/components/PartialsComponent/PartialsComponent.test.tsx @@ -15,10 +15,16 @@ const Partial: React.FunctionComponent<{}> = ({}) => ( jest.mock("./PartialsImporter", () => { return jest.fn(() => { - const mapKey = category.concat("#").concat(name); - const pmap: PartialsMap = {}; - pmap[mapKey] = Partial as FunctionComponent; - return pmap; + const allPartials: PartialsMap = {}; + const mapKey = "current".concat("#").concat(category).concat("#").concat(name); + allPartials[mapKey] = Partial as FunctionComponent; + return allPartials; + }); +}); + +jest.mock("./GetVersion", () => { + return jest.fn(() => { + return "current"; }); }); @@ -35,7 +41,7 @@ describe("Partials Component", () => { it("partial does not exist", () => { expect(() => render()).toThrow( - "No partial found for name unknownName in category unknownCat." + "No partial found for name unknownName in category unknownCat for version current." ); }); }); diff --git a/src/components/PartialsComponent/PartialsComponent.tsx b/src/components/PartialsComponent/PartialsComponent.tsx index 3c5b180109..5e344ca134 100644 --- a/src/components/PartialsComponent/PartialsComponent.tsx +++ b/src/components/PartialsComponent/PartialsComponent.tsx @@ -1,18 +1,34 @@ import React from "react"; -import ImportPartials from "./PartialsImporter"; +import ImportPartials, { PartialsMap } from "./PartialsImporter"; +import GetVersion from "./GetVersion"; interface ComponentProperties { [key: string]: string; } -const AllPartials = ImportPartials(); +let AllPartials: PartialsMap = {}; +let firstLoad = true; export default function PartialsComponent(details: ComponentProperties): React.ReactElement { - const mapKey = details.category.concat("#").concat(details.name); + // Hooks can only be invoked inside the body of the component, so we cannot load this beforehand. + if (firstLoad) { + AllPartials = ImportPartials(); + firstLoad = false; + } + // Get the version this page is on. + const ver: string = GetVersion(); + // Construct the map key including the version + const mapKey = getMapKey(ver, details.category, details.name); if (!AllPartials[mapKey]) { throw new Error( - "No partial found for name ".concat(details.name).concat(" in category ").concat(details.category).concat(".") + "No partial found for name " + .concat(details.name) + .concat(" in category ") + .concat(details.category) + .concat(" for version ") + .concat(ver) + .concat(".") ); } @@ -28,3 +44,10 @@ export default function PartialsComponent(details: ComponentProperties): React.R return React.createElement(AllPartials[mapKey], propAttribute); } + +function getMapKey(ver: string, category: string, name: string): string { + if (ver == "current") { + return ver.concat("#").concat(category).concat("#").concat(name); + } + return "version-".concat(ver).concat("#").concat(category).concat("#").concat(name); +} diff --git a/src/components/PartialsComponent/PartialsImporter.tsx b/src/components/PartialsComponent/PartialsImporter.tsx index b9441d67a7..7f05b8893c 100644 --- a/src/components/PartialsComponent/PartialsImporter.tsx +++ b/src/components/PartialsComponent/PartialsImporter.tsx @@ -1,12 +1,11 @@ import { FunctionComponent } from "react"; -// Import all the partials as one module. -import * as PartialModules from "@site/_partials"; +import GetAllVersions from "./GetAllVersions"; export interface PartialsMap { [key: string]: FunctionComponent; } -interface Modules { +export interface Modules { [key: string]: Module; } @@ -20,12 +19,46 @@ interface Module { export default function ImportPartials(): PartialsMap { const pmap: PartialsMap = {}; - const allPartialModules: Modules = PartialModules; + const versions: string[] = GetAllVersions(); + versions.map((ver) => { + if (ver == "current") { + importLatestHelper(ver, pmap); + } else { + //Import all the versioned partials if this is the first time we visit this version. + importVersionedHelper("version-" + ver, pmap); + } + }); + + return pmap; +} + +function importLatestHelper(prefix: string, existingPartials: PartialsMap) { + try { + // Linting is disabled for next line, as we need the ability to dynamically import partials. + // eslint-disable-next-line @typescript-eslint/no-var-requires + const modules: Modules = require("@site/_partials"); + MapPartials(prefix, modules, existingPartials); + } catch { + console.log("No partials found for " + prefix + ". Skipping imports for it."); + } +} + +function importVersionedHelper(prefix: string, existingPartials: PartialsMap) { + try { + // Linting is disabled for next line, as we need the ability to dynamically import partials. + // eslint-disable-next-line @typescript-eslint/no-var-requires + const modules: Modules = require("@site/versioned_partials/" + prefix + "/"); + MapPartials(prefix, modules, existingPartials); + } catch { + console.log("No partials found for " + prefix + ". Skipping imports for it."); + } +} +export function MapPartials(ver: string, module: Modules, pmap: PartialsMap) { // The keys are the names of each exported module in _partials/index.ts - const partialKeys: string[] = Object.keys(allPartialModules); + const partialKeys: string[] = Object.keys(module); partialKeys.map(function (pkey) { - const currentPartial: Module = allPartialModules[pkey]; + const currentPartial: Module = module[pkey]; const catFrontMatter = currentPartial.frontMatter.partial_category; const nameFrontMatter = currentPartial.frontMatter.partial_name; @@ -33,13 +66,15 @@ export default function ImportPartials(): PartialsMap { throw new Error("Please specify partial_category and partial_name for ".concat(pkey).concat(".")); } - const mapKey = catFrontMatter.concat("#").concat(nameFrontMatter); + const mapKey = ver.concat("#").concat(catFrontMatter).concat("#").concat(nameFrontMatter); if (pmap[mapKey]) { throw new Error( "Duplicate partial defined for name " .concat(nameFrontMatter) .concat(" in category ") .concat(catFrontMatter) + .concat("for version") + .concat(ver) .concat(".") ); }