This is an automated email from the ASF dual-hosted git repository.
vogievetsky pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new c89a8281c32 Web console: fix + check doc links (#18618)
c89a8281c32 is described below
commit c89a8281c320352b58d2c213217e7373880e4d5c
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Mon Oct 13 17:43:46 2025 +0100
Web console: fix + check doc links (#18618)
* bump axios to resolve vuln
* make expression input multiline
* fix doc links
* use local docs instead
---
licenses.yaml | 2 +-
web-console/package-lock.json | 12 +-
web-console/script/check-doc-links.mjs | 140 +++++++++++++++++++++
.../druid-models/transform-spec/transform-spec.tsx | 9 +-
.../src/views/load-data-view/info-messages.tsx | 2 +-
.../src/views/load-data-view/load-data-view.tsx | 2 +-
6 files changed, 154 insertions(+), 13 deletions(-)
diff --git a/licenses.yaml b/licenses.yaml
index 64757d23112..0c5e6e3b1a0 100644
--- a/licenses.yaml
+++ b/licenses.yaml
@@ -5445,7 +5445,7 @@ license_category: binary
module: web-console
license_name: MIT License
copyright: Matt Zabriskie
-version: 1.8.4
+version: 1.12.2
license_file_path: licenses/bin/axios.MIT
---
diff --git a/web-console/package-lock.json b/web-console/package-lock.json
index eb7df97842a..78bf22c12a6 100644
--- a/web-console/package-lock.json
+++ b/web-console/package-lock.json
@@ -5419,9 +5419,9 @@
}
},
"node_modules/axios": {
- "version": "1.8.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
- "integrity":
"sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+ "integrity":
"sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -22376,9 +22376,9 @@
}
},
"axios": {
- "version": "1.8.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
- "integrity":
"sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+ "integrity":
"sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"requires": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
diff --git a/web-console/script/check-doc-links.mjs
b/web-console/script/check-doc-links.mjs
new file mode 100755
index 00000000000..240a0afe44f
--- /dev/null
+++ b/web-console/script/check-doc-links.mjs
@@ -0,0 +1,140 @@
+#!/usr/bin/env node
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* eslint-disable no-undef */
+
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+const SRC_DIR = path.join(__dirname, '../src');
+const DOCS_DIR = path.join(__dirname, '../../docs');
+
+// Regex to find ${getLink('DOCS')}/path patterns
+const DOC_LINK_REGEX = /\$\{getLink\(['"]DOCS['"]\)\}\/([^\s`'"}\]]+)/g;
+
+function getAllTsFiles(dir) {
+ const files = [];
+
+ function traverse(currentPath) {
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const fullPath = path.join(currentPath, entry.name);
+
+ if (entry.isDirectory()) {
+ traverse(fullPath);
+ } else if (entry.isFile() && (entry.name.endsWith('.ts') ||
entry.name.endsWith('.tsx'))) {
+ files.push(fullPath);
+ }
+ }
+ }
+
+ traverse(dir);
+ return files;
+}
+
+function extractDocLinks(filePath) {
+ const content = fs.readFileSync(filePath, 'utf-8');
+ const links = [];
+ let match;
+
+ // Reset regex state
+ DOC_LINK_REGEX.lastIndex = 0;
+
+ while ((match = DOC_LINK_REGEX.exec(content)) !== null) {
+ const fullPath = match[1];
+ // Remove anchor if present
+ let pathWithoutAnchor = fullPath.split('#')[0];
+
+ // Remove trailing slash if present
+ pathWithoutAnchor = pathWithoutAnchor.replace(/\/$/, '');
+
+ // Try path.md first, fallback to path/index.md
+ let docFilePath = path.join(DOCS_DIR, `${pathWithoutAnchor}.md`);
+ if (!fs.existsSync(docFilePath)) {
+ docFilePath = path.join(DOCS_DIR, pathWithoutAnchor, 'index.md');
+ }
+
+ links.push({
+ file: path.relative(path.join(__dirname, '..'), filePath),
+ docPath: fullPath,
+ docFilePath: docFilePath
+ });
+ }
+
+ return links;
+}
+
+function checkDocFile(docFilePath) {
+ return {
+ docFilePath,
+ exists: fs.existsSync(docFilePath)
+ };
+}
+
+async function main() {
+ console.log('Searching for doc links in TypeScript files...\n');
+
+ const tsFiles = getAllTsFiles(SRC_DIR);
+ console.log(`Found ${tsFiles.length} TypeScript files\n`);
+
+ const allLinks = [];
+ for (const file of tsFiles) {
+ const links = extractDocLinks(file);
+ allLinks.push(...links);
+ }
+
+ console.log(`Found ${allLinks.length} doc links\n`);
+
+ if (allLinks.length === 0) {
+ console.log('No doc links found.');
+ return;
+ }
+
+ console.log('Checking if doc files exist...\n');
+
+ let hasErrors = false;
+ for (const link of allLinks) {
+ const result = checkDocFile(link.docFilePath);
+
+ if (!result.exists) {
+ hasErrors = true;
+ console.log(`❌ BROKEN: ${link.file}`);
+ console.log(` Doc path: ${link.docPath}`);
+ console.log(` Expected file: ${path.relative(path.join(__dirname,
'../..'), link.docFilePath)}\n`);
+ }
+ }
+
+ if (hasErrors) {
+ console.log('⚠️ Some links are broken!');
+ process.exit(1);
+ } else {
+ console.log('✅ All links are valid!');
+ process.exit(0);
+ }
+}
+
+main().catch(err => {
+ console.error('Error:', err);
+ process.exit(1);
+});
diff --git a/web-console/src/druid-models/transform-spec/transform-spec.tsx
b/web-console/src/druid-models/transform-spec/transform-spec.tsx
index ae6e37cba11..4b8a93fb8ec 100644
--- a/web-console/src/druid-models/transform-spec/transform-spec.tsx
+++ b/web-console/src/druid-models/transform-spec/transform-spec.tsx
@@ -50,12 +50,13 @@ export const TRANSFORM_FIELDS: Field<Transform>[] = [
{
name: 'expression',
type: 'string',
- placeholder: '"foo" + "bar"',
+ multiline: true,
+ placeholder: `ex: strlen(concat("foo", '-bar'))`,
required: true,
info: (
<>
A valid Druid{' '}
- <ExternalLink
href={`${getLink('DOCS')}/misc/math-expr`}>expression</ExternalLink>.
+ <ExternalLink
href={`${getLink('DOCS')}/querying/math-expr`}>expression</ExternalLink>.
</>
),
},
@@ -82,8 +83,8 @@ export function getTimestampExpressionFields(transforms:
Transform[]): Field<Tra
info: (
<>
A valid Druid{' '}
- <ExternalLink
href={`${getLink('DOCS')}/misc/math-expr`}>expression</ExternalLink> that
- should output a millis timestamp. You most likely want to use the{'
'}
+ <ExternalLink
href={`${getLink('DOCS')}/querying/math-expr`}>expression</ExternalLink>{' '}
+ that should output a millis timestamp. You most likely want to use
the{' '}
<Code>timestamp_parse</Code> function at the outer level.
</>
),
diff --git a/web-console/src/views/load-data-view/info-messages.tsx
b/web-console/src/views/load-data-view/info-messages.tsx
index e3a5e7f3993..8f034e01a92 100644
--- a/web-console/src/views/load-data-view/info-messages.tsx
+++ b/web-console/src/views/load-data-view/info-messages.tsx
@@ -101,7 +101,7 @@ export const TransformMessage = React.memo(function
TransformMessage() {
<Callout>
<p>
Druid can perform per-row{' '}
- <ExternalLink
href={`${getLink('DOCS')}/ingestion/transform-spec#transforms`}>
+ <ExternalLink
href={`${getLink('DOCS')}/ingestion/ingestion-spec#transforms`}>
transforms
</ExternalLink>{' '}
of column values allowing you to create new derived columns or alter
existing column.
diff --git a/web-console/src/views/load-data-view/load-data-view.tsx
b/web-console/src/views/load-data-view/load-data-view.tsx
index 520dfc3034a..cb42359878f 100644
--- a/web-console/src/views/load-data-view/load-data-view.tsx
+++ b/web-console/src/views/load-data-view/load-data-view.tsx
@@ -1182,7 +1182,7 @@ export class LoadDataView extends
React.PureComponent<LoadDataViewProps, LoadDat
</p>
<p>
For more information please refer to the{' '}
- <ExternalLink
href={`${getLink('DOCS')}/operations/including-extensions`}>
+ <ExternalLink
href={`${getLink('DOCS')}/configuration/extensions#loading-extensions`}>
documentation on loading extensions
</ExternalLink>
.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]