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]

Reply via email to