Skip to content

Commit

Permalink
feat(docs) use documentation links from objects.inv.txt file shipped …
Browse files Browse the repository at this point in the history
…with lxd in favor of hard coding the link replacements WD-8064

Signed-off-by: David Edler <david.edler@canonical.com>
  • Loading branch information
edlerd committed Jan 19, 2024
1 parent 6bc9e9f commit 46296dc
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 235 deletions.
11 changes: 10 additions & 1 deletion src/api/server.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { handleResponse } from "util/helpers";
import { handleResponse, handleTextResponse } from "util/helpers";
import { LxdSettings } from "types/server";
import { LxdApiResponse } from "types/apiResponse";
import { LxdConfigOptions, LxdConfigPair } from "types/config";
Expand Down Expand Up @@ -44,3 +44,12 @@ export const fetchConfigOptions = (): Promise<LxdConfigOptions> => {
.catch(reject);
});
};

export const fetchDocObjects = (): Promise<string[]> => {
return new Promise((resolve, reject) => {
fetch("/documentation/objects.inv.txt")
.then(handleTextResponse)
.then((data) => resolve(data.split("\n")))
.catch(reject);
});
};
12 changes: 11 additions & 1 deletion src/pages/settings/ConfigFieldDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { FC } from "react";
import { useDocs } from "context/useDocs";
import { configDescriptionToHtml } from "util/config";
import { useQuery } from "@tanstack/react-query";
import { fetchDocObjects } from "api/server";

interface Props {
description?: string;
Expand All @@ -9,12 +11,20 @@ interface Props {

const ConfigFieldDescription: FC<Props> = ({ description, className }) => {
const docBaseLink = useDocs();
const objectsInvTxt = useQuery({
queryKey: ["documentation/objects.inv.txt"],
queryFn: fetchDocObjects,
});

return description ? (
<p
className={className}
dangerouslySetInnerHTML={{
__html: configDescriptionToHtml(description, docBaseLink),
__html: configDescriptionToHtml(
description,
docBaseLink,
objectsInvTxt.data,
),
}}
></p>
) : null;
Expand Down
15 changes: 14 additions & 1 deletion src/util/config.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,23 @@ describe("toConfigFields and replaceDocLinks", () => {
const input =
"Specify a Pongo2 template string that represents the snapshot name.\nThis template is used for scheduled snapshots and for unnamed snapshots.\n\nSee {ref}`instance-options-snapshots-names` for more information.";

const result = configDescriptionToHtml(input, "https://docs.example.org");
const result = configDescriptionToHtml(
input,
"https://docs.example.org",
objectsInvTxt.split("\n"),
);

expect(result).toBe(
'Specify a Pongo2 template string that represents the snapshot name.<br>This template is used for scheduled snapshots and for unnamed snapshots.<br><br>See <a href="https://docs.example.org/reference/instance_options/#instance-options-snapshots-names" target="_blank" rel="noreferrer">instance options snapshots names</a> for more information.',
);
});
});

const objectsInvTxt =
"config:option\n" +
" instance-options-nvidia NVIDIA and CUDA configuration : reference/instance_options/#instance-options-nvidia\n" +
" instance-options-qemu Override QEMU configuration : reference/instance_options/#instance-options-qemu\n" +
" instance-options-raw Raw instance configuration overrides : reference/instance_options/#instance-options-raw\n" +
" instance-options-security Security policies : reference/instance_options/#instance-options-security\n" +
" instance-options-snapshots Snapshot scheduling and configuration : reference/instance_options/#instance-options-snapshots\n" +
" instance-options-snapshots-names Automatic snapshot names : reference/instance_options/#instance-options-snapshots-names";
97 changes: 30 additions & 67 deletions src/util/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,79 +24,42 @@ export const toConfigFields = (
return result;
};

const docLinkReplacements = {
"{config:option}`instance-raw:raw.idmap":
"/reference/instance_options/#instance-raw:raw.idmap",
"{config:option}`instance-raw:raw.lxc":
"/reference/instance_options/#instance-raw:raw.lxc",
"{config:option}`instance-raw:raw.qemu":
"/reference/instance_options/#instance-raw:raw.qemu",
"{config:option}`instance-resource-limits:limits.cpu":
"/reference/instance_options/#instance-resource-limits:limits.cpu",
"{config:option}`instance-resource-limits:limits.memory":
"/reference/instance_options/#instance-resource-limits:limits.memory",
"{config:option}`instance-resource-limits:limits.processes":
"/reference/instance_options/#instance-resource-limits:limits.processes",
"{config:option}`instance-security:security.csm":
"/reference/instance_options/#instance-security:security.csm",
"{config:option}`instance-security:security.idmap.isolated":
"/reference/instance_options/#instance-security:security.idmap.isolated",
"{config:option}`instance-security:security.nesting":
"/reference/instance_options/#instance-security:security.nesting",
"{config:option}`instance-security:security.privileged":
"/reference/instance_options/#instance-security:security.privileged",
"{config:option}`instance-security:security.secureboot":
"/reference/instance_options/#instance-security:security.secureboot",
"{config:option}`project-restricted:restricted.devices.disk":
"/reference/projects/#project-restricted:restricted.devices.disk",
"{config:option}`project-restricted:restricted.devices.nic":
"/reference/projects/#project-restricted:restricted.devices.nic",
"{ref}`cluster-evacuate": "/howto/cluster_manage/#cluster-evacuate",
"{ref}`cluster-https-address":
"/howto/cluster_config_networks/#cluster-https-address",
"{ref}`clustering-instance-placement":
"/explanation/clustering/#clustering-instance-placement",
"{ref}`clustering-instance-placement-scriptlet":
"/explanation/clustering/#clustering-instance-placement-scriptlet",
"{ref}`dev-lxd": "/dev-lxd/#dev-lxd",
"{ref}`howto-storage-buckets":
"/howto/storage_buckets/#howto-storage-buckets",
"{ref}`instance-options-limits-cpu":
"/reference/instance_options/#instance-options-limits-cpu",
"{ref}`instance-options-limits-cpu-container":
"/reference/instance_options/#instance-options-limits-cpu-container",
"{ref}`instance-options-qemu":
"/reference/instance_options/#instance-options-qemu",
"{ref}`instance-options-snapshots-names":
"/reference/instance_options/#instance-options-snapshots-names",
"{ref}`instances-limit-units":
"/reference/instance_units/#instances-limit-units",
"{ref}`metrics": "/metrics/#metrics",
"{ref}`network-bgp": "/howto/network_bgp/#network-bgp",
"{ref}`network-dns-server": "/howto/network_zones/#network-dns-server",
"{ref}`server-expose": "/howto/server_expose/#server-expose",
};

export const configDescriptionToHtml = (input: string, docBaseLink: string) => {
export const configDescriptionToHtml = (
input: string,
docBaseLink: string,
objectsInvTxt?: string[],
) => {
// special characters
let result = input
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("\n", "<br>");
.replaceAll("\n", "<br>")
.replaceAll("```", "");

// documentation links
Object.entries(docLinkReplacements).forEach(([key, value]) => {
const href = `${docBaseLink}${value}`;
const linkText = key
.substring(key.lastIndexOf("`") + 1)
.split(":")
.pop()
?.replaceAll("-", " ");
result = result.replaceAll(
key + "`",
`<a href="${href}" target="_blank" rel="noreferrer">${linkText}</a>`,
);
});
if (objectsInvTxt) {
// tags like {ref}`instance-options-qemu` can be in the input string
const linkTags = input.match(/{(config|ref|config:option)}`[a-z-:.]+`/g);
linkTags?.map((tag) => {
const token = tag
.substring(tag.indexOf("`") + 1, tag.lastIndexOf("`"))
?.split(":")
.pop();
if (!token) {
return;
}
// find line with token wrapped in spaces to avoid matching partial tokens
const line = objectsInvTxt.find((item) => item.includes(` ${token} `));
if (!line) {
return;
}
const docPath = line.split(": ")[1];
const linkText = token.replaceAll("-", " ");
const link = `<a href="${docBaseLink}/${docPath}" target="_blank" rel="noreferrer">${linkText}</a>`;

result = result.replaceAll(tag, link);
});
}

// code blocks
let count = 0;
Expand Down
Loading

0 comments on commit 46296dc

Please sign in to comment.