diff --git a/.gitignore b/.gitignore index 254ec132e7..7732be83bb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ node_modules .hugo_build.lock /content/influxdb/*/api/**/*.html /api-docs/redoc-static.html* +/data/api* +/content/influxdb/*/api/** .vscode/* .idea **/config.toml diff --git a/api-docs/.spectral.yaml b/api-docs/.spectral.yaml new file mode 100644 index 0000000000..63790cd08f --- /dev/null +++ b/api-docs/.spectral.yaml @@ -0,0 +1 @@ +extends: ["spectral:oas", "spectral:asyncapi", "spectral:arazzo"] diff --git a/api-docs/cloud-dedicated/management/openapi.yml b/api-docs/cloud-dedicated/management/openapi.yml index 6aa4041113..7ef076091f 100644 --- a/api-docs/cloud-dedicated/management/openapi.yml +++ b/api-docs/cloud-dedicated/management/openapi.yml @@ -13,14 +13,7 @@ info: url: https://opensource.org/licenses/MIT version: '' servers: - - url: https://{baseurl}/api/v0 - description: InfluxDB Cloud Dedicated Management API URL - variables: - baseurl: - enum: - - console.influxdata.com - default: console.influxdata.com - description: InfluxDB Cloud Dedicated Console URL + - url: / security: - bearerAuthManagementToken: [] bearerAuthJwt: [] @@ -37,8 +30,6 @@ tags: See how to [create a management token](/influxdb/cloud-dedicated/admin/tokens/management/). By default, management tokens in InfluxDB v3 are short-lived tokens issued by an OAuth2 identity provider that grant a specific user administrative access to your InfluxDB cluster. However, for automation purposes, you can manually create management tokens that authenticate directly with your InfluxDB cluster and do not require human interaction with your identity provider. - - - name: Database tokens description: Manage database read/write tokens for a cluster - name: Databases @@ -1020,7 +1011,6 @@ paths: For example, see how to [authenticate Telegraf using tokens in your OS secret store](https://github.com/influxdata/telegraf/tree/master/plugins/secretstores/os). If you lose a token, [delete the token from InfluxDB](/influxdb/cloud-dedicated/admin/tokens/database/delete/) and create a new one. - parameters: - name: accountId in: path diff --git a/api-docs/cloud-dedicated/v1-compatibility/swaggerV1Compat.yml b/api-docs/cloud-dedicated/v1-compatibility/swaggerV1Compat.yml index e13b311fed..563f4b69d3 100644 --- a/api-docs/cloud-dedicated/v1-compatibility/swaggerV1Compat.yml +++ b/api-docs/cloud-dedicated/v1-compatibility/swaggerV1Compat.yml @@ -35,6 +35,7 @@ tags: x-traitTag: true + - name: Ping - name: Query - name: Write paths: @@ -244,7 +245,7 @@ paths: get: description: | Reports the InfluxQL bridge querier health and the InfluxDB version of the instance. - + The response is a HTTP `204` status code to inform you the querier is available. For InfluxDB Cloud Dedicated, this endpoint only checks the status of queriers; doesn't check the status of ingesters. @@ -282,7 +283,7 @@ paths: head: description: | Reports the InfluxQL bridge querier health and the InfluxDB version of the instance. - + The response is a HTTP `204` status code to inform you the querier is available. For InfluxDB Cloud Dedicated, this endpoint only checks the status of queriers; doesn't check the status of ingesters. @@ -290,7 +291,6 @@ paths: To check the health of ingesters before writing data, send a request to one of the [write endpoints](/influxdb/cloud-dedicated/api/v2/#tag/Write). This endpoint doesn't require authentication. - operationId: HeadPing responses: '204': @@ -357,9 +357,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -408,12 +408,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object InfluxQLCSVResponse: diff --git a/api-docs/cloud-dedicated/v2/ref.yml b/api-docs/cloud-dedicated/v2/ref.yml index b223b244ed..0d0520428a 100644 --- a/api-docs/cloud-dedicated/v2/ref.yml +++ b/api-docs/cloud-dedicated/v2/ref.yml @@ -12,14 +12,7 @@ info: summary: The InfluxDB v2 HTTP API for InfluxDB Cloud Dedicated provides a v2-compatible programmatic interface for writing data stored in an InfluxDB Cloud Dedicated database. version: '' servers: - - url: https://{baseurl} - description: InfluxDB Cloud Dedicated API URL - variables: - baseurl: - enum: - - cluster-id.a.influxdb.io - default: cluster-id.a.influxdb.io - description: InfluxDB Cloud Dedicated URL + - url: / security: - BearerAuthentication: [] - TokenAuthentication: [] @@ -150,7 +143,7 @@ paths: get: description: | Reports the InfluxQL bridge querier health and the InfluxDB version of the instance. - + The response is a HTTP `204` status code to inform you the querier is available. For InfluxDB Cloud Dedicated, this endpoint only checks the status of queriers; doesn't check the status of ingesters. @@ -188,7 +181,7 @@ paths: head: description: | Reports the InfluxQL bridge querier health and the InfluxDB version of the instance. - + The response is a HTTP `204` status code to inform you the querier is available. For InfluxDB Cloud Dedicated, this endpoint only checks the status of queriers; doesn't check the status of ingesters. @@ -1243,9 +1236,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -1294,12 +1287,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object IntegerLiteral: diff --git a/api-docs/cloud-serverless/v1-compatibility/swaggerV1Compat.yml b/api-docs/cloud-serverless/v1-compatibility/swaggerV1Compat.yml index 44880f9f3e..7aed37ab59 100644 --- a/api-docs/cloud-serverless/v1-compatibility/swaggerV1Compat.yml +++ b/api-docs/cloud-serverless/v1-compatibility/swaggerV1Compat.yml @@ -280,9 +280,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -331,12 +331,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object InfluxQLCSVResponse: diff --git a/api-docs/cloud-serverless/v2/ref.yml b/api-docs/cloud-serverless/v2/ref.yml index 4c6c01654d..0187c892ac 100644 --- a/api-docs/cloud-serverless/v2/ref.yml +++ b/api-docs/cloud-serverless/v2/ref.yml @@ -13,14 +13,7 @@ info: The InfluxDB v2 HTTP API for InfluxDB Cloud Serverless provides a programmatic interface for writing data stored in an InfluxDB Cloud Serverless bucket. version: '' servers: - - url: https://{baseurl} - description: InfluxDB Cloud Serverless API URL - variables: - baseurl: - enum: - - us-east-1-1.aws.cloud2.influxdata.com - default: us-east-1-1.aws.cloud2.influxdata.com - description: InfluxDB Cloud Serverless URL + - url: / security: - TokenAuthentication: [] tags: @@ -10280,9 +10273,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -10331,12 +10324,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object IntegerLiteral: diff --git a/api-docs/cloud/v1-compatibility/swaggerV1Compat.yml b/api-docs/cloud/v1-compatibility/swaggerV1Compat.yml index 56853fefa1..3f9e0a1e7b 100644 --- a/api-docs/cloud/v1-compatibility/swaggerV1Compat.yml +++ b/api-docs/cloud/v1-compatibility/swaggerV1Compat.yml @@ -276,9 +276,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -327,12 +327,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object InfluxQLCSVResponse: diff --git a/api-docs/cloud/v2/ref.yml b/api-docs/cloud/v2/ref.yml index df84bf180b..2866b2efef 100644 --- a/api-docs/cloud/v2/ref.yml +++ b/api-docs/cloud/v2/ref.yml @@ -12,14 +12,7 @@ info: url: https://opensource.org/licenses/MIT summary: The InfluxDB v2 HTTP API provides a programmatic interface for all interactions with InfluxDB v2. servers: - - url: https://{baseurl} - description: InfluxDB Cloud API URL - variables: - baseurl: - enum: - - us-east-1-1.aws.cloud2.influxdata.com - default: us-east-1-1.aws.cloud2.influxdata.com - description: InfluxDB Cloud URL + - url: / security: - TokenAuthentication: [] tags: @@ -14600,9 +14593,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -14651,12 +14644,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object IntegerLiteral: diff --git a/api-docs/clustered/v1-compatibility/swaggerV1Compat.yml b/api-docs/clustered/v1-compatibility/swaggerV1Compat.yml index 8f3a1373a6..91eb303736 100644 --- a/api-docs/clustered/v1-compatibility/swaggerV1Compat.yml +++ b/api-docs/clustered/v1-compatibility/swaggerV1Compat.yml @@ -280,9 +280,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -331,12 +331,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object InfluxQLCSVResponse: diff --git a/api-docs/clustered/v2/ref.yml b/api-docs/clustered/v2/ref.yml index b617a71033..42c01f9599 100644 --- a/api-docs/clustered/v2/ref.yml +++ b/api-docs/clustered/v2/ref.yml @@ -12,14 +12,7 @@ info: url: https://opensource.org/licenses/MIT summary: The InfluxDB v2 HTTP API for InfluxDB Clustered provides a v2-compatible programmatic interface for writing data stored in an InfluxDB Clustered database. servers: - - url: https://{baseurl} - description: InfluxDB Clustered API URL - variables: - baseurl: - enum: - - cluster-host.com - default: cluster-host.com - description: InfluxDB Clustered URL + - url: / security: - BearerAuthentication: [] - TokenAuthentication: [] @@ -1235,9 +1228,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -1286,12 +1279,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object IntegerLiteral: diff --git a/api-docs/create-pages.mjs b/api-docs/create-pages.mjs new file mode 100644 index 0000000000..ad33ab59e7 --- /dev/null +++ b/api-docs/create-pages.mjs @@ -0,0 +1,104 @@ +/** + * This script generates markdown files for each endpoint in the + * configured OpenAPI specs. + */ + +import { writeFileSync, rmSync, readFileSync, existsSync, mkdirSync } from 'fs'; +import * as yaml from 'js-yaml'; +import { execCommand, getSwagger, isPlaceholderFragment } from './util.mjs'; +import { apis } from './templates.mjs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +// Get the current file's directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +function getPathGroups(openapi) { + const pathGroups = {}; + Object.keys(openapi.paths).sort() + .forEach((p) => { + const delimiter = '/'; + let key = p.split(delimiter); + let isItemPath = isPlaceholderFragment(key[key.length - 1]); + if(isItemPath) { + key = key.slice(0, -1); + } + key = (key.slice(0, 4)) + isItemPath = isPlaceholderFragment(key[key.length - 1]); + if(isItemPath) { + key = key.slice(0, -1); + } + const groupKey = key.join('/'); + pathGroups[groupKey] = pathGroups[groupKey] || {}; + pathGroups[groupKey][p] = openapi.paths[p]; + }); + return pathGroups; +} + +function createIndexPage(target_dir, spec, pageParams) { + const menuKey = Object.keys(pageParams.menu)[0]; + pageParams.menu[menuKey].name = spec.info.title; + pageParams.title = spec.info.title; + pageParams.description = spec.info.description; + + let page = (JSON.stringify(pageParams)).concat('\n\n', spec.info.description, '\n\n', '{{< children >}}', '\n\n'); + + const pagePath = path.join(target_dir, '_index.md'); + writeFileSync(pagePath, page); + console.log(`Created: ${pagePath}`); +} + +function createPathGroupPage(targetDir, pathGroup, pathSpec, pageParams) { + pathSpec['x-pathGroupTitle'] = `${pathGroup}\n${pathSpec.info.title}`; + pathSpec['x-pathGroup'] = pathGroup; + + pageParams.title = pathSpec['x-pathGroupTitle']; + pageParams.description = pathSpec.info.description; + pageParams.api = { + spec: JSON.stringify(pathSpec), + }; + const menuKey = Object.keys(pageParams.menu)[0]; + pageParams.menu[menuKey].parent = pathSpec.info.title; + pageParams.menu[menuKey].weight = 1; + pageParams.menu[menuKey].name = pathGroup; + // Create a unique identifier for the menu item + pageParams.menu[menuKey].identifier = (`${pageParams.api_name}_${pathGroup}`).replace(/-/g, '_'); + + console.log(pageParams); + const pageName = `${pathGroup.replaceAll('/', '-').replace(/^-/, '')}`; + const pagePath = path.join(targetDir, `${pageName}.md`); + writeFileSync(pagePath, JSON.stringify(pageParams)); + console.log(`Created: ${pagePath}`); +} + +export function createAPIPages(pageParams, specPath, docPath) { + // Execute the script to fetch and bundle the configured spec. + execCommand(`${getSwagger} ${pageParams.api_name} -B`); + + console.log(`Creating pages for: ${pageParams.api_name} ${specPath}`); + const spec = yaml.load(readFileSync(specPath, 'utf8')); + + if (!existsSync(docPath)) { + mkdirSync(docPath, { recursive: true }); + }; + + const pageParamsClone = JSON.stringify(pageParams); + createIndexPage(docPath, spec, JSON.parse(pageParamsClone)); + + const pathGroups = getPathGroups(spec); + Object.keys(pathGroups).forEach( pathGroup => { + // Deep copy the spec object + const specClone = JSON.parse(JSON.stringify(spec)); + specClone.paths = pathGroups[pathGroup]; + createPathGroupPage(docPath, pathGroup, specClone, JSON.parse(pageParamsClone)); + }); +} + +export function deleteAPIPages(docPath) { + try { + rmSync(docPath, {recursive: true, force: true}); + } catch (error) { + console.error(`Error deleting API pages: ${docPath}`); + } +} diff --git a/api-docs/getswagger.sh b/api-docs/getswagger.sh index 8d0fd3061a..0a1491fb7c 100755 --- a/api-docs/getswagger.sh +++ b/api-docs/getswagger.sh @@ -21,10 +21,13 @@ # sh ./getswagger.sh -c -o -B # # Examples: -# sh ./getswagger.sh cloud-serverless +# sh ./getswagger.sh cloud-serverless-v2 # sh ./getswagger.sh clustered -B -# sh ./getswagger.sh cloud -# sh ./getswagger.sh -c v2 -o v2.0 -b file:///Users/johnsmith/github/openapi +# sh ./getswagger.sh cloud-v2 +# sh ./getswagger.sh -c oss-v2 -b file:///Users/johnsmith/github/openapi + +DOCS_ROOT=$(git rev-parse --show-toplevel) +API_DOCS_ROOT=$DOCS_ROOT/api-docs versionDirs=($(ls -d */)) latestOSS=${versionDirs[${#versionDirs[@]}-1]} @@ -45,8 +48,8 @@ function showHelp { echo " ./getswagger.sh cloud" echo " ./getswagger.sh cloud-dedicated" echo " ./getswagger.sh cloud-serverless" - echo " ./getswagger.sh oss -o -V" - echo " ./getswagger.sh all -o " + echo " ./getswagger.sh v2 -V" + echo " ./getswagger.sh all" echo "Commands:" echo "-b The base URL to fetch from." echo " ex. ./getswagger.sh -b file:///Users/yourname/github/openapi" @@ -62,7 +65,7 @@ function showHelp { subcommand=$1 case "$subcommand" in - cloud-dedicated-v2|cloud-dedicated-management|cloud-serverless-v2|clustered-v2|cloud-v2|v2|v1-compat|all) + cloud-dedicated-v2|cloud-dedicated-v1-compatibility|cloud-dedicated-management|cloud-serverless-v2|cloud-serverless-v1-compatibility|clustered-v2|clustered-v1-compatibility|cloud-v2|cloud-v1-compatibility|v2-v2|v2-v1-compatibility|all|v1-compat) product=$1 shift @@ -83,9 +86,6 @@ case "$subcommand" in baseUrl=$OPTARG baseUrlOSS=$OPTARG ;; - o) - ossVersion=$OPTARG - ;; \?) echo "Invalid option: $OPTARG" 1>&2 showHelp @@ -105,7 +105,6 @@ esac function showArgs { echo "product: $product"; echo "baseUrl: $baseUrl"; - echo "ossVersion: $ossVersion"; } function postProcess() { @@ -113,11 +112,12 @@ function postProcess() { # npm_config_yes=true npx overrides the prompt # and (vs. npx --yes) is compatible with npm@6 and npm@7. specPath="$1" + # Replace the .yml extension in specPath with .json. + specJsonPath="${specPath%.yml}.json" configPath="$2" api="$3" openapiCLI=" @redocly/cli" - currentPath=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) # TODO: Move some of this into the plugin: @@ -131,101 +131,146 @@ function postProcess() { npx --version INFLUXDB_PRODUCT=$(dirname "$configPath") \ INFLUXDB_API_NAME=$(echo "$api" | sed 's/@.*//g;') \ - API_DOCS_ROOT_PATH=$currentPath \ + API_DOCS_ROOT_PATH=$API_DOCS_ROOT \ npm_config_yes=true \ npx $openapiCLI bundle $specPath \ - -o $specPath \ - --config=$configPath + --config=$configPath \ + --ext yml \ + --output $specPath #\ + # TODO: Uncommend this after fixing the circular reference ($ref) issues in the specs. + # && npx $openapiCLI bundle $specPath \ + # --dereferenced \ + # --ext json \ + # --output $specJsonPath } - function updateCloudV2 { - outFile="cloud/v2/ref.yml" +function updateCloudV2 { + outFile="$API_DOCS_ROOT/cloud/v2/ref.yml" if [[ -z "$baseUrl" ]]; then - echo "Using existing $outFile" + echo "No URL was provided. I'll rebuild from the existing spec $outFile" else curl $UPDATE_OPTIONS ${baseUrl}/contracts/ref/cloud.yml -o $outFile fi - postProcess $outFile 'cloud/.config.yml' v2@2 + postProcess $outFile "$API_DOCS_ROOT/cloud/.config.yml" v2@2 } function updateCloudDedicatedManagement { - outFile="cloud-dedicated/management/openapi.yml" + outFile="$API_DOCS_ROOT/cloud-dedicated/management/openapi.yml" if [[ -z "$baseUrl" ]]; then - echo "Using existing $outFile" + echo "No URL was provided. I'll rebuild from the existing spec $outFile" else curl $UPDATE_OPTIONS https://raw.githubusercontent.com/influxdata/granite/ab7ee2aceacfae7f415d15ffbcf8c9d0f6f3e015/openapi.yaml -o $outFile fi - postProcess $outFile 'cloud-dedicated/.config.yml' management@0 + postProcess $outFile "$API_DOCS_ROOT/cloud-dedicated/.config.yml" management@0 } function updateCloudDedicatedV2 { - outFile="cloud-dedicated/v2/ref.yml" + outFile="$API_DOCS_ROOT/cloud-dedicated/v2/ref.yml" if [[ -z "$baseUrl" ]]; then - echo "Using existing $outFile" + echo "No URL was provided. I'll rebuild from the existing spec $outFile" else curl $UPDATE_OPTIONS ${baseUrl}/contracts/ref/cloud.yml -o $outFile fi - postProcess $outFile 'cloud-dedicated/.config.yml' v2@2 + postProcess $outFile "$API_DOCS_ROOT/cloud-dedicated/.config.yml" v2@2 } function updateClusteredV2 { - outFile="clustered/v2/ref.yml" + outFile="$API_DOCS_ROOT/clustered/v2/ref.yml" if [[ -z "$baseUrl" ]]; then - echo "Using existing $outFile" + echo "No URL was provided. I'll rebuild from the existing spec $outFile" else curl $UPDATE_OPTIONS ${baseUrl}/contracts/ref/cloud.yml -o $outFile fi - postProcess $outFile 'clustered/.config.yml' v2@2 + postProcess $outFile "$API_DOCS_ROOT/clustered/.config.yml" v2@2 } function updateCloudServerlessV2 { - outFile="cloud-serverless/v2/ref.yml" + outFile="$API_DOCS_ROOT/cloud-serverless/v2/ref.yml" if [[ -z "$baseUrl" ]]; then - echo "Using existing $outFile" + echo "No URL was provided. I'll rebuild from the existing spec $outFile" else curl $UPDATE_OPTIONS ${baseUrl}/contracts/ref/cloud.yml -o $outFile fi - postProcess $outFile 'cloud-serverless/.config.yml' v2@2 + postProcess $outFile "$API_DOCS_ROOT/cloud-serverless/.config.yml" v2@2 } -function updateOSSV2 { - outFile="v2/ref.yml" - if [[ -z "$baseUrlOSS" ]]; +function updateV2V2 { + outFile="$API_DOCS_ROOT/v2/v2/ref.yml" + if [[ -z "$baseUrl" ]]; then - echo "Using existing $outFile" + echo "No URL was provided. I'll rebuild from the existing spec $outFile" else - curl $UPDATE_OPTIONS ${baseUrlOSS}/contracts/ref/oss.yml -o $outFile + curl $UPDATE_OPTIONS ${baseUrl}/contracts/ref/oss.yml -o $outFile fi - postProcess $outFile 'v2/.config.yml' '@2' + postProcess $outFile "$API_DOCS_ROOT/v2/.config.yml" 'v2@2' } -function updateV1Compat { - outFile="cloud/v1-compatibility/swaggerV1Compat.yml" +function updateCloudV1Compat { + outFile="$API_DOCS_ROOT/cloud/v1-compatibility/swaggerV1Compat.yml" if [[ -z "$baseUrl" ]]; then - echo "Using existing $outFile" + echo "No URL was provided. I'll rebuild from the existing spec $outFile" else - curl $UPDATE_OPTIONS ${baseUrl}/contracts/swaggerV1Compat.yml -o $outFile + curl $UPDATE_OPTIONS ${baseUrl}/contracts/swaggerV1Compat.yml -o $outFile fi - postProcess $outFile 'cloud/.config.yml' 'v1-compatibility' + postProcess $outFile "$API_DOCS_ROOT/cloud/.config.yml" 'v1-compatibility' +} - outFile="v2/v1-compatibility/swaggerV1Compat.yml" - cp cloud/v1-compatibility/swaggerV1Compat.yml $outFile - postProcess $outFile 'v2/.config.yml' 'v1-compatibility' +function updateCloudDedicatedV1Compat { + outFile="$API_DOCS_ROOT/cloud-dedicated/v1-compatibility/swaggerV1Compat.yml" + if [[ -z "$baseUrl" ]]; + then + echo "No URL was provided. I'll rebuild from the existing spec $outFile" + else + curl $UPDATE_OPTIONS ${baseUrl}/contracts/swaggerV1Compat.yml -o $outFile + fi + postProcess $outFile "$API_DOCS_ROOT/cloud-dedicated/.config.yml" 'v1-compatibility' +} + +function updateCloudServerlessV1Compat { + outFile="$API_DOCS_ROOT/cloud-serverless/v1-compatibility/swaggerV1Compat.yml" + if [[ -z "$baseUrl" ]]; + then + echo "No URL was provided. I'll rebuild from the existing spec $outFile" + else + curl $UPDATE_OPTIONS ${baseUrl}/contracts/swaggerV1Compat.yml -o $outFile + fi + postProcess $outFile "$API_DOCS_ROOT/cloud-serverless/.config.yml" 'v1-compatibility' +} - outFile="cloud-dedicated/v1-compatibility/swaggerV1Compat.yml" - postProcess $outFile 'cloud-dedicated/.config.yml' 'v1-compatibility' +function updateClusteredV1Compat { + outFile="$API_DOCS_ROOT/clustered/v1-compatibility/swaggerV1Compat.yml" + if [[ -z "$baseUrl" ]]; + then + echo "No URL was provided. I'll rebuild from the existing spec $outFile" + else + curl $UPDATE_OPTIONS ${baseUrl}/contracts/swaggerV1Compat.yml -o $outFile + fi + postProcess $outFile "$API_DOCS_ROOT/clustered/.config.yml" 'v1-compatibility' +} - outFile="cloud-serverless/v1-compatibility/swaggerV1Compat.yml" - postProcess $outFile 'cloud-serverless/.config.yml' 'v1-compatibility' +function updateV2V1Compatibility { + outFile="$API_DOCS_ROOT/v2/v1-compatibility/swaggerV1Compat.yml" + if [[ -z "$baseUrl" ]]; + then + echo "No URL was provided. I'll rebuild from the existing spec $outFile" + else + curl $UPDATE_OPTIONS ${baseUrl}/contracts/swaggerV1Compat.yml -o $outFile + fi + postProcess $outFile "$API_DOCS_ROOT/v2/.config.yml" 'v1-compatibility' +} - outFile="clustered/v1-compatibility/swaggerV1Compat.yml" - postProcess $outFile 'clustered/.config.yml' 'v1-compatibility' +function updateV1Compat { + updateCloudV1Compat + updateCloudDedicatedV1Compat + updateCloudServerlessV1Compat + updateClusteredV1Compat + updateV2V1Compatibility } UPDATE_OPTIONS="--fail" @@ -252,9 +297,24 @@ then elif [ "$product" = "clustered-v2" ]; then updateClusteredV2 -elif [ "$product" = "v2" ]; +elif [ "$product" = "v2-v2" ]; +then + updateV2V2 +elif [ "$product" = "cloud-dedicated-v1-compatibility" ]; +then + updateCloudDedicatedV1Compat +elif [ "$product" = "cloud-v1-compatibility" ]; +then + updateCloudV1Compat +elif [ "$product" = "cloud-serverless-v1-compatibility" ]; +then + updateCloudServerlessV1Compat +elif [ "$product" = "clustered-v1-compatibility" ]; +then + updateClusteredV1Compat +elif [ "$product" = "v2-v1-compatibility" ]; then - updateOSSV2 + updateV2V1Compatibility elif [ "$product" = "v1-compat" ]; then updateV1Compat @@ -265,9 +325,9 @@ then updateCloudDedicatedManagement updateCloudServerlessV2 updateClusteredV2 - updateOSSV2 + updateV2V2 updateV1Compat else - echo "Provide a product argument: cloud-v2, cloud-serverless-v2, cloud-dedicated-v2, clustered-v2, v2, v1-compat, or all." + echo "Provide a product argument: cloud-v2, cloud-serverless-v2, cloud-dedicated-v2, clustered-v2, v2-v2, v1-compat, or all." showHelp fi diff --git a/api-docs/index.mjs b/api-docs/index.mjs new file mode 100644 index 0000000000..92f5b7e218 --- /dev/null +++ b/api-docs/index.mjs @@ -0,0 +1,71 @@ +import { createAPIPages, deleteAPIPages } from './create-pages.mjs'; +import { readFileSync } from 'fs'; +import * as yaml from 'js-yaml'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +// Get the current file's directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Define the root directory for the docs +const DOCS_ROOT = path.resolve(__dirname, '..'); + +function getAPIConfigs() { + // Load the products data + const products = yaml.load(readFileSync(path.join(DOCS_ROOT, '/data/products.yml'), 'utf8')); + + const configs = []; + + Object.keys(products).forEach(productKey => { + const product = products[productKey]; + const productVersion = product.versions[0]; + let menuName = productKey; + if (menuName === 'influxdb') { + menuName = `${menuName}_${productVersion}`; + } + + if (!product.apis) { + return; + } + + const { apis } = yaml.load(readFileSync(path.join(DOCS_ROOT, product.apis), 'utf8')); + + Object.keys(apis).forEach(version => { + const [ apiShortName ] = version.split('@'); + const api = apis[version]; + api.doc_path = path.join(DOCS_ROOT, `content/influxdb/${productVersion}/api/${apiShortName}`); + api.spec_path = path.join(DOCS_ROOT, 'api-docs', productVersion, api.root); + const menu = {} + menu[productKey] = { + parent: 'InfluxDB HTTP API', + weight: 0, + }, + api.pageParams = { + type: 'api', + title: `${product.name} ${apiShortName}`, + description: 'InfluxDB API documentation', + menu, + tags: [productKey, 'api'], + api_version: version, + api_name: `${productVersion}-${apiShortName}`, + }; + configs.push(api); + }); + }); + return configs; +} + +function buildAPIPages() { + console.log(getAPIConfigs()); + getAPIConfigs().forEach(api => { + // Delete existing pages for each API; + deleteAPIPages(api.doc_path); + + // Create pages for each; + createAPIPages(api.pageParams, api.spec_path, api.doc_path); + }); +} + +buildAPIPages(); +//console.log(getAPIConfigs()); \ No newline at end of file diff --git a/api-docs/package.json b/api-docs/package.json deleted file mode 100755 index 1e81d828db..0000000000 --- a/api-docs/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "private": true, - "name": "api-docs", - "version": "1.0.0", - "description": "InfluxDB API documentation", - "license": "MIT", - "dependencies": { - "js-yaml": "^4.1.0" - } -} diff --git a/api-docs/templates.mjs b/api-docs/templates.mjs new file mode 100644 index 0000000000..3f59e05d3d --- /dev/null +++ b/api-docs/templates.mjs @@ -0,0 +1,20 @@ +export const apis = [ + { + name: 'influxdb_cloud', + menu_name: 'influxdb_cloud', + menu: + { + parent: 'INFLUXDB HTTP API', + weight: 0, + }, + }, + { + name: 'influxdb', + menu_name: 'influxdb_v2', + menu: + { + parent: 'INFLUXDB HTTP API', + weight: 0, + }, + } +]; diff --git a/api-docs/util.mjs b/api-docs/util.mjs new file mode 100644 index 0000000000..4d9d639470 --- /dev/null +++ b/api-docs/util.mjs @@ -0,0 +1,26 @@ +import { execSync } from 'child_process'; +import process from 'process'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +// Get the current file's directory +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Function to execute shell commands +export function execCommand(command) { + try { + console.log(`Executing: ${command}`); + execSync(command, { stdio: 'inherit' }); + } catch (error) { + console.error(`Error executing command: ${command}`); + process.exit(1); + } +}; + +export const getSwagger = path.join(__dirname, 'getswagger.sh'); + +export function isPlaceholderFragment(str) { + const placeholderRegex = new RegExp('^\{.*\}$'); + return placeholderRegex.test(str); +}; diff --git a/api-docs/v2/.config.yml b/api-docs/v2/.config.yml index ffcc1e8852..db660d83b7 100644 --- a/api-docs/v2/.config.yml +++ b/api-docs/v2/.config.yml @@ -6,8 +6,8 @@ extends: x-influxdata-product-name: InfluxDB v2 OSS apis: - '@2': - root: ref.yml + v2@2: + root: v2/ref.yml x-influxdata-default: true v1-compatibility@2: root: v1-compatibility/swaggerV1Compat.yml diff --git a/api-docs/v2/v1-compatibility/swaggerV1Compat.yml b/api-docs/v2/v1-compatibility/swaggerV1Compat.yml index b008e03153..b509c282a9 100644 --- a/api-docs/v2/v1-compatibility/swaggerV1Compat.yml +++ b/api-docs/v2/v1-compatibility/swaggerV1Compat.yml @@ -276,9 +276,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -327,12 +327,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object InfluxQLCSVResponse: diff --git a/api-docs/v2/ref.yml b/api-docs/v2/v2/ref.yml similarity index 99% rename from api-docs/v2/ref.yml rename to api-docs/v2/v2/ref.yml index 28fbe371e2..2179ecd684 100644 --- a/api-docs/v2/ref.yml +++ b/api-docs/v2/v2/ref.yml @@ -15515,9 +15515,9 @@ components: properties: results: description: | - A resultset object that contains the `statement_id` and the `series` array. + A resultset object that contains the `statement_id` and the `series` array. - Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. + Except for `statement_id`, all properties are optional and omitted if empty. If a property is not present, it is assumed to be `null`. items: properties: error: @@ -15566,12 +15566,12 @@ components: type: integer type: object oneOf: - - required: - - statement_id - - error - - required: - - statement_id - - series + - required: + - statement_id + - error + - required: + - statement_id + - series type: array type: object IntegerLiteral: diff --git a/api-docs/validate-spec.mjs b/api-docs/validate-spec.mjs new file mode 100644 index 0000000000..5e38228455 --- /dev/null +++ b/api-docs/validate-spec.mjs @@ -0,0 +1,34 @@ +/** This script contains functions for running various + * OpenAPI spec validation tools. + */ +import { execCommand } from './util.mjs'; +import { apis } from './templates.mjs'; +// import SwaggerParser from '@apidevtools/swagger-parser'; + +function validate(spec) { + + // detectCircularRefs(); + + // // swagger-cli validate + // execCommand(`npx swagger-cli validate ${spec}`); + + // // speccy lint. Treat $ref like JSON schema and convert to OpenAPI Schema Objects. + // execCommand(`npx speccy lint -j -v ${spec}`); + + // execCommand(`npx @redocly/cli lint ${spec}`); + + // Create a Spectral ruleset file + // Spectral is a flexible JSON/YAML linter, formatter, and style checker for OpenAPI v2, v3.0, v3.1, and AsyncAPI v2.0. + // For rule examples, see https://apistylebook.stoplight.io/docs/stoplight-style-guide/ + execCommand(`npx @stoplight/spectral-cli lint ${spec} --ruleset ./api-docs/.spectral.yaml`); // --ruleset myruleset.yaml +} + +const influxdbHttpSpecs = apis.map((api) => api.spec_file); + +export function validateAll(specs) { + if (specs.length === 0) { + // Set default specs + specs = influxdbHttpSpecs; + } + specs.forEach(validate); +} diff --git a/api-docs/yarn.lock b/api-docs/yarn.lock deleted file mode 100644 index e79302e282..0000000000 --- a/api-docs/yarn.lock +++ /dev/null @@ -1,15 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" diff --git a/assets/js/FluxGroupKeys.js b/assets/js/FluxGroupKeys.js new file mode 100644 index 0000000000..f05f58a179 --- /dev/null +++ b/assets/js/FluxGroupKeys.js @@ -0,0 +1,6 @@ +import { groupData } from './flux-group-keys.js'; + +export default function FluxGroupKeys() { + // Group and render tables on load + document.querySelector(tablesElement).addEventListener('load', groupData()); +}; \ No newline at end of file diff --git a/assets/js/Sidebar.js b/assets/js/Sidebar.js new file mode 100644 index 0000000000..a2c648ab1d --- /dev/null +++ b/assets/js/Sidebar.js @@ -0,0 +1,5 @@ +import { setSidebarState } from './sidebar-toggle.js'; + +export default function Sidebar() { + setSidebarState(); +} \ No newline at end of file diff --git a/assets/js/ThemeStyle.js b/assets/js/ThemeStyle.js new file mode 100644 index 0000000000..9c4aef740a --- /dev/null +++ b/assets/js/ThemeStyle.js @@ -0,0 +1,5 @@ +import { setStyleFromCookie } from './docs-themes.js'; + +export default function ThemeStyle() { + setStyleFromCookie(); +} \ No newline at end of file diff --git a/assets/js/api-doc/ApiReferencePage.js b/assets/js/api-doc/ApiReferencePage.js new file mode 100644 index 0000000000..511a3f3d84 --- /dev/null +++ b/assets/js/api-doc/ApiReferencePage.js @@ -0,0 +1,15 @@ +import { setStyles, setServerUrl, onPreferenceChanged } from './index.js'; + +export default function ApiReferencePage() { + const rapidocEl = document.getElementById('api-doc'); + if(rapidocEl === null) return; + setStyles(rapidocEl); + setServerUrl(rapidocEl); + rapidocEl.loadSpec(JSON.parse(rapidocEl.dataset.openapiSpec)); + rapidocEl.addEventListener('spec-loaded', (e) => {}); + cookieStore.addEventListener('change', (e) => { + if(e.changed) { + onPreferenceChanged(e, rapidocEl); + } + }); +} \ No newline at end of file diff --git a/assets/js/api-doc/index.js b/assets/js/api-doc/index.js new file mode 100644 index 0000000000..4faab88195 --- /dev/null +++ b/assets/js/api-doc/index.js @@ -0,0 +1,52 @@ +import 'rapidoc'; +import { getPreference } from '../cookies.js'; +import { getUrls } from '../influxdb-url.js'; + +function getUserPreferredUrl() { + const urlName = getPreference('influxdb_url'); + return getUrls()[urlName]; +} + +export function setServerUrl(el) { + const baseUrl = getUserPreferredUrl(); + el.setAttribute('server-url', baseUrl); + el.setApiServer(baseUrl); +} + +const darkThemeAttributes = { + 'theme': 'dark', +}; + +const lightThemeAttributes = { + 'theme': 'light', +}; + +export function setStyles(el) { + let theme = getPreference('theme') || 'light'; + theme = theme.replace(/-theme/, ''); + let themeAttributes = { + 'render-style': 'view', + 'show-header': 'false', + 'show-info': 'false', + 'style': 'height:100vh; width:100%', + } + switch (theme) { + case 'light': + themeAttributes = { ...themeAttributes, ...lightThemeAttributes }; + break; + case 'dark': + themeAttributes = { ...themeAttributes, ...darkThemeAttributes }; + break; + } + + for (const [key, value] of Object.entries(themeAttributes)) { + el.setAttribute(key, value); + } +} + +export function onPreferenceChanged(e) { + const rapidocEl = document.getElementById('api-doc'); + if(rapidocEl === null) return; + setStyles(rapidocEl); + setServerUrl(rapidocEl); +} diff --git a/assets/js/api-libs.js b/assets/js/api-libs.js index 7cd5a75739..52f8a599b2 100644 --- a/assets/js/api-libs.js +++ b/assets/js/api-libs.js @@ -1,6 +1,8 @@ //////////////////////////////////////////////////////////////////////////////// ///////////////// Preferred Client Library programming language /////////////// //////////////////////////////////////////////////////////////////////////////// +import { setPreference, getPreference } from './cookies.js'; +import { activateTabs, updateBtnURLs } from './tabbed-content.js'; function getVisitedApiLib () { const path = window.location.pathname.match( @@ -35,3 +37,10 @@ var tab = getApiLibPreference(); selector => activateTabs(selector, tab), updateBtnURLs(tab) ); + +export { + getApiLibPreference, + setApiLibPreference, + getVisitedApiLib, + isApiLib, +}; diff --git a/assets/js/code-controls.js b/assets/js/code-controls.js index 76589de43b..201c2576fc 100644 --- a/assets/js/code-controls.js +++ b/assets/js/code-controls.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + var codeBlockSelector = ".article--content pre"; var codeBlocks = $(codeBlockSelector); diff --git a/assets/js/code-placeholders.js b/assets/js/code-placeholders.js index 11cc9af274..cef02aa375 100644 --- a/assets/js/code-placeholders.js +++ b/assets/js/code-placeholders.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + const placeholderWrapper = '.code-placeholder-wrapper'; const placeholderElement = 'var.code-placeholder'; const codePlaceholders = $(placeholderElement); diff --git a/assets/js/content-interactions.js b/assets/js/content-interactions.js index 3a1737f76f..c99be1aeb8 100644 --- a/assets/js/content-interactions.js +++ b/assets/js/content-interactions.js @@ -1,4 +1,28 @@ ///////////////////////////// Make headers linkable ///////////////////////////// +import $ from 'jquery'; +import { scrollToAnchor } from './scroll.js'; + +// Expand accordions on load based on URL anchor +function openAccordionByHash() { + var anchor = window.location.hash; + + function expandElement() { + if ($(anchor).parents('.expand').length > 0) { + return $(anchor).closest('.expand').children('.expand-label'); + } else if ($(anchor).hasClass('expand')){ + return $(anchor).children('.expand-label'); + } + }; + + if (expandElement() != null) { + if (expandElement().children('.expand-toggle').hasClass('open')) {} + else { + expandElement().children('.expand-toggle').trigger('click'); + }; + }; +}; + +function contentInteractions () { var headingWhiteList = $("\ .article--content h2, \ @@ -12,7 +36,7 @@ var headingBlackList = ("\ .influxdbu-banner h4 \ "); -headingElements = headingWhiteList.not(headingBlackList); +const headingElements = headingWhiteList.not(headingBlackList); headingElements.each(function() { function getLink(element) { @@ -33,26 +57,6 @@ var elementWhiteList = [ "a.fullscreen-close" ] -function scrollToAnchor(target) { - var $target = $(target); - if($target && $target.length > 0) { - $('html, body').stop().animate({ - 'scrollTop': ($target.offset().top) - }, 400, 'swing', function () { - window.location.hash = target; - }); - - // Unique accordion functionality - // If the target is an accordion element, open the accordion after scrolling - if ($target.hasClass('expand')) { - if ($(target + ' .expand-label .expand-toggle').hasClass('open')) {} - else { - $(target + '> .expand-label').trigger('click'); - }; - }; - } -} - $('.article a[href^="#"]:not(' + elementWhiteList + ')').click(function (e) { e.preventDefault(); scrollToAnchor(this.hash); @@ -98,25 +102,7 @@ $('.expand-label').click(function() { $(this).next('.expand-content').slideToggle(200) }) -// Expand accordions on load based on URL anchor -function openAccordionByHash() { - var anchor = window.location.hash; - function expandElement() { - if ($(anchor).parents('.expand').length > 0) { - return $(anchor).closest('.expand').children('.expand-label'); - } else if ($(anchor).hasClass('expand')){ - return $(anchor).children('.expand-label'); - } - }; - - if (expandElement() != null) { - if (expandElement().children('.expand-toggle').hasClass('open')) {} - else { - expandElement().children('.expand-toggle').trigger('click'); - }; - }; -}; // Open accordions by hash on page load. openAccordionByHash() @@ -152,3 +138,9 @@ $('.article--content a').each(function() { $(this).attr('target', '_blank'); }; }) + +} + +export default function ContentInteractions() { + contentInteractions(); +} diff --git a/assets/js/cookies.js b/assets/js/cookies.js index 4567941834..84969fb159 100644 --- a/assets/js/cookies.js +++ b/assets/js/cookies.js @@ -13,22 +13,11 @@ - callouts: Feature callouts that have been seen (array) - influxdata_docs_ported: Temporary cookie to help port old cookies to new structure */ +import Cookies from 'js-cookie'; // Prefix for all InfluxData docs cookies const cookiePrefix = 'influxdata_docs_'; -/* - Initialize a cookie with a default value. -*/ -initializeCookie = (cookieName, defaultValue) => { - fullCookieName = cookiePrefix + cookieName; - - // Check if the cookie exists before initializing the cookie - if (Cookies.get(fullCookieName) === undefined) { - Cookies.set(fullCookieName, defaultValue); - } -}; - // Initialize all InfluxData docs cookies with defaults /* @@ -49,11 +38,22 @@ var defaultPref = { v3_wayfinding_show: true, }; +/* + Initialize a cookie with a default value. +*/ +function initializeCookie (cookieName, defaultValue) { + const fullCookieName = cookiePrefix + cookieName; + if (typeof defaultValue !== 'string') { + val = JSON.stringify(defaultValue); + Cookies.set(fullCookieName, val); + } +}; + /* Retrieve a preference from the preference cookie. If the cookie doesn't exist, initialize it with default values. */ -getPreference = prefName => { +const getPreference = prefName => { // Initialize the preference cookie if it doesn't already exist if (Cookies.get(prefCookieName) === undefined) { initializeCookie('preferences', defaultPref); @@ -68,17 +68,17 @@ getPreference = prefName => { }; // Set a preference in the preferences cookie -setPreference = (prefID, prefValue) => { +const setPreference = (prefID, prefValue) => { var prefString = Cookies.get(prefCookieName); let prefObj = JSON.parse(prefString); - + prefObj[prefID] = prefValue; - Cookies.set(prefCookieName, prefObj); + Cookies.set(prefCookieName, JSON.stringify(prefObj)); }; // Return an object containing all preferences -getPreferences = () => JSON.parse(Cookies.get(prefCookieName)); +const getPreferences = () => JSON.parse(Cookies.get(prefCookieName)); /* //////////////////////////////////////////////////////////////////////////////// @@ -113,7 +113,7 @@ var defaultUrlsCookie = { }; // Return an object that contains all InfluxDB urls stored in the urls cookie -getInfluxDBUrls = () => { +const getInfluxDBUrls = () => { // Initialize the urls cookie if it doesn't already exist if (Cookies.get(urlCookieName) === undefined) { initializeCookie('urls', defaultUrlsCookie); @@ -123,7 +123,7 @@ getInfluxDBUrls = () => { }; // Get the current or previous URL for a specific product or a custom url -getInfluxDBUrl = product => { +const getInfluxDBUrl = product => { // Initialize the urls cookie if it doesn't already exist if (Cookies.get(urlCookieName) === undefined) { initializeCookie('urls', defaultUrlsCookie); @@ -142,23 +142,23 @@ getInfluxDBUrl = product => { Input should be an object where the key is the product and the value is the URL to set for that product. */ -setInfluxDBUrls = updatedUrlsObj => { +const setInfluxDBUrls = updatedUrlsObj => { var urlsString = Cookies.get(urlCookieName); let urlsObj = JSON.parse(urlsString); - newUrlsObj = { ...urlsObj, ...updatedUrlsObj }; + const newUrlsObj = { ...urlsObj, ...updatedUrlsObj }; - Cookies.set(urlCookieName, newUrlsObj); + Cookies.set(urlCookieName, JSON.stringify(newUrlsObj)); }; // Set an InfluxDB URL to an empty string in the urls cookie -removeInfluxDBUrl = product => { +const removeInfluxDBUrl = product => { var urlsString = Cookies.get(urlCookieName); - let urlsObj = JSON.parse(urlsString); + const urlsObj = JSON.parse(urlsString); urlsObj[product] = ''; - Cookies.set(urlCookieName, urlsObj); + Cookies.set(urlCookieName, JSON.stringify(urlsObj)); }; /* @@ -175,7 +175,7 @@ var defaultNotifications = { callouts: [], }; -getNotifications = () => { +const getNotifications = () => { // Initialize the notifications cookie if it doesn't already exist if (Cookies.get(notificationCookieName) === undefined) { initializeCookie('notifications', defaultNotifications); @@ -199,11 +199,9 @@ getNotifications = () => { If the notification ID exists in the array assigned to the specified type, the notification has been read. */ -notificationIsRead = (notificationID, notificationType) => { +const notificationIsRead = (notificationID, notificationType) => { let notificationsObj = getNotifications(); - readNotifications = notificationsObj[`${notificationType}s`]; - - return readNotifications.includes(notificationID); + return notificationsObj[`${notificationType}s`].includes(notificationID); }; /* @@ -215,12 +213,26 @@ notificationIsRead = (notificationID, notificationType) => { The notification ID is added to the array assigned to the specified type. */ -setNotificationAsRead = (notificationID, notificationType) => { +const setNotificationAsRead = (notificationID, notificationType) => { let notificationsObj = getNotifications(); let readNotifications = notificationsObj[`${notificationType}s`]; readNotifications.push(notificationID); notificationsObj[notificationType + 's'] = readNotifications; - Cookies.set(notificationCookieName, notificationsObj); + Cookies.set(notificationCookieName, JSON.stringify(notificationsObj)); }; + +export { + initializeCookie, + getPreference, + setPreference, + getPreferences, + getInfluxDBUrls, + getInfluxDBUrl, + setInfluxDBUrls, + removeInfluxDBUrl, + getNotifications, + notificationIsRead, + setNotificationAsRead, +} diff --git a/assets/js/custom-timestamps.js b/assets/js/custom-timestamps.js index 2442931f8e..c1c4004053 100644 --- a/assets/js/custom-timestamps.js +++ b/assets/js/custom-timestamps.js @@ -1,3 +1,8 @@ +import $ from 'jquery'; +import { setPreference, getPreference } from './cookies.js'; +import { toggleModal } from './modal.js'; +import { Datepicker } from 'vanillajs-datepicker'; + // Placeholder start date used in InfluxDB custom timestamps const defaultStartDate = '2022-01-01'; diff --git a/assets/js/datetime.js b/assets/js/datetime.js index ec0f8ee2b7..7e88c1a858 100644 --- a/assets/js/datetime.js +++ b/assets/js/datetime.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var date = new Date() var currentTimestamp = date.toISOString().replace(/^(.*)(\.\d+)(Z)/, '$1$3') // 2023-01-01T12:34:56Z diff --git a/assets/js/docs-themes.js b/assets/js/docs-themes.js index ce9fa90f41..2c19ff0f46 100644 --- a/assets/js/docs-themes.js +++ b/assets/js/docs-themes.js @@ -3,6 +3,8 @@ http://www.thesitewizard.com/javascripts/change-style-sheets.shtml */ +import { getPreference, setPreference } from './cookies.js'; + // *** TO BE CUSTOMISED *** var style_preference_name = 'theme'; var style_cookie_duration = 30; @@ -36,7 +38,15 @@ function switchStyle (css_title) { function setStyleFromCookie () { var css_title = `${getPreference(style_preference_name)}-theme`; - if (css_title !== undefined) { + if (css_title !== 'undefined-theme') { switchStyle(css_title); } } + +export { + setStyleFromCookie, + switchStyle, + style_preference_name, + style_cookie_duration, + style_domain +} \ No newline at end of file diff --git a/assets/js/feature-callouts.js b/assets/js/feature-callouts.js index 918d783c22..6e68e6e540 100644 --- a/assets/js/feature-callouts.js +++ b/assets/js/feature-callouts.js @@ -5,6 +5,7 @@ Callouts are treated as notifications and use the notification cookie API in assets/js/cookies.js. */ +import $ from 'jquery'; // Get notification ID function getCalloutID (el) { diff --git a/assets/js/flux-group-keys.js b/assets/js/flux-group-keys.js index 80ab46b70f..87cf09c374 100644 --- a/assets/js/flux-group-keys.js +++ b/assets/js/flux-group-keys.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + var tablesElement = $("#flux-group-keys-demo #grouped-tables") // Sample data @@ -153,5 +155,10 @@ $(".column-list label").click(function () { buildGroupExample(); }); -// Group and render tables on load -groupData() +export { + buildTable, + buildTables, + groupData, + getChecked, + toggleCheckbox +}; diff --git a/assets/js/flux-influxdb-versions.js b/assets/js/flux-influxdb-versions.js index 4e6f66afeb..42e19e8e4d 100644 --- a/assets/js/flux-influxdb-versions.js +++ b/assets/js/flux-influxdb-versions.js @@ -1,6 +1,7 @@ /* Interactions related to the Flux/InfluxDB version modal */ +import $ from 'jquery'; const fluxInfluxDBModal = '.modal-content#flux-influxdb-versions' const pageType = ($(document).attr('title')).includes("package") ? "package" : "function"; diff --git a/assets/js/home-interactions.js b/assets/js/home-interactions.js index a90df14cd4..1bfc8db62d 100644 --- a/assets/js/home-interactions.js +++ b/assets/js/home-interactions.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + $('.exp-btn').click(function() { var targetBtnElement = $(this).parent() $('.exp-btn > p', targetBtnElement).fadeOut(100); diff --git a/assets/js/influxdb-url.js b/assets/js/influxdb-url.js index a2f1527487..d5a26afde7 100644 --- a/assets/js/influxdb-url.js +++ b/assets/js/influxdb-url.js @@ -1,3 +1,8 @@ +import $ from 'jquery'; +import { getInfluxDBUrls, getInfluxDBUrl, getPreference, setInfluxDBUrls, setPreference } from './cookies.js'; +// Import parameters passed from the calling page to js.Build. +import { cloudUrls } from '@params'; + var placeholderUrls = { oss: 'http://localhost:8086', cloud: 'https://cloud2.influxdata.com', @@ -646,8 +651,8 @@ productsWithUniqueURLs.forEach(function (productEl) { //////////////////////////////////////////////////////////////////////////////// // Extract the protocol and hostname of referrer -referrerMatch = document.referrer.match(/^(?:[^\/]*\/){2}[^\/]+/g); -referrerHost = referrerMatch ? referrerMatch[0] : ''; +const referrerMatch = document.referrer.match(/^(?:[^\/]*\/){2}[^\/]+/g); +const referrerHost = referrerMatch ? referrerMatch[0] : ''; // Check if the referrerHost is one of the cloud URLs // cloudUrls is built dynamically in layouts/partials/footer/javascript.html @@ -668,3 +673,5 @@ if (getUrls().dedicated == 'cluster-id.influxdb.io') { storeUrl('dedicated', 'cluster-id.a.influxdb.io', getUrls().dedicated); updateUrls(getPrevUrls(), getUrls()); } + +export { getUrls, referrerHost }; \ No newline at end of file diff --git a/assets/js/js.cookie.js b/assets/js/js.cookie.js deleted file mode 100644 index 9a0945ed89..0000000000 --- a/assets/js/js.cookie.js +++ /dev/null @@ -1,165 +0,0 @@ -/*! - * JavaScript Cookie v2.2.0 - * https://github.com/js-cookie/js-cookie - * - * Copyright 2006, 2015 Klaus Hartl & Fagner Brack - * Released under the MIT license - */ -;(function (factory) { - var registeredInModuleLoader = false; - if (typeof define === 'function' && define.amd) { - define(factory); - registeredInModuleLoader = true; - } - if (typeof exports === 'object') { - module.exports = factory(); - registeredInModuleLoader = true; - } - if (!registeredInModuleLoader) { - var OldCookies = window.Cookies; - var api = window.Cookies = factory(); - api.noConflict = function () { - window.Cookies = OldCookies; - return api; - }; - } -}(function () { - function extend () { - var i = 0; - var result = {}; - for (; i < arguments.length; i++) { - var attributes = arguments[ i ]; - for (var key in attributes) { - result[key] = attributes[key]; - } - } - return result; - } - - function init (converter) { - function api (key, value, attributes) { - var result; - if (typeof document === 'undefined') { - return; - } - - // Write - - if (arguments.length > 1) { - attributes = extend({ - path: '/' - }, api.defaults, attributes); - - if (typeof attributes.expires === 'number') { - var expires = new Date(); - expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); - attributes.expires = expires; - } - - // We're using "expires" because "max-age" is not supported by IE - attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; - - try { - result = JSON.stringify(value); - if (/^[\{\[]/.test(result)) { - value = result; - } - } catch (e) {} - - if (!converter.write) { - value = encodeURIComponent(String(value)) - .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); - } else { - value = converter.write(value, key); - } - - key = encodeURIComponent(String(key)); - key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); - key = key.replace(/[\(\)]/g, escape); - - var stringifiedAttributes = ''; - - for (var attributeName in attributes) { - if (!attributes[attributeName]) { - continue; - } - stringifiedAttributes += '; ' + attributeName; - if (attributes[attributeName] === true) { - continue; - } - stringifiedAttributes += '=' + attributes[attributeName]; - } - return (document.cookie = key + '=' + value + stringifiedAttributes); - } - - // Read - - if (!key) { - result = {}; - } - - // To prevent the for loop in the first place assign an empty array - // in case there are no cookies at all. Also prevents odd result when - // calling "get()" - var cookies = document.cookie ? document.cookie.split('; ') : []; - var rdecode = /(%[0-9A-Z]{2})+/g; - var i = 0; - - for (; i < cookies.length; i++) { - var parts = cookies[i].split('='); - var cookie = parts.slice(1).join('='); - - if (!this.json && cookie.charAt(0) === '"') { - cookie = cookie.slice(1, -1); - } - - try { - var name = parts[0].replace(rdecode, decodeURIComponent); - cookie = converter.read ? - converter.read(cookie, name) : converter(cookie, name) || - cookie.replace(rdecode, decodeURIComponent); - - if (this.json) { - try { - cookie = JSON.parse(cookie); - } catch (e) {} - } - - if (key === name) { - result = cookie; - break; - } - - if (!key) { - result[name] = cookie; - } - } catch (e) {} - } - - return result; - } - - api.set = api; - api.get = function (key) { - return api.call(api, key); - }; - api.getJSON = function () { - return api.apply({ - json: true - }, [].slice.call(arguments)); - }; - api.defaults = {}; - - api.remove = function (key, attributes) { - api(key, '', extend(attributes, { - expires: -1 - })); - }; - - api.withConverter = init; - - return api; - } - - return init(function () {}); -})); diff --git a/assets/js/keybindings.js b/assets/js/keybindings.js index 6c8f2fcbe2..1bd4529992 100644 --- a/assets/js/keybindings.js +++ b/assets/js/keybindings.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + // Dynamically update keybindings or hotkeys function getPlatform() { if (/Mac/.test(navigator.platform)) { diff --git a/assets/js/list-filters.js b/assets/js/list-filters.js index 7b008dcb68..3e95616aa5 100644 --- a/assets/js/list-filters.js +++ b/assets/js/list-filters.js @@ -1,3 +1,5 @@ +import $ from 'jquery'; + // Count tag elements function countTag(tag) { return $(".visible[data-tags*='" + tag + "']").length diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000000..7920404c7f --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,80 @@ +// assets/js/main.js +/** Import existing JS as "libraries" for now + * until we can refactor them into component modules. + */ +import * as codeblocksPreferences from './api-libs.js' +import * as codeControls from './code-controls.js'; +import * as cookies from './cookies.js'; +import * as datetime from './datetime.js'; +import * as featureCallouts from './feature-callouts.js'; +import * as fluxGroupKeys from './flux-group-keys.js'; +import * as fluxInfluxDBVersions from './flux-influxdb-versions.js'; +import * as homeInteractions from './home-interactions.js'; +import * as influxDBUrls from './influxdb-url.js'; +import * as keybindings from './keybindings.js'; +import * as listFilters from './list-filters.js'; +import * as modals from './modals.js'; +import * as notifications from './notifications.js'; +import * as pageFeedback from './page-feedback.js'; +import * as releaseTOC from './release-toc.js'; +import * as scroll from './scroll.js'; +import * as searchInteractions from './search-interactions.js'; +import * as sidebarToggle from './sidebar-toggle.js'; +import * as tabbedContent from './tabbed-content.js'; +import * as themes from './docs-themes.js'; +import * as v3wayfinding from './v3-wayfinding.js'; + + +/** UI Component-like Modules **/ +/** Following the React JSX component pattern, a component is a module + * that exports a single function + * encapsulating the behavior of the component. + * This function should + * be called in a DOMContentLoaded event listener to ensure that the + * component is properly initialized. +*/ +import ApiReferencePage from "./api-doc/ApiReferencePage.js"; +import ContentInteractions from './content-interactions.js'; +import FluxGroupKeys from './FluxGroupKeys.js'; +import SearchInput from './search-interactions.js'; +import Sidebar from './Sidebar.js' +import ThemeStyle from './ThemeStyle.js'; +import VersionSelector from './version-selector.js'; + +// Import parameters passed from the calling page to js.Build. +import * as pageParams from '@params'; + +document.addEventListener('DOMContentLoaded', () => { + // Expose libraries and components within a namespaced object. + window.influxdatadocs = { + ApiReferencePage, + codeblocksPreferences, + codeControls, + ContentInteractions, + cookies, + datetime, + featureCallouts, + fluxGroupKeys, + FluxGroupKeys, + fluxInfluxDBVersions, + homeInteractions, + influxDBUrls, + keybindings, + listFilters, + modals, + notifications, + pageFeedback, + pageParams, + releaseTOC, + scroll, + searchInteractions, + SearchInput, + Sidebar, + sidebarToggle, + tabbedContent, + themes, + ThemeStyle, + v3wayfinding, + VersionSelector, + }; +}); \ No newline at end of file diff --git a/assets/js/modals.js b/assets/js/modals.js index 9111f1945e..c4352ae2b7 100644 --- a/assets/js/modals.js +++ b/assets/js/modals.js @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// /////////////////////// General modal window interactions ////////////////////// //////////////////////////////////////////////////////////////////////////////// +import $ from 'jquery'; // Toggle the URL selector modal window function toggleModal(modalID="") { diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 2400f90529..8f5b16f15e 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -4,6 +4,7 @@ messages array in the influxdata_docs_notifications cookie. IDs in the messages array are considered read and no longer appear to the user. */ +import $ from 'jquery'; // Get notification ID function notificationID(el) { diff --git a/assets/js/page-feedback.js b/assets/js/page-feedback.js index af2ea0cd29..8ec5e665fa 100644 --- a/assets/js/page-feedback.js +++ b/assets/js/page-feedback.js @@ -2,6 +2,7 @@ * This file controls the interactions and life-cycles of the page feedback * buttons and modal. */ +import $ from 'jquery'; // Collect data from the page path const pathArr = location.pathname.split('/').slice(1, -1) diff --git a/assets/js/release-toc.js b/assets/js/release-toc.js index 42858fccc7..eb51b62ae0 100644 --- a/assets/js/release-toc.js +++ b/assets/js/release-toc.js @@ -4,6 +4,7 @@ * This script is used to generate a table of contents for the * release notes pages. */ +import $ from 'jquery'; // Use jQuery filter to get an array of all the *release* h2 elements const releases = $('h2').filter( diff --git a/assets/js/scroll.js b/assets/js/scroll.js new file mode 100644 index 0000000000..3c7af0d723 --- /dev/null +++ b/assets/js/scroll.js @@ -0,0 +1,21 @@ +import $ from 'jquery'; + +export function scrollToAnchor(target) { + var $target = $(target); + if($target && $target.length > 0) { + $('html, body').stop().animate({ + 'scrollTop': ($target.offset().top) + }, 400, 'swing', function () { + window.location.hash = target; + }); + + // Unique accordion functionality + // If the target is an accordion element, open the accordion after scrolling + if ($target.hasClass('expand')) { + if ($(target + ' .expand-label .expand-toggle').hasClass('open')) {} + else { + $(target + '> .expand-label').trigger('click'); + }; + }; + } +} \ No newline at end of file diff --git a/assets/js/search-interactions.js b/assets/js/search-interactions.js index 4f8fdd8ac1..8b3a12182f 100644 --- a/assets/js/search-interactions.js +++ b/assets/js/search-interactions.js @@ -1,10 +1,14 @@ -// Fade content wrapper when focusing on search input -$('#algolia-search-input').focus(function() { - $('.content-wrapper').fadeTo(300, .35); -}) +import $ from 'jquery'; -// Hide search dropdown when leaving search input -$('#algolia-search-input').blur(function() { - $('.content-wrapper').fadeTo(200, 1); - $('.ds-dropdown-menu').hide(); -}) +export default function SearchInput() { + // Fade content wrapper when focusing on search input + $('#algolia-search-input').focus(function() { + $('.content-wrapper').fadeTo(300, .35); + }) + + // Hide search dropdown when leaving search input + $('#algolia-search-input').blur(function() { + $('.content-wrapper').fadeTo(200, 1); + $('.ds-dropdown-menu').hide(); + }) +}; \ No newline at end of file diff --git a/assets/js/sidebar-toggle.js b/assets/js/sidebar-toggle.js index 2c20b3ddf5..d094c64db4 100644 --- a/assets/js/sidebar-toggle.js +++ b/assets/js/sidebar-toggle.js @@ -3,6 +3,8 @@ http://www.thesitewizard.com/javascripts/change-style-sheets.shtml */ +import { getPreference, setPreference } from './cookies.js'; + // *** TO BE CUSTOMISED *** var sidebar_state_preference_name = 'sidebar_state'; var sidebar_state_duration = 30; @@ -43,3 +45,11 @@ function setSidebarState () { toggleSidebar(toggle_state); } } + +export { + setSidebarState, + toggleSidebar, + sidebar_state_preference_name, + sidebar_state_duration, + style_domain +} diff --git a/assets/js/tabbed-content.js b/assets/js/tabbed-content.js index 536301828b..b6244defcc 100644 --- a/assets/js/tabbed-content.js +++ b/assets/js/tabbed-content.js @@ -1,5 +1,8 @@ //////////////////////////////// Tabbed Content //////////////////////////////// +import $ from 'jquery'; +import { scrollToAnchor } from './scroll.js'; + /** * NOTE: Tab elements are whitelisted elements that do not trigger * smoothscroll when clicked. The whitelist is defined in content-interactions.js. @@ -30,9 +33,6 @@ function tabbedContent (container, tab, content) { }); } -tabbedContent('.code-tabs-wrapper', '.code-tabs p a', '.code-tab-content'); -tabbedContent('.tabs-wrapper', '.tabs p a', '.tab-content'); - function getTabQueryParam () { const queryParams = new URLSearchParams(window.location.search); return $('