consul/website/components/config-entry-reference/index.jsx

201 lines
6.6 KiB
JavaScript

import { Tabs, Tab } from '@hashicorp/nextjs-scripts/lib/providers/docs'
import EnterpriseAlertBase from '@hashicorp/react-enterprise-alert'
/**
* ConfigEntryReference renders the reference docs for a config entry.
* It creates two tabs, one for HCL docs and one for Kubernetes docs.
*
* @param {array<object>} keys Array of objects, that describe all
* keys that can be set for this config entry.
* @param {boolean} topLevel Indicates this is a reference block that contains
* the top level keys vs a reference block that documents
* nested keys and that is separated out for clarity.
*
* The objects in the keys array support the following keys:
* - name <required>: the name of the HCL key, e.g. Name, Listener. This case sensitive.
* - description <required>: the description of the key. If this key has different descriptions
* for HCL vs. Kube YAML then description can be an object:
* description: {
* hcl: 'HCL description',
* yaml: 'YAML description'
* }
* - hcl <optional>: a boolean to indicate if this key should be shown in the HCL
* documentation. Defaults to true.
* - yaml <optional>: a boolean to indicate if this key should be shown in the YAML
* documentation. Defaults to true.
* - enterprise <optional>: a boolean to indicate if this key is Consul Enterprise
* only. Defaults to false.
* - children <optional>: accepts an array of keys that must be set under this key.
* The schema for these keys is the same as the top level keys.
* - type <optional>: the type and default of this key, e.g. string: "default".
*/
export default function ConfigEntryReference({ keys, topLevel = true }) {
// Kube needs to have its non-top-level keys nested under a "spec" key.
const kubeKeys = topLevel ? toKubeKeys(keys) : keys
return (
<Tabs>
<Tab heading="HCL">{renderKeys(keys, true)}</Tab>
<Tab heading="Kubernetes YAML">{renderKeys(kubeKeys, false)}</Tab>
</Tabs>
)
}
/**
* Renders keys as HTML. It works recursively through all keys.
* @param {array} keys
* @param {boolean} isHCLTab
* @returns {JSX.Element|null}
*/
function renderKeys(keys, isHCLTab) {
if (!keys) return null
return <ul>{keys.map((key) => renderKey(key, isHCLTab))}</ul>
}
/**
* Renders a single key as its HTML element.
*
* @param {object} key
* @param {boolean} isHCLTab
* @returns {JSX.Element|null}
*/
function renderKey(key, isHCLTab) {
if (!key.name) return null
if (isHCLTab && key.hcl === false) return null
if (!isHCLTab && key.yaml === false) return null
const keyName = isHCLTab ? key.name : toYAMLKeyName(key.name)
let description = ''
if (key.description) {
if (typeof key.description === 'string') {
description = key.description
} else if (!isHCLTab && key.description.yaml) {
description = key.description.yaml
} else if (key.description.hcl) {
description = key.description.hcl
}
}
const htmlDescription = description && markdownToHtml(' - ' + description)
const type = key.type && <code>{`(${key.type})`}</code>
const enterpriseAlert = key.enterprise && <EnterpriseAlert inline />
const keyLower = keyName.toLowerCase()
// NOTE: This code copies from https://github.com/hashicorp/remark-plugins/blob/df606efc844319a2532ec54e4cf6ff2d575108ff/plugins/anchor-links/index.js
// to ensure the styling of each bullet is correct. The two locations should be kept
// in sync.
return (
<li key={keyLower} className="g-type-long-body">
<a id={keyLower} className="__target-lic" aria-hidden="" />
<p>
<a
href={'#' + keyLower}
aria-label={keyLower + ' permalink'}
className="__permalink-lic"
>
<code>{keyName}</code>
</a>{' '}
{type}
{enterpriseAlert}
<span dangerouslySetInnerHTML={{ __html: htmlDescription }} />
</p>
{renderKeys(key.children, isHCLTab)}
</li>
)
}
/**
* Constructs a keys object for Kubernetes out of HCL keys.
* Really all this entails is nesting the correct keys under the Kubernetes
* 'spec' key since in HCL there is no 'spec' key.
*
* @param {array} keys
* @returns {array}
*/
function toKubeKeys(keys) {
const topLevelKeys = keys.filter((key) => isTopLevelKubeKey(key.name))
const keysUnderSpec = keys.filter((key) => !isTopLevelKubeKey(key.name))
return topLevelKeys.concat([{ name: 'spec', children: keysUnderSpec }])
}
/**
* Converts an HCL key name to a kube yaml key name.
*
* Examples:
* - Protocol => protocol
* - MeshGateway => meshGateway
* - ACLToken => aclToken
* - HTTP => http
*
* @param {string} hclKey
* @returns {string}
*/
function toYAMLKeyName(hclKey) {
// Handle something like HTTP.
if (hclKey.toUpperCase() === hclKey) {
return hclKey.toLowerCase()
}
let indexFirstLowercaseChar = hclKey
.split('')
.findIndex((c) => c === c.toLowerCase())
// Special case to handle something like ACLToken => aclToken.
if (indexFirstLowercaseChar > 1) {
indexFirstLowercaseChar--
}
let lowercasePortion = ''
for (let i = 0; i < indexFirstLowercaseChar; i++) {
lowercasePortion += hclKey[i].toLowerCase()
}
return (
lowercasePortion + hclKey.split('').slice(indexFirstLowercaseChar).join('')
)
}
/**
* Converts a markdown string to its HTML representation.
* Currently it only supports inline code blocks (e.g. `code here`) and
* links (e.g. [link text](http://link-url) because these were the most
* commonly used markdown features in the key descriptions.
*
* @param {string} markdown the input markdown
* @returns {string}
*/
function markdownToHtml(markdown) {
let html = markdown
// Replace inline code blocks defined by backticks with <code></code>.
while (html.indexOf('`') > 0) {
html = html.replace('`', '<code>')
if (html.indexOf('`') <= 0) {
throw new Error(`'${markdown} does not have matching '\`' characters`)
}
html = html.replace('`', '</code>')
}
// Replace links, e.g. [link text](http://link-url),
// with <a href="http://link-url">link text</a>.
return html.replace(/\[(.*?)]\((.*?)\)/g, '<a href="$2">$1</a>')
}
/**
* Returns true if key is a key used at the top level of a CRD. By top level we
* mean not nested under any other key.
*
* @param {string} name name of the key
*
* @return {boolean}
*/
function isTopLevelKubeKey(name) {
return (
name.toLowerCase() === 'metadata' ||
name.toLowerCase() === 'kind' ||
name.toLowerCase() === 'apiversion'
)
}
function EnterpriseAlert(props) {
return <EnterpriseAlertBase product={'consul'} {...props} />
}