Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add PartialsComponent to handle Docusaurus Markdown Imports in librarium DOC-1189 #2887

Merged
merged 3 commits into from
May 23, 2024
Merged
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,7 @@ screenshots/
tests/screenshot.spec.ts-snapshots/
test-results/
playwright-report/
artifact.zip
artifact.zip

# Ignore _partials/index.ts
_partials/index.ts
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ init: ## Initialize npm dependencies
npx husky install

start: ## Start a local development server
make generate-partials
npm run start

build: ## Run npm build
Expand Down Expand Up @@ -203,6 +204,11 @@ format-images: ## Format images
@echo "formatting images in /static/assets/docs/images/ folder"
./scripts/compress-convert-images.sh

###@ Generate _partials/index.ts required to automatic partials usage.

generate-partials: ## Generate
./scripts/generate-partials.sh

###@ Aloglia Indexing

update-dev-index: ## Update the Algolia index for the dev environment
Expand Down
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,64 @@ scheme. The rows of cards are dynamically created according to the list of speci
/>
```

## Partials Component

This is a custom component that allows you to create and use
[Import Markdown](https://docusaurus.io/docs/3.2.1/markdown-features/react#importing-markdown).

Partials must be created under the `_partials` folder. They must be named using an `_` prefix and the `*.mdx` filetype.
Partials may be organised in any further subfolders as required. For example, you could create
`_partials/public-cloud/_palette_setup.mdx`.

In order to aid with organisation and categorization, partials must have a `partial_category` and `partial_name` defined
in their frontmatter:

```mdx
---
partial_category: public-cloud
partial_name: palette-setup
---

This is how you set up Palette in {props.cloud}.
```

Partials are customized using properties which can be read using the `{props.field}` syntax.

Once your partial has been created, run the `make generate-partials` command to make your partial available for use.
This command will also be invoked during the `make start` and `make build` commands.

Finally, you can reference your partial in any `*.md` file by using the `PartialsComponent`, together with the specified
category and name of the partial:

```md
<PartialsComponent
category="public-cloud"
name="palette-setup"
cloud="AWS"
/>
```

Note that the `cloud` field corresponds to the `{props.cloud}` reference in the `*.mdx` file.

### Internal Links

Due to the complexities of Docusaurus plugin rendering, links do not support versioning in `*.mdx` files. If you want to
add an internal link you will have to use the `VersionedLink` component inside the `*.mdx` file.

```mdx
---
partial_category: public-cloud
partial_name: palette-setup
---

This is how you set up Palette in {props.cloud}.

This is an <VersionedLink name="Internal Link" url="/getting-started/additional-capabilities"/>.
```

The path of the link should be the path of the destination file from the root directory, without any back operators
`..`. External links can be referenced as usual.

## Netlify Previews

By default Netlify previews are enabled for pull requests. However, some branches do not require Netlify previews. In
Expand Down
8 changes: 8 additions & 0 deletions _partials/_partial_example.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
partial_category: example-cat
partial_name: example-name
---

This is an example partial with an example property. {props.message}

Read more about how to use partials in our [README](https://github.com/spectrocloud/librarium/blob/master/README.md).
2 changes: 1 addition & 1 deletion docs/docs-content/getting-started/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The first step towards adopting Palette in your organization is to
We have curated the pages in the Getting Started section to give you a gradual introduction to the fundamental concepts
and workflows you need to deploy and manage Kubernetes clusters through Palette.

<div class="desktop-only-display">
<div className="desktop-only-display">

![Overview of the getting started journey rocket](/getting-started/getting-started_getting-started_journey-overview.webp)

Expand Down
11 changes: 11 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const redirects = require("./redirects");
const ArchivedVersions = require("./archiveVersions.json");
const { pluginPacksAndIntegrationsData } = require("./plugins/packs-integrations");
const { pluginImportFontAwesomeIcons } = require("./plugins/font-awesome");
import path from "path";

/** @type {import('@docusaurus/types').Config} */
const config = {
Expand Down Expand Up @@ -412,3 +413,13 @@ const config = {
},
};
module.exports = config;

export default function (context, options) {
return {
name: "@docusaurus/plugin-content-docs",
getPathsToWatch() {
const contentPath = path.resolve(context.siteDir, options.path);
return [`${contentPath}/_partials/*/*.{mdx}`];
},
};
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start --host 0.0.0.0 --port 9000",
"build": "npm run generate-api-docs && docusaurus build",
"build": "npm run generate-api-docs && npm run generate-partials && docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
Expand All @@ -18,6 +18,7 @@
"generate-api-docs": "npm run run-api-parser && docusaurus gen-api-docs palette && docusaurus gen-api-docs emc",
"clean-api-docs": "docusaurus clean-api-docs palette && docusaurus clean-api-docs emc",
"run-api-parser": "node utils/api-parser/index.js",
"generate-partials": "./scripts/generate-partials.sh",
"lint": "eslint . --ext .js,.ts,.jsx,.tsx",
"lint:fix": "npm run lint -- --fix",
"format": "prettier --write \"**/*.{js,jsx,json,ts,tsx,md,mdx,css}\"",
Expand Down
26 changes: 26 additions & 0 deletions scripts/generate-partials.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash

# Enable error handling
set -e

echo "Starting generation of _partials/index.ts."

# # Remove the index.ts file if it exists already.
rm -f _partials/index.ts

# Make the _partials folder an index.ts file
mkdir -p _partials
touch _partials/index.ts

# Create the file and add the generated warning.
echo "// This file is generated. DO NOT EDIT!" >> _partials/index.ts

# Find all the MDX files recursively in the _partials folder.
# Loop through each file.
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
done

echo "Completed generation of _partials/index.ts."
41 changes: 41 additions & 0 deletions src/components/PartialsComponent/PartialsComponent.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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<{}> = ({}) => (
<div>
<p>{category}</p>
<p>{name}</p>
<p>{propValue}</p>
</div>
);

jest.mock("./PartialsImporter", () => {
return jest.fn(() => {
const mapKey = category.concat("#").concat(name);
const pmap: PartialsMap = {};
pmap[mapKey] = Partial as FunctionComponent;
return pmap;
});
});

import PartialsComponent from "./PartialsComponent";
import { PartialsMap } from "./PartialsImporter";

describe("Partials Component", () => {
it("partial exists", () => {
render(<PartialsComponent category={category} name={name} propTest={propValue} />);
expect(screen.getByText(category)).toBeInTheDocument();
expect(screen.getByText(name)).toBeInTheDocument();
expect(screen.getByText(propValue)).toBeInTheDocument();
});

it("partial does not exist", () => {
expect(() => render(<PartialsComponent category="unknownCat" name="unknownName" propTest={propValue} />)).toThrow(
"No partial found for name unknownName in category unknownCat."
);
});
});
30 changes: 30 additions & 0 deletions src/components/PartialsComponent/PartialsComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import ImportPartials from "./PartialsImporter";

interface ComponentProperties {
[key: string]: string;
}

const AllPartials = ImportPartials();

export default function PartialsComponent(details: ComponentProperties): React.ReactElement {
const mapKey = details.category.concat("#").concat(details.name);

if (!AllPartials[mapKey]) {
throw new Error(
"No partial found for name ".concat(details.name).concat(" in category ").concat(details.category).concat(".")
);
}

// Map elements to object properties
const propAttribute: { [key: string]: string } = {};
for (const key in details) {
// Don't send category and name to the partial
if (key == "category" || key == "name") {
continue;
}
propAttribute[key] = details[key];
}

return React.createElement(AllPartials[mapKey], propAttribute);
}
51 changes: 51 additions & 0 deletions src/components/PartialsComponent/PartialsImporter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { FunctionComponent } from "react";
// Import all the partials as one module.
import * as PartialModules from "@site/_partials";

export interface PartialsMap {
[key: string]: FunctionComponent;
}

interface Modules {
[key: string]: Module;
}

interface Module {
frontMatter: {
partial_category: string;
partial_name: string;
};
default: FunctionComponent;
}

export default function ImportPartials(): PartialsMap {
const pmap: PartialsMap = {};
const allPartialModules: Modules = PartialModules;

// The keys are the names of each exported module in _partials/index.ts
const partialKeys: string[] = Object.keys(allPartialModules);
partialKeys.map(function (pkey) {
const currentPartial: Module = allPartialModules[pkey];
const catFrontMatter = currentPartial.frontMatter.partial_category;
const nameFrontMatter = currentPartial.frontMatter.partial_name;

if (!catFrontMatter || !nameFrontMatter) {
throw new Error("Please specify partial_category and partial_name for ".concat(pkey).concat("."));
}

const mapKey = catFrontMatter.concat("#").concat(nameFrontMatter);
if (pmap[mapKey]) {
throw new Error(
"Duplicate partial defined for name "
.concat(nameFrontMatter)
.concat(" in category ")
.concat(catFrontMatter)
.concat(".")
);
}

pmap[mapKey] = currentPartial.default;
});

return pmap;
}
3 changes: 3 additions & 0 deletions src/components/PartialsComponent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import PartialsComponent from "./PartialsComponent";

export default PartialsComponent;
14 changes: 14 additions & 0 deletions src/components/VersionedLink/CheckVersion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useActivePluginAndVersion } from "@docusaurus/plugin-content-docs/client";

export default function GetVersionPath() {
const activePlugin = useActivePluginAndVersion();
if (
activePlugin != undefined &&
activePlugin.activeVersion != undefined &&
activePlugin.activeVersion.name != "current"
) {
return activePlugin.activeVersion.path;
}

return "";
}
59 changes: 59 additions & 0 deletions src/components/VersionedLink/VersionedLink.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import VersionedLink from "./VersionedLink";

let prevVersionPath = "";
jest.mock("./CheckVersion", () => {
return jest.fn(() => {
return prevVersionPath;
});
});

describe("Versioned link", () => {
it("latest version", () => {
prevVersionPath = "";
const text = "Test link";
const url = "/path/url";
render(<VersionedLink text={text} url={url} />);
const link = screen.getByText(text).getAttribute("href");
expect(link).not.toBeNull();
expect(link).toBe(url);
});

it("previous version", () => {
prevVersionPath = "/v4.3.1";
const text = "Test link";
const url = "/path/url";
render(<VersionedLink text={text} url={url} />);
const link = screen.getByText(text).getAttribute("href");
expect(link).not.toBeNull();
expect(link).toBe(prevVersionPath.concat(url));
});

it("url with back dots", () => {
prevVersionPath = "";
const text = "Test link";
const url = "../path/url";
expect(() => render(<VersionedLink text={text} url={url} />)).toThrow(
"Versioned links should provide the path of the destination URL from root, without any `./` or `..` references."
);
});

it("url with back relative reference", () => {
prevVersionPath = "";
const text = "Test link";
const url = "./path/url";
expect(() => render(<VersionedLink text={text} url={url} />)).toThrow(
"Versioned links should provide the path of the destination URL from root, without any `./` or `..` references."
);
});

it("url with external domain", () => {
prevVersionPath = "";
const text = "Test link";
const url = "https://google.com";
expect(() => render(<VersionedLink text={text} url={url} />)).toThrow(
"Versioned links should not be used for external URLs. Please use the default markdown syntax instead."
);
});
});
Loading