From a950ecefae7235895af3b5944dcb08909f514809 Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Thu, 8 Jan 2026 14:44:08 -0500 Subject: [PATCH 1/9] scripts automation (check links + images) --- .github/workflows/validate-docs.yml | 75 ++++++++ scripts/check-image-locations.js | 274 ++++++++++++++++++++++++++++ scripts/check-links.js | 2 +- 3 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/validate-docs.yml create mode 100755 scripts/check-image-locations.js diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml new file mode 100644 index 00000000..6da35eee --- /dev/null +++ b/.github/workflows/validate-docs.yml @@ -0,0 +1,75 @@ +name: Validate Documentation + +on: + pull_request: + branches: + - main + paths: + - '**.mdx' + - '**.md' + - 'images/**' + - 'scripts/**' + - '.github/workflows/validate-docs.yml' + +jobs: + validate: + name: Check Links and Images + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Check for broken links + id: check-links + run: | + echo "::group::Checking for broken links" + node scripts/check-links.js + echo "::endgroup::" + continue-on-error: true + + - name: Check image locations + id: check-images + run: | + echo "::group::Checking image locations" + node scripts/check-image-locations.js + echo "::endgroup::" + continue-on-error: true + + - name: Report results + if: steps.check-links.outcome == 'failure' || steps.check-images.outcome == 'failure' + run: | + echo "## Documentation Validation Failed ❌" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check-links.outcome }}" == "failure" ]; then + echo "### Broken Links Found πŸ”—" >> $GITHUB_STEP_SUMMARY + echo "Please fix the broken links identified above." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ steps.check-images.outcome }}" == "failure" ]; then + echo "### Image Location Issues Found πŸ–ΌοΈ" >> $GITHUB_STEP_SUMMARY + echo "Images must mirror the page structure. See [CONTRIBUTING.md](../CONTRIBUTING.md#image-guidelines) for guidelines." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + echo "---" >> $GITHUB_STEP_SUMMARY + echo "πŸ’‘ **Quick Reference:**" >> $GITHUB_STEP_SUMMARY + echo "- A page at \`guides/dashboard.mdx\` should use images from \`images/guides/dashboard/\`" >> $GITHUB_STEP_SUMMARY + echo "- Shared images can be placed in parent directories" >> $GITHUB_STEP_SUMMARY + echo "- All internal links must point to existing files" >> $GITHUB_STEP_SUMMARY + + exit 1 + + - name: Success message + if: steps.check-links.outcome == 'success' && steps.check-images.outcome == 'success' + run: | + echo "## Documentation Validation Passed βœ…" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All links are valid and images are in the correct locations!" >> $GITHUB_STEP_SUMMARY diff --git a/scripts/check-image-locations.js b/scripts/check-image-locations.js new file mode 100755 index 00000000..c26a71b7 --- /dev/null +++ b/scripts/check-image-locations.js @@ -0,0 +1,274 @@ +#!/usr/bin/env node + +/** + * Validates that images are placed in the correct directory structure + * Images should mirror the page structure as defined in CONTRIBUTING.md + * + * Rules: + * - A page at guides/dashboard.mdx should use images from images/guides/dashboard/ + * - Shared images can be in parent folders + * - All images must exist + * + * Run: node scripts/check-image-locations.js + */ + +const fs = require('fs'); +const path = require('path'); + +// Regex patterns for finding image references +const MARKDOWN_IMAGE_REGEX = /!\[([^\]]*)\]\(([^)]+)\)/g; +const JSX_IMG_SRC_REGEX = /]+src=["']([^"']+)["']/g; +const FRAME_IMG_REGEX = /]*>[\s\S]*?]+src=["']([^"']+)["']/g; + +const VALID_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp']; + +function findMDXFiles(dir, fileList = []) { + const files = fs.readdirSync(dir); + + files.forEach(file => { + const filePath = path.join(dir, file); + const stat = fs.statSync(filePath); + + if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') { + findMDXFiles(filePath, fileList); + } else if (file.endsWith('.mdx') || file.endsWith('.md')) { + fileList.push(filePath); + } + }); + + return fileList; +} + +function extractImageReferences(content, filePath) { + const images = []; + + // Extract markdown images: ![alt](path) + let match; + while ((match = MARKDOWN_IMAGE_REGEX.exec(content)) !== null) { + const imagePath = match[2]; + if (!imagePath.startsWith('http://') && !imagePath.startsWith('https://')) { + images.push({ + alt: match[1], + path: imagePath, + type: 'markdown', + line: content.substring(0, match.index).split('\n').length + }); + } + } + + // Extract JSX image src + while ((match = JSX_IMG_SRC_REGEX.exec(content)) !== null) { + const imagePath = match[1]; + if (!imagePath.startsWith('http://') && !imagePath.startsWith('https://')) { + images.push({ + path: imagePath, + type: 'jsx', + line: content.substring(0, match.index).split('\n').length + }); + } + } + + return images; +} + +function getExpectedImageDirectory(mdxFilePath) { + // Get path relative to project root + const relativePath = path.relative(process.cwd(), mdxFilePath); + + // Remove file extension + const withoutExt = relativePath.replace(/\.(mdx|md)$/, ''); + + // Convert to expected image path + // e.g., guides/dashboard.mdx -> images/guides/dashboard/ + // e.g., get-started/setup.mdx -> images/get-started/setup/ + return path.join('images', withoutExt); +} + +function validateImageLocation(imagePath, mdxFilePath) { + const issues = []; + + // Resolve absolute path for image + let absoluteImagePath; + if (imagePath.startsWith('/')) { + // Absolute path from project root + absoluteImagePath = path.join(process.cwd(), imagePath.substring(1)); + } else { + // Relative path from MDX file + absoluteImagePath = path.join(path.dirname(mdxFilePath), imagePath); + } + + // Check if image exists + if (!fs.existsSync(absoluteImagePath)) { + issues.push({ + type: 'missing', + message: 'Image file does not exist', + imagePath: imagePath, + absolutePath: absoluteImagePath + }); + return issues; // No point checking location if file doesn't exist + } + + // Check if it's actually an image file + const ext = path.extname(absoluteImagePath).toLowerCase(); + if (!VALID_IMAGE_EXTENSIONS.includes(ext)) { + issues.push({ + type: 'invalid-type', + message: `Invalid file extension: ${ext}. Expected: ${VALID_IMAGE_EXTENSIONS.join(', ')}`, + imagePath: imagePath + }); + } + + // Get expected directory + const expectedDir = getExpectedImageDirectory(mdxFilePath); + + // Get actual directory of the image (relative to project root) + let imageDir; + if (imagePath.startsWith('/')) { + imageDir = path.dirname(imagePath.substring(1)); + } else { + // Convert to absolute then back to relative from project root + const relativeImagePath = path.relative(process.cwd(), absoluteImagePath); + imageDir = path.dirname(relativeImagePath); + } + + // Check if image is in the expected directory or a valid parent directory + const isInExpectedDir = imageDir === expectedDir; + const isInParentDir = expectedDir.startsWith(imageDir + path.sep) || expectedDir === imageDir; + + // Special case: logo and favicon can be in root + const isSpecialFile = imagePath.includes('/logo/') || + imagePath.includes('favicon') || + imagePath.includes('/snippets/'); + + if (!isInExpectedDir && !isInParentDir && !isSpecialFile) { + // Check if it's in a valid parent directory (shared images) + const expectedParts = expectedDir.split(path.sep); + const imageParts = imageDir.split(path.sep); + + // Allow if image is in a parent directory of the expected path + const isValidParent = expectedParts.length > imageParts.length && + expectedParts.slice(0, imageParts.length).join(path.sep) === imageDir; + + if (!isValidParent) { + issues.push({ + type: 'wrong-location', + message: 'Image is not in the correct directory', + imagePath: imagePath, + expectedDir: expectedDir, + actualDir: imageDir, + suggestion: `Move to ${expectedDir}/ or a shared parent directory` + }); + } + } + + return issues; +} + +function getRelativePath(filePath) { + return path.relative(process.cwd(), filePath).replace(/\\/g, '/'); +} + +async function main() { + console.log('πŸ–ΌοΈ Checking image locations in documentation...\n'); + + const mdxFiles = findMDXFiles('.'); + const excludedPaths = ['node_modules', '.git', 'snippets', 'CONTRIBUTING.md']; + const filteredFiles = mdxFiles.filter(file => + !excludedPaths.some(excluded => file.includes(excluded)) + ); + + console.log(`πŸ“„ Found ${filteredFiles.length} documentation files\n`); + + const allIssues = []; + let totalImages = 0; + let missingImages = 0; + let misplacedImages = 0; + let invalidTypes = 0; + + // Check images in each file + for (const file of filteredFiles) { + const content = fs.readFileSync(file, 'utf8'); + const images = extractImageReferences(content, file); + const relativePath = getRelativePath(file); + + for (const image of images) { + totalImages++; + + const issues = validateImageLocation(image.path, file); + + if (issues.length > 0) { + issues.forEach(issue => { + allIssues.push({ + file: relativePath, + line: image.line, + ...issue + }); + + if (issue.type === 'missing') missingImages++; + if (issue.type === 'wrong-location') misplacedImages++; + if (issue.type === 'invalid-type') invalidTypes++; + }); + } + } + } + + // Display results + if (allIssues.length === 0) { + console.log('βœ… All images are in the correct locations!\n'); + } else { + console.log(`❌ Found ${allIssues.length} image issues:\n`); + + // Group by issue type + const missingIssues = allIssues.filter(i => i.type === 'missing'); + const locationIssues = allIssues.filter(i => i.type === 'wrong-location'); + const typeIssues = allIssues.filter(i => i.type === 'invalid-type'); + + if (missingIssues.length > 0) { + console.log(`πŸ“ Missing Images (${missingIssues.length}):\n`); + missingIssues.forEach(({ file, line, imagePath, message }) => { + console.log(` πŸ“„ ${file}:${line}`); + console.log(` πŸ”— ${imagePath}`); + console.log(` ❌ ${message}\n`); + }); + } + + if (locationIssues.length > 0) { + console.log(`πŸ“ Misplaced Images (${locationIssues.length}):\n`); + locationIssues.forEach(({ file, line, imagePath, expectedDir, actualDir, suggestion }) => { + console.log(` πŸ“„ ${file}:${line}`); + console.log(` πŸ”— ${imagePath}`); + console.log(` ❌ Expected in: ${expectedDir}/`); + console.log(` πŸ“ Actually in: ${actualDir}/`); + console.log(` πŸ’‘ ${suggestion}\n`); + }); + } + + if (typeIssues.length > 0) { + console.log(`⚠️ Invalid File Types (${typeIssues.length}):\n`); + typeIssues.forEach(({ file, line, imagePath, message }) => { + console.log(` πŸ“„ ${file}:${line}`); + console.log(` πŸ”— ${imagePath}`); + console.log(` ❌ ${message}\n`); + }); + } + } + + // Summary + console.log('─'.repeat(60)); + console.log(`Total images checked: ${totalImages}`); + console.log(`Missing images: ${missingImages}`); + console.log(`Misplaced images: ${misplacedImages}`); + console.log(`Invalid file types: ${invalidTypes}`); + console.log('─'.repeat(60)); + + console.log('\nπŸ’‘ Image Placement Rules:'); + console.log(' β€’ Images should mirror page structure'); + console.log(' β€’ guides/dashboard.mdx β†’ images/guides/dashboard/'); + console.log(' β€’ Shared images can be in parent directories'); + console.log(' β€’ See CONTRIBUTING.md for full guidelines\n'); + + // Exit with error if issues found + process.exit(allIssues.length > 0 ? 1 : 0); +} + +main(); diff --git a/scripts/check-links.js b/scripts/check-links.js index ec0acee2..8b01657d 100644 --- a/scripts/check-links.js +++ b/scripts/check-links.js @@ -208,7 +208,7 @@ async function main() { console.log('πŸ” Checking for broken links in documentation...\n'); const mdxFiles = findMDXFiles('.'); - const excludedPaths = ['node_modules', '.git']; + const excludedPaths = ['node_modules', '.git', 'CONTRIBUTING.md']; const filteredFiles = mdxFiles.filter(file => !excludedPaths.some(excluded => file.includes(excluded)) ); From 98c8ec37de5697404b800667c71b7bcb64533686 Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Thu, 8 Jan 2026 14:51:43 -0500 Subject: [PATCH 2/9] last scripts check --- .github/workflows/validate-docs.yml | 51 +++++++++++++-------------- scripts/check-links.js | 53 ++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 26 deletions(-) diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml index 6da35eee..862a4ccb 100644 --- a/.github/workflows/validate-docs.yml +++ b/.github/workflows/validate-docs.yml @@ -42,34 +42,35 @@ jobs: continue-on-error: true - name: Report results - if: steps.check-links.outcome == 'failure' || steps.check-images.outcome == 'failure' + if: always() run: | - echo "## Documentation Validation Failed ❌" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "${{ steps.check-links.outcome }}" == "failure" ]; then - echo "### Broken Links Found πŸ”—" >> $GITHUB_STEP_SUMMARY - echo "Please fix the broken links identified above." >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.check-links.outcome }}" == "failure" ] || [ "${{ steps.check-images.outcome }}" == "failure" ]; then + echo "## Documentation Validation Warnings ⚠️" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - fi - - if [ "${{ steps.check-images.outcome }}" == "failure" ]; then - echo "### Image Location Issues Found πŸ–ΌοΈ" >> $GITHUB_STEP_SUMMARY - echo "Images must mirror the page structure. See [CONTRIBUTING.md](../CONTRIBUTING.md#image-guidelines) for guidelines." >> $GITHUB_STEP_SUMMARY + echo "The following issues were found. Please review and fix when possible:" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - fi - echo "---" >> $GITHUB_STEP_SUMMARY - echo "πŸ’‘ **Quick Reference:**" >> $GITHUB_STEP_SUMMARY - echo "- A page at \`guides/dashboard.mdx\` should use images from \`images/guides/dashboard/\`" >> $GITHUB_STEP_SUMMARY - echo "- Shared images can be placed in parent directories" >> $GITHUB_STEP_SUMMARY - echo "- All internal links must point to existing files" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.check-links.outcome }}" == "failure" ]; then + echo "### Broken Links Found πŸ”—" >> $GITHUB_STEP_SUMMARY + echo "See the check-links step above for details." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi - exit 1 + if [ "${{ steps.check-images.outcome }}" == "failure" ]; then + echo "### Image Location Issues Found πŸ–ΌοΈ" >> $GITHUB_STEP_SUMMARY + echo "Images should mirror the page structure. See the check-images step above for details." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi - - name: Success message - if: steps.check-links.outcome == 'success' && steps.check-images.outcome == 'success' - run: | - echo "## Documentation Validation Passed βœ…" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "All links are valid and images are in the correct locations!" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "πŸ’‘ **Quick Reference:**" >> $GITHUB_STEP_SUMMARY + echo "- A page at \`guides/dashboard.mdx\` should use images from \`images/guides/dashboard/\`" >> $GITHUB_STEP_SUMMARY + echo "- Shared images can be placed in parent directories" >> $GITHUB_STEP_SUMMARY + echo "- All internal links must point to existing files" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "ℹ️ **Note:** These are informational warnings and will not block your PR from merging." >> $GITHUB_STEP_SUMMARY + else + echo "## Documentation Validation Passed βœ…" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All links are valid and images are in the correct locations!" >> $GITHUB_STEP_SUMMARY + fi diff --git a/scripts/check-links.js b/scripts/check-links.js index 8b01657d..0718fcd0 100644 --- a/scripts/check-links.js +++ b/scripts/check-links.js @@ -204,6 +204,39 @@ function findOrphanedPages(allMdxFiles, docsJson) { return orphans; } +function extractRedirects(docsJson) { + if (!docsJson || !docsJson.redirects) return []; + return docsJson.redirects.map(r => ({ + source: r.source.startsWith('/') ? r.source.substring(1) : r.source, + destination: r.destination.startsWith('/') ? r.destination.substring(1) : r.destination + })); +} + +function validateRedirects(redirects) { + const issues = []; + + redirects.forEach(redirect => { + // Check if destination exists + const possiblePaths = [ + path.join(process.cwd(), redirect.destination), + path.join(process.cwd(), redirect.destination + '.mdx'), + path.join(process.cwd(), redirect.destination + '.md'), + ]; + + const exists = possiblePaths.some(p => fs.existsSync(p)); + + if (!exists) { + issues.push({ + source: redirect.source, + destination: redirect.destination, + issue: 'Redirect destination does not exist' + }); + } + }); + + return issues; +} + async function main() { console.log('πŸ” Checking for broken links in documentation...\n'); @@ -256,6 +289,11 @@ async function main() { const docsJson = loadDocsJson(); const orphanedPages = findOrphanedPages(filteredFiles, docsJson); + // Check redirects + console.log('πŸ” Checking redirects in docs.json...\n'); + const redirects = extractRedirects(docsJson); + const redirectIssues = validateRedirects(redirects); + // Display results if (brokenLinks.length === 0) { console.log('βœ… No broken internal links found!\n'); @@ -287,6 +325,18 @@ async function main() { console.log('\nπŸ’‘ These pages exist but are not linked in docs.json navigation.\n'); } + // Display redirect issues + if (redirectIssues.length === 0) { + console.log('βœ… All redirects are valid!\n'); + } else { + console.log(`❌ Found ${redirectIssues.length} invalid redirects in docs.json:\n`); + redirectIssues.forEach(({ source, destination, issue }) => { + console.log(` πŸ”€ ${source} β†’ ${destination}`); + console.log(` ❌ ${issue}\n`); + }); + console.log('πŸ’‘ When moving pages, ensure redirect destinations exist.\n'); + } + // Check external links if requested if (CHECK_EXTERNAL && externalLinks.length > 0) { console.log(`🌐 Checking ${externalLinks.length} external links...\n`); @@ -317,13 +367,14 @@ async function main() { console.log(`Total links checked: ${totalLinks}`); console.log(`Broken internal links: ${brokenLinks.length}`); console.log(`Orphaned pages: ${orphanedPages.length}`); + console.log(`Invalid redirects: ${redirectIssues.length}`); if (CHECK_EXTERNAL) { console.log(`External links checked: ${externalLinks.length}`); } console.log('─'.repeat(60)); // Exit with error if issues found - const hasIssues = brokenLinks.length > 0 || orphanedPages.length > 0; + const hasIssues = brokenLinks.length > 0 || orphanedPages.length > 0 || redirectIssues.length > 0; process.exit(hasIssues ? 1 : 0); } From 110af7dae652eb0e5e735425bfb622a53d416603 Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Thu, 8 Jan 2026 15:06:32 -0500 Subject: [PATCH 3/9] update warning messge --- .github/workflows/validate-docs.yml | 85 ++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml index 862a4ccb..0538c9df 100644 --- a/.github/workflows/validate-docs.yml +++ b/.github/workflows/validate-docs.yml @@ -29,7 +29,8 @@ jobs: id: check-links run: | echo "::group::Checking for broken links" - node scripts/check-links.js + node scripts/check-links.js > /tmp/check-links-output.txt 2>&1 || echo "issues_found=true" >> $GITHUB_OUTPUT + cat /tmp/check-links-output.txt echo "::endgroup::" continue-on-error: true @@ -37,38 +38,72 @@ jobs: id: check-images run: | echo "::group::Checking image locations" - node scripts/check-image-locations.js + node scripts/check-image-locations.js > /tmp/check-images-output.txt 2>&1 || echo "issues_found=true" >> $GITHUB_OUTPUT + cat /tmp/check-images-output.txt echo "::endgroup::" continue-on-error: true + - name: Post PR comment + if: steps.check-links.outputs.issues_found == 'true' || steps.check-images.outputs.issues_found == 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + + let comment = '## πŸ“‹ Documentation Validation Report\n\n'; + comment += '⚠️ Some issues were found in this PR. These are **informational warnings** and will not block merging.\n\n'; + + if ('${{ steps.check-links.outputs.issues_found }}' === 'true') { + const linksOutput = fs.readFileSync('/tmp/check-links-output.txt', 'utf8'); + const lines = linksOutput.split('\n'); + const brokenLinks = lines.filter(line => line.includes('Broken link:') || line.includes('πŸ“„')).slice(0, 10); + + comment += '### πŸ”— Links\n'; + if (brokenLinks.length > 0) { + comment += '
❌ Found broken links (click to expand)\n\n'; + comment += '```\n' + brokenLinks.join('\n') + '\n```\n'; + comment += '
\n\n'; + } + } else { + comment += '### πŸ”— Links\nβœ… All links valid\n\n'; + } + + if ('${{ steps.check-images.outputs.issues_found }}' === 'true') { + const imagesOutput = fs.readFileSync('/tmp/check-images-output.txt', 'utf8'); + const lines = imagesOutput.split('\n'); + const imageIssues = lines.filter(line => line.includes('πŸ“„') || line.includes('Expected in:')).slice(0, 10); + + comment += '### πŸ–ΌοΈ Images\n'; + if (imageIssues.length > 0) { + comment += '
❌ Found image location issues (click to expand)\n\n'; + comment += '```\n' + imageIssues.join('\n') + '\n```\n'; + comment += '
\n\n'; + } + } else { + comment += '### πŸ–ΌοΈ Images\nβœ… All images in correct locations\n\n'; + } + + comment += '---\n'; + comment += 'πŸ’‘ **Quick Reference:**\n'; + comment += '- A page at `guides/dashboard.mdx` should use images from `images/guides/dashboard/`\n'; + comment += '- Shared images can be placed in parent directories\n'; + comment += '- All internal links must point to existing files\n\n'; + comment += 'πŸ“– Run `node scripts/check-links.js` and `node scripts/check-image-locations.js` locally for full details.\n'; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + - name: Report results if: always() run: | - if [ "${{ steps.check-links.outcome }}" == "failure" ] || [ "${{ steps.check-images.outcome }}" == "failure" ]; then + if [ "${{ steps.check-links.outputs.issues_found }}" == "true" ] || [ "${{ steps.check-images.outputs.issues_found }}" == "true" ]; then echo "## Documentation Validation Warnings ⚠️" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "The following issues were found. Please review and fix when possible:" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "${{ steps.check-links.outcome }}" == "failure" ]; then - echo "### Broken Links Found πŸ”—" >> $GITHUB_STEP_SUMMARY - echo "See the check-links step above for details." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - - if [ "${{ steps.check-images.outcome }}" == "failure" ]; then - echo "### Image Location Issues Found πŸ–ΌοΈ" >> $GITHUB_STEP_SUMMARY - echo "Images should mirror the page structure. See the check-images step above for details." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - - echo "---" >> $GITHUB_STEP_SUMMARY - echo "πŸ’‘ **Quick Reference:**" >> $GITHUB_STEP_SUMMARY - echo "- A page at \`guides/dashboard.mdx\` should use images from \`images/guides/dashboard/\`" >> $GITHUB_STEP_SUMMARY - echo "- Shared images can be placed in parent directories" >> $GITHUB_STEP_SUMMARY - echo "- All internal links must point to existing files" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "ℹ️ **Note:** These are informational warnings and will not block your PR from merging." >> $GITHUB_STEP_SUMMARY + echo "Issues found - see PR comment for details" >> $GITHUB_STEP_SUMMARY else echo "## Documentation Validation Passed βœ…" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY From d5ef96cd360d5497a38e494ae46fcbb24d3b5589 Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Thu, 8 Jan 2026 16:01:57 -0500 Subject: [PATCH 4/9] fix broken links and adjust link script to skip example urls --- guides/embedding/dashboards.mdx | 2 +- guides/table-calculations.mdx | 4 ++-- scripts/check-links.js | 39 ++++++++++++++++++++++++++++----- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/guides/embedding/dashboards.mdx b/guides/embedding/dashboards.mdx index cf51ea0e..42aad223 100644 --- a/guides/embedding/dashboards.mdx +++ b/guides/embedding/dashboards.mdx @@ -444,7 +444,7 @@ Filter data based on the viewing user's properties. See the [user attributes ref Embed individual charts for focused displays - + Show different data to different users diff --git a/guides/table-calculations.mdx b/guides/table-calculations.mdx index 6db2b093..fd83fe40 100644 --- a/guides/table-calculations.mdx +++ b/guides/table-calculations.mdx @@ -3,7 +3,7 @@ title: "Table calculations" description: "Table calculations make it easy to create metrics on the fly (for example, aggregating the data in your table over some window, or getting a running total of a column)." --- -You add table calculations in the Explore view and they appear as green columns in your results table (remember, [dimensions](references/dimensions) are blue, and [metrics](references/metrics) are orange). +You add table calculations in the Explore view and they appear as green columns in your results table (remember, [dimensions](/references/dimensions) are blue, and [metrics](/references/metrics) are orange). @@ -24,7 +24,7 @@ Table calculations work as an additional computational layer that builds on top - Other table calculations (you can even stack them) -Table calculations are useful for specialized, temporary needs, but if you find yourself repeatedly reusing a table calculation, it's a sign to add it to dbt so it's tracked in the semantic layer. This keeps your organization's metrics centralized and consistently defined. You can read more about [adding metrics to your dbt project here](references/metrics). +Table calculations are useful for specialized, temporary needs, but if you find yourself repeatedly reusing a table calculation, it's a sign to add it to dbt so it's tracked in the semantic layer. This keeps your organization's metrics centralized and consistently defined. You can read more about [adding metrics to your dbt project here](/references/metrics). ## Creating table calculations diff --git a/scripts/check-links.js b/scripts/check-links.js index 0718fcd0..48a0e5b2 100644 --- a/scripts/check-links.js +++ b/scripts/check-links.js @@ -66,11 +66,40 @@ function isExternalLink(url) { } function isSpecialLink(url) { - return url.startsWith('mailto:') || - url.startsWith('tel:') || - url.startsWith('#') || - url.includes('{{') || // Template variables - url.includes('${'); // Template literals + // Skip standard special links + if (url.startsWith('mailto:') || + url.startsWith('tel:') || + url.startsWith('#')) { + return true; + } + + // Skip template variables + if (url.includes('{{') || // Handlebars + url.includes('${') || // JS template literals + url.includes('<%=') || // ERB/EJS templates + url.includes('<%')) { // ERB/EJS templates + return true; + } + + // Skip placeholder/example URLs + const placeholderPatterns = [ + 'uuid', + 'your-project', + 'your-workspace', + 'example-', + 'dashboard-id', + 'chart-id', + 'project-id', + 'workspace-id', + 'embed-proxy', // Example embed URLs + ]; + + const lowerUrl = url.toLowerCase(); + if (placeholderPatterns.some(pattern => lowerUrl.includes(pattern))) { + return true; + } + + return false; } function resolveInternalLink(url, sourceFile) { From 452501fd618d9d035cc18ce9199842d61f3e622b Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Thu, 8 Jan 2026 16:19:21 -0500 Subject: [PATCH 5/9] updating image directory errors and script to check location (ignores images used in multiple mdx files) --- .../chatgpt-01-click-profile-settings.png | Bin ...atgpt-02-select-connectors-then-create.png | Bin ...ill-out-name-desc-mcp-url-select-oauth.png | Bin .../chatgpt-04-oauth-flow-via-lightdsah.png | Bin .../claude-ai-01-click-profile-settings.png | Bin ...t-connectors-then-add-custom-connector.png | Bin ...claude-ai-03-fill-out-name-and-mcp-url.png | Bin .../{mcp => }/claude-ai-04-click-connect.png | Bin .../claude-ai-05-oauth-flow-via-lightdsah.png | Bin .../claude-ai-06-OPTIONAL-configure.png | Bin .../claude-code-01-slash-mcp-command.png | Bin ...-lightdash-navigating-using-arrow-keys.png | Bin ...-code-03-select-authenticate-hit-enter.png | Bin ...e-04-complete-oauth-flow-via-lightdash.png | Bin ...-start-prompting-after-successful-auth.png | Bin .../examples-01-verifying-mcp-connection.png | Bin ...ting-up-project-and-finding-dashboards.png | Bin ...executing-metric-query-to-analyze-data.png | Bin ...tting-up-custom-instructions-01-claude.png | Bin references/integrations/lightdash-mcp.mdx | 38 +++++++++--------- 20 files changed, 19 insertions(+), 19 deletions(-) rename images/references/integrations/lightdash-mcp/{mcp => }/chatgpt-01-click-profile-settings.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/chatgpt-02-select-connectors-then-create.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/chatgpt-03-fill-out-name-desc-mcp-url-select-oauth.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/chatgpt-04-oauth-flow-via-lightdsah.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-ai-01-click-profile-settings.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-ai-02-select-connectors-then-add-custom-connector.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-ai-03-fill-out-name-and-mcp-url.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-ai-04-click-connect.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-ai-05-oauth-flow-via-lightdsah.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-ai-06-OPTIONAL-configure.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-code-01-slash-mcp-command.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-code-02-select-lightdash-navigating-using-arrow-keys.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-code-03-select-authenticate-hit-enter.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-code-04-complete-oauth-flow-via-lightdash.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/claude-code-05-start-prompting-after-successful-auth.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/examples-01-verifying-mcp-connection.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/examples-02-setting-up-project-and-finding-dashboards.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/examples-03-finding-explores-and-fields-and-executing-metric-query-to-analyze-data.png (100%) rename images/references/integrations/lightdash-mcp/{mcp => }/setting-up-custom-instructions-01-claude.png (100%) diff --git a/images/references/integrations/lightdash-mcp/mcp/chatgpt-01-click-profile-settings.png b/images/references/integrations/lightdash-mcp/chatgpt-01-click-profile-settings.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/chatgpt-01-click-profile-settings.png rename to images/references/integrations/lightdash-mcp/chatgpt-01-click-profile-settings.png diff --git a/images/references/integrations/lightdash-mcp/mcp/chatgpt-02-select-connectors-then-create.png b/images/references/integrations/lightdash-mcp/chatgpt-02-select-connectors-then-create.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/chatgpt-02-select-connectors-then-create.png rename to images/references/integrations/lightdash-mcp/chatgpt-02-select-connectors-then-create.png diff --git a/images/references/integrations/lightdash-mcp/mcp/chatgpt-03-fill-out-name-desc-mcp-url-select-oauth.png b/images/references/integrations/lightdash-mcp/chatgpt-03-fill-out-name-desc-mcp-url-select-oauth.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/chatgpt-03-fill-out-name-desc-mcp-url-select-oauth.png rename to images/references/integrations/lightdash-mcp/chatgpt-03-fill-out-name-desc-mcp-url-select-oauth.png diff --git a/images/references/integrations/lightdash-mcp/mcp/chatgpt-04-oauth-flow-via-lightdsah.png b/images/references/integrations/lightdash-mcp/chatgpt-04-oauth-flow-via-lightdsah.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/chatgpt-04-oauth-flow-via-lightdsah.png rename to images/references/integrations/lightdash-mcp/chatgpt-04-oauth-flow-via-lightdsah.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-ai-01-click-profile-settings.png b/images/references/integrations/lightdash-mcp/claude-ai-01-click-profile-settings.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-ai-01-click-profile-settings.png rename to images/references/integrations/lightdash-mcp/claude-ai-01-click-profile-settings.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-ai-02-select-connectors-then-add-custom-connector.png b/images/references/integrations/lightdash-mcp/claude-ai-02-select-connectors-then-add-custom-connector.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-ai-02-select-connectors-then-add-custom-connector.png rename to images/references/integrations/lightdash-mcp/claude-ai-02-select-connectors-then-add-custom-connector.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-ai-03-fill-out-name-and-mcp-url.png b/images/references/integrations/lightdash-mcp/claude-ai-03-fill-out-name-and-mcp-url.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-ai-03-fill-out-name-and-mcp-url.png rename to images/references/integrations/lightdash-mcp/claude-ai-03-fill-out-name-and-mcp-url.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-ai-04-click-connect.png b/images/references/integrations/lightdash-mcp/claude-ai-04-click-connect.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-ai-04-click-connect.png rename to images/references/integrations/lightdash-mcp/claude-ai-04-click-connect.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-ai-05-oauth-flow-via-lightdsah.png b/images/references/integrations/lightdash-mcp/claude-ai-05-oauth-flow-via-lightdsah.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-ai-05-oauth-flow-via-lightdsah.png rename to images/references/integrations/lightdash-mcp/claude-ai-05-oauth-flow-via-lightdsah.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-ai-06-OPTIONAL-configure.png b/images/references/integrations/lightdash-mcp/claude-ai-06-OPTIONAL-configure.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-ai-06-OPTIONAL-configure.png rename to images/references/integrations/lightdash-mcp/claude-ai-06-OPTIONAL-configure.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-code-01-slash-mcp-command.png b/images/references/integrations/lightdash-mcp/claude-code-01-slash-mcp-command.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-code-01-slash-mcp-command.png rename to images/references/integrations/lightdash-mcp/claude-code-01-slash-mcp-command.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-code-02-select-lightdash-navigating-using-arrow-keys.png b/images/references/integrations/lightdash-mcp/claude-code-02-select-lightdash-navigating-using-arrow-keys.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-code-02-select-lightdash-navigating-using-arrow-keys.png rename to images/references/integrations/lightdash-mcp/claude-code-02-select-lightdash-navigating-using-arrow-keys.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-code-03-select-authenticate-hit-enter.png b/images/references/integrations/lightdash-mcp/claude-code-03-select-authenticate-hit-enter.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-code-03-select-authenticate-hit-enter.png rename to images/references/integrations/lightdash-mcp/claude-code-03-select-authenticate-hit-enter.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-code-04-complete-oauth-flow-via-lightdash.png b/images/references/integrations/lightdash-mcp/claude-code-04-complete-oauth-flow-via-lightdash.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-code-04-complete-oauth-flow-via-lightdash.png rename to images/references/integrations/lightdash-mcp/claude-code-04-complete-oauth-flow-via-lightdash.png diff --git a/images/references/integrations/lightdash-mcp/mcp/claude-code-05-start-prompting-after-successful-auth.png b/images/references/integrations/lightdash-mcp/claude-code-05-start-prompting-after-successful-auth.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/claude-code-05-start-prompting-after-successful-auth.png rename to images/references/integrations/lightdash-mcp/claude-code-05-start-prompting-after-successful-auth.png diff --git a/images/references/integrations/lightdash-mcp/mcp/examples-01-verifying-mcp-connection.png b/images/references/integrations/lightdash-mcp/examples-01-verifying-mcp-connection.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/examples-01-verifying-mcp-connection.png rename to images/references/integrations/lightdash-mcp/examples-01-verifying-mcp-connection.png diff --git a/images/references/integrations/lightdash-mcp/mcp/examples-02-setting-up-project-and-finding-dashboards.png b/images/references/integrations/lightdash-mcp/examples-02-setting-up-project-and-finding-dashboards.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/examples-02-setting-up-project-and-finding-dashboards.png rename to images/references/integrations/lightdash-mcp/examples-02-setting-up-project-and-finding-dashboards.png diff --git a/images/references/integrations/lightdash-mcp/mcp/examples-03-finding-explores-and-fields-and-executing-metric-query-to-analyze-data.png b/images/references/integrations/lightdash-mcp/examples-03-finding-explores-and-fields-and-executing-metric-query-to-analyze-data.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/examples-03-finding-explores-and-fields-and-executing-metric-query-to-analyze-data.png rename to images/references/integrations/lightdash-mcp/examples-03-finding-explores-and-fields-and-executing-metric-query-to-analyze-data.png diff --git a/images/references/integrations/lightdash-mcp/mcp/setting-up-custom-instructions-01-claude.png b/images/references/integrations/lightdash-mcp/setting-up-custom-instructions-01-claude.png similarity index 100% rename from images/references/integrations/lightdash-mcp/mcp/setting-up-custom-instructions-01-claude.png rename to images/references/integrations/lightdash-mcp/setting-up-custom-instructions-01-claude.png diff --git a/references/integrations/lightdash-mcp.mdx b/references/integrations/lightdash-mcp.mdx index 56aa3e0c..44ea0356 100644 --- a/references/integrations/lightdash-mcp.mdx +++ b/references/integrations/lightdash-mcp.mdx @@ -50,21 +50,21 @@ Set up MCP in the [Claude.ai](https://claude.ai) web app, and it will automatica Navigate to your profile menu (bottom-left corner) and select Settings. - ![Navigate to profile menu in bottom-left corner and click Settings](/images/references/integrations/lightdash-mcp/mcp/claude-ai-01-click-profile-settings.png) + ![Navigate to profile menu in bottom-left corner and click Settings](/images/references/integrations/lightdash-mcp/claude-ai-01-click-profile-settings.png) 2. **Add Custom Connector** In the Settings menu, select "Connectors" from the sidebar, then click "Add custom connector". - ![Select Connectors from the sidebar menu, then click Add custom connector button](/images/references/integrations/lightdash-mcp/mcp/claude-ai-02-select-connectors-then-add-custom-connector.png) + ![Select Connectors from the sidebar menu, then click Add custom connector button](/images/references/integrations/lightdash-mcp/claude-ai-02-select-connectors-then-add-custom-connector.png) 3. **Configure Connection** Fill in the connection details with your Lightdash instance information. - ![Enter Lightdash as the name and your instance MCP URL in the configuration form](/images/references/integrations/lightdash-mcp/mcp/claude-ai-03-fill-out-name-and-mcp-url.png) + ![Enter Lightdash as the name and your instance MCP URL in the configuration form](/images/references/integrations/lightdash-mcp/claude-ai-03-fill-out-name-and-mcp-url.png) - **Name:** Lightdash (or any name you prefer) - **URL:** `https://.lightdash.cloud/api/v1/mcp` @@ -73,21 +73,21 @@ Set up MCP in the [Claude.ai](https://claude.ai) web app, and it will automatica Click the "Connect" button to initiate the authentication process. - ![Click the Connect button to start OAuth authentication with Lightdash](/images/references/integrations/lightdash-mcp/mcp/claude-ai-04-click-connect.png) + ![Click the Connect button to start OAuth authentication with Lightdash](/images/references/integrations/lightdash-mcp/claude-ai-04-click-connect.png) 5. **Complete OAuth Flow** Log in to your Lightdash account and approve the connection when prompted. - ![Login to Lightdash with your credentials and approve the OAuth connection request](/images/references/integrations/lightdash-mcp/mcp/claude-ai-05-oauth-flow-via-lightdsah.png) + ![Login to Lightdash with your credentials and approve the OAuth connection request](/images/references/integrations/lightdash-mcp/claude-ai-05-oauth-flow-via-lightdsah.png) 6. **Configure Permissions (Optional)** Optionally configure which MCP tools Claude can access and set any additional permissions. - ![Optional: Configure MCP tool access and permissions for your Claude assistant](/images/references/integrations/lightdash-mcp/mcp/claude-ai-06-OPTIONAL-configure.png) + ![Optional: Configure MCP tool access and permissions for your Claude assistant](/images/references/integrations/lightdash-mcp/claude-ai-06-OPTIONAL-configure.png) @@ -101,7 +101,7 @@ ChatGPT support for MCP is coming soon\! Stay tuned for updates. 1. **Access Settings in ChatGPT** Click profile > Settings @@ -109,7 +109,7 @@ ChatGPT support for MCP is coming soon\! Stay tuned for updates. 2. **Create Custom Connector** Select Connectors > Create @@ -117,7 +117,7 @@ ChatGPT support for MCP is coming soon\! Stay tuned for updates. 3. **Configure Connection** Fill out name, description, MCP URL and select OAuth @@ -130,7 +130,7 @@ ChatGPT support for MCP is coming soon\! Stay tuned for updates. 4. **Complete OAuth Flow** Login and approve through Lightdash @@ -152,27 +152,27 @@ Replace `` with your actual Lightdash instance name. 1. **Use the /mcp command in Claude Code** - ![Type /mcp to see available MCP connections](/images/references/integrations/lightdash-mcp/mcp/claude-code-01-slash-mcp-command.png) + ![Type /mcp to see available MCP connections](/images/references/integrations/lightdash-mcp/claude-code-01-slash-mcp-command.png) 2. **Select Lightdash from the list** - ![Navigate to Lightdash using arrow keys](/images/references/integrations/lightdash-mcp/mcp/claude-code-02-select-lightdash-navigating-using-arrow-keys.png) + ![Navigate to Lightdash using arrow keys](/images/references/integrations/lightdash-mcp/claude-code-02-select-lightdash-navigating-using-arrow-keys.png) 3. **Authenticate with Lightdash** - ![Select Authenticate and hit Enter](/images/references/integrations/lightdash-mcp/mcp/claude-code-03-select-authenticate-hit-enter.png) + ![Select Authenticate and hit Enter](/images/references/integrations/lightdash-mcp/claude-code-03-select-authenticate-hit-enter.png) 4. **Complete OAuth Flow** - ![Complete the OAuth flow via Lightdash](/images/references/integrations/lightdash-mcp/mcp/claude-code-04-complete-oauth-flow-via-lightdash.png) + ![Complete the OAuth flow via Lightdash](/images/references/integrations/lightdash-mcp/claude-code-04-complete-oauth-flow-via-lightdash.png) 5. **Start using MCP** - ![Start prompting after successful authentication](/images/references/integrations/lightdash-mcp/mcp/claude-code-05-start-prompting-after-successful-auth.png) + ![Start prompting after successful authentication](/images/references/integrations/lightdash-mcp/claude-code-05-start-prompting-after-successful-auth.png) After authentication, you can start asking questions about your Lightdash data directly in Claude Code\! @@ -212,7 +212,7 @@ Since MCP provides raw tools without built-in intelligence, your AI assistant ne - ![Setting up custom instructions for Lightdash MCP in Claude](/images/references/integrations/lightdash-mcp/mcp/setting-up-custom-instructions-01-claude.png) + ![Setting up custom instructions for Lightdash MCP in Claude](/images/references/integrations/lightdash-mcp/setting-up-custom-instructions-01-claude.png) Here's a suggested template for optimizing Lightdash MCP usage. Feel free to modify these instructions based on your specific needs and use cases: @@ -276,17 +276,17 @@ Here are some examples of how you can interact with AI assistants using MCP: - ![Verifying MCP connection and listing available tools](/images/references/integrations/lightdash-mcp/mcp/examples-01-verifying-mcp-connection.png) + ![Verifying MCP connection and listing available tools](/images/references/integrations/lightdash-mcp/examples-01-verifying-mcp-connection.png) - ![Setting up project context and finding dashboards](/images/references/integrations/lightdash-mcp/mcp/examples-02-setting-up-project-and-finding-dashboards.png) + ![Setting up project context and finding dashboards](/images/references/integrations/lightdash-mcp/examples-02-setting-up-project-and-finding-dashboards.png) - ![Finding explores and fields, then executing metric queries to analyze data](/images/references/integrations/lightdash-mcp/mcp/examples-03-finding-explores-and-fields-and-executing-metric-query-to-analyze-data.png) + ![Finding explores and fields, then executing metric queries to analyze data](/images/references/integrations/lightdash-mcp/examples-03-finding-explores-and-fields-and-executing-metric-query-to-analyze-data.png) From 657bc9d49d6362b81e59db009cd9b3bb443d35d5 Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Thu, 8 Jan 2026 16:19:36 -0500 Subject: [PATCH 6/9] check-links adjustment --- scripts/check-image-locations.js | 59 ++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/scripts/check-image-locations.js b/scripts/check-image-locations.js index c26a71b7..f45fd3bc 100755 --- a/scripts/check-image-locations.js +++ b/scripts/check-image-locations.js @@ -84,7 +84,7 @@ function getExpectedImageDirectory(mdxFilePath) { return path.join('images', withoutExt); } -function validateImageLocation(imagePath, mdxFilePath) { +function validateImageLocation(imagePath, mdxFilePath, isShared = false) { const issues = []; // Resolve absolute path for image @@ -118,6 +118,11 @@ function validateImageLocation(imagePath, mdxFilePath) { }); } + // Skip location validation for shared images (used by multiple files) + if (isShared) { + return issues; + } + // Get expected directory const expectedDir = getExpectedImageDirectory(mdxFilePath); @@ -168,6 +173,36 @@ function getRelativePath(filePath) { return path.relative(process.cwd(), filePath).replace(/\\/g, '/'); } +function buildSharedImagesMap(files) { + const imageUsageMap = new Map(); // imagePath -> [files using it] + + for (const file of files) { + const content = fs.readFileSync(file, 'utf8'); + const images = extractImageReferences(content, file); + + for (const image of images) { + const normalizedPath = image.path.startsWith('/') + ? image.path + : '/' + path.relative(process.cwd(), path.join(path.dirname(file), image.path)).replace(/\\/g, '/'); + + if (!imageUsageMap.has(normalizedPath)) { + imageUsageMap.set(normalizedPath, []); + } + imageUsageMap.get(normalizedPath).push(getRelativePath(file)); + } + } + + // Return set of images used by 2+ files (shared images) + const sharedImages = new Set(); + for (const [imagePath, files] of imageUsageMap.entries()) { + if (files.length >= 2) { + sharedImages.add(imagePath); + } + } + + return { sharedImages, imageUsageMap }; +} + async function main() { console.log('πŸ–ΌοΈ Checking image locations in documentation...\n'); @@ -179,11 +214,17 @@ async function main() { console.log(`πŸ“„ Found ${filteredFiles.length} documentation files\n`); + // First pass: identify shared images + console.log('πŸ” Identifying shared images...\n'); + const { sharedImages, imageUsageMap } = buildSharedImagesMap(filteredFiles); + console.log(`πŸ“Š Found ${sharedImages.size} images used by multiple pages\n`); + const allIssues = []; let totalImages = 0; let missingImages = 0; let misplacedImages = 0; let invalidTypes = 0; + let sharedImagesCount = 0; // Check images in each file for (const file of filteredFiles) { @@ -194,7 +235,18 @@ async function main() { for (const image of images) { totalImages++; - const issues = validateImageLocation(image.path, file); + // Normalize image path for comparison + const normalizedPath = image.path.startsWith('/') + ? image.path + : '/' + path.relative(process.cwd(), path.join(path.dirname(file), image.path)).replace(/\\/g, '/'); + + // Skip location validation for shared images + const isShared = sharedImages.has(normalizedPath); + if (isShared) { + sharedImagesCount++; + } + + const issues = validateImageLocation(image.path, file, isShared); if (issues.length > 0) { issues.forEach(issue => { @@ -256,6 +308,7 @@ async function main() { // Summary console.log('─'.repeat(60)); console.log(`Total images checked: ${totalImages}`); + console.log(`Shared images (used by 2+ files): ${sharedImagesCount}`); console.log(`Missing images: ${missingImages}`); console.log(`Misplaced images: ${misplacedImages}`); console.log(`Invalid file types: ${invalidTypes}`); @@ -264,7 +317,7 @@ async function main() { console.log('\nπŸ’‘ Image Placement Rules:'); console.log(' β€’ Images should mirror page structure'); console.log(' β€’ guides/dashboard.mdx β†’ images/guides/dashboard/'); - console.log(' β€’ Shared images can be in parent directories'); + console.log(' β€’ Shared images (used by multiple pages) are automatically allowed'); console.log(' β€’ See CONTRIBUTING.md for full guidelines\n'); // Exit with error if issues found From 90e5dda118572ecb2c7ef54084522e42fc68e591 Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Thu, 8 Jan 2026 16:32:21 -0500 Subject: [PATCH 7/9] fixing directory --- .../adding-tables-to-lightdash.mdx | 2 +- get-started/exploring-data/using-explores.mdx | 2 +- guides/table-calculations.mdx | 12 ++++++------ .../dbt-repo-example.png | Bin .../save-your-chart.png | Bin ...le-calc-sql-20de5398dc117ef3d4087cf3b6d9939b.jpg | Bin ...-table-calc-772add6d063a5122c0a25accf798fbb3.jpg | Bin ...-table-calc-520776c8631697527bf3760000dce88e.jpg | Bin ...-table-calc-c0e3828b6681db084b4ddd6799ff2c96.jpg | Bin ...calculation-b1e76d3e54d84ca3c5066ffdf989514b.png | Bin .../table_calculation_filter_example.jpg | Bin .../{appearance => }/light-dark-mode-palette.gif | Bin .../{appearance => }/list-color-palettes.png | Bin .../{appearance => }/use-color-palette-chart.png | Bin .../{appearance => }/use-theme.png | Bin ...wser-schema-62b496e0d9f5f705ae823c7d4fdec946.png | Bin .../customizing-the-appearance-of-your-project.mdx | 8 ++++---- .../enable-headless-browser-for-lightdash.mdx | 2 +- 18 files changed, 13 insertions(+), 13 deletions(-) rename images/{guides => get-started/develop-in-lightdash/adding-tables-to-lightdash}/dbt-repo-example.png (100%) rename images/get-started/exploring-data/{dashboards => using-explores}/save-your-chart.png (100%) rename images/{references => guides/table-calculations}/add-table-calc-sql-20de5398dc117ef3d4087cf3b6d9939b.jpg (100%) rename images/{references => guides/table-calculations}/create-new-table-calc-772add6d063a5122c0a25accf798fbb3.jpg (100%) rename images/{references => guides/table-calculations}/edit-delete-table-calc-520776c8631697527bf3760000dce88e.jpg (100%) rename images/{references => guides/table-calculations}/format-table-calc-c0e3828b6681db084b4ddd6799ff2c96.jpg (100%) rename images/{references => guides/table-calculations}/quick-table-calculation-b1e76d3e54d84ca3c5066ffdf989514b.png (100%) rename images/{references => guides/table-calculations}/table_calculation_filter_example.jpg (100%) rename images/references/workspace/customizing-the-appearance-of-your-project/{appearance => }/light-dark-mode-palette.gif (100%) rename images/references/workspace/customizing-the-appearance-of-your-project/{appearance => }/list-color-palettes.png (100%) rename images/references/workspace/customizing-the-appearance-of-your-project/{appearance => }/use-color-palette-chart.png (100%) rename images/references/workspace/customizing-the-appearance-of-your-project/{appearance => }/use-theme.png (100%) rename images/self-host/customize-deployment/{assets/images => enable-headless-browser-for-lightdash}/headless-browser-schema-62b496e0d9f5f705ae823c7d4fdec946.png (100%) diff --git a/get-started/develop-in-lightdash/adding-tables-to-lightdash.mdx b/get-started/develop-in-lightdash/adding-tables-to-lightdash.mdx index 0ab7893f..debb009a 100644 --- a/get-started/develop-in-lightdash/adding-tables-to-lightdash.mdx +++ b/get-started/develop-in-lightdash/adding-tables-to-lightdash.mdx @@ -159,7 +159,7 @@ Here's an example of our dbt project at Lightdash too see what that looks like i - We have one `.yml` file per model (these are the files where all of your Tables' configuration sits) - + **But, in my dbt project, I have a single schema.yml file. Not one for each model. Will that still work?** diff --git a/get-started/exploring-data/using-explores.mdx b/get-started/exploring-data/using-explores.mdx index 1d11f057..2063ec22 100644 --- a/get-started/exploring-data/using-explores.mdx +++ b/get-started/exploring-data/using-explores.mdx @@ -107,7 +107,7 @@ Saved Charts allow you to save a specific chart or table so you can share, add i When you open a saved chart, it will always update to display the latest data in your database since it will re-run the query each time you open it. - ![](/images/get-started/exploring-data/dashboards/save-your-chart.png) + ![](/images/get-started/exploring-data/using-explores/save-your-chart.png) To save a chart, click the `Save chart` button at the top of the page, then give your chart a useful name and description. You'll have the option to save the chart to a Dashboard or a Space. diff --git a/guides/table-calculations.mdx b/guides/table-calculations.mdx index fd83fe40..d154f96c 100644 --- a/guides/table-calculations.mdx +++ b/guides/table-calculations.mdx @@ -38,7 +38,7 @@ Table calculations work with whatever data you've already pulled into your resul Quick calculations are shortcuts to the most common table calculations, these are only available to metrics in the contextual menu in the results table. -![](/images/references/quick-table-calculation-b1e76d3e54d84ca3c5066ffdf989514b.png) +![](/images/guides/table-calculations/quick-table-calculation-b1e76d3e54d84ca3c5066ffdf989514b.png) To learn more about what these calculations are doing, check out the docs [here](/guides/table-calculations/sql-templates). Once the table calculation is generated you can edit it to modify the SQL query or update the format. @@ -48,7 +48,7 @@ To learn more about what these calculations are doing, check out the docs [here] Once you've got some data in your results table, you can create a table calculation by clicking on the `+ Table calculation` in the `Results` tab of the Explore view: - ![](/images/references/create-new-table-calc-772add6d063a5122c0a25accf798fbb3.jpg) + ![](/images/guides/table-calculations/create-new-table-calc-772add6d063a5122c0a25accf798fbb3.jpg) ##### Write the SQL for your table calculation in the pop-up box @@ -58,7 +58,7 @@ Your table calculation is defined using raw SQL that you write up in this pop up To reference the metrics and dimensions in your results table, you can either use the autocomplete, or you can manually write the full field name using the format `${table_name.field_name}`. - ![](/images/references/add-table-calc-sql-20de5398dc117ef3d4087cf3b6d9939b.jpg) + ![](/images/guides/table-calculations/add-table-calc-sql-20de5398dc117ef3d4087cf3b6d9939b.jpg) @@ -67,7 +67,7 @@ To reference the metrics and dimensions in your results table, you can either us If needed, you can update the format of your table calculation to things like percent formatting using the `format` tab. - ![](/images/references/format-table-calc-c0e3828b6681db084b4ddd6799ff2c96.jpg) + ![](/images/guides/table-calculations/format-table-calc-c0e3828b6681db084b4ddd6799ff2c96.jpg) | Format types | Description | Raw value | How it might look like in Lightdash | @@ -82,7 +82,7 @@ If needed, you can update the format of your table calculation to things like pe If you need to edit or delete a table calculation, you can just click on the toggle beside the field and do what you need to do! - ![](/images/references/edit-delete-table-calc-520776c8631697527bf3760000dce88e.jpg) + ![](/images/guides/table-calculations/edit-delete-table-calc-520776c8631697527bf3760000dce88e.jpg) @@ -102,7 +102,7 @@ Since all calculations are performed before filtering, table calculation filters For example: You use table calculations to calculate the `percentage_of_shipping_method_amount`, then filter to hide rows below 30%. The filtered rows (shown in purple) will disappear but the `percentage_total` calculation still shows 100% even though the visible percentages in the `percentage_of_shipping_method_amount` column no longer add up to 100%. -![](/images/references/table_calculation_filter_example.jpg) +![](/images/guides/table-calculations/table_calculation_filter_example.jpg) ## Table calculation SQL templates diff --git a/images/guides/dbt-repo-example.png b/images/get-started/develop-in-lightdash/adding-tables-to-lightdash/dbt-repo-example.png similarity index 100% rename from images/guides/dbt-repo-example.png rename to images/get-started/develop-in-lightdash/adding-tables-to-lightdash/dbt-repo-example.png diff --git a/images/get-started/exploring-data/dashboards/save-your-chart.png b/images/get-started/exploring-data/using-explores/save-your-chart.png similarity index 100% rename from images/get-started/exploring-data/dashboards/save-your-chart.png rename to images/get-started/exploring-data/using-explores/save-your-chart.png diff --git a/images/references/add-table-calc-sql-20de5398dc117ef3d4087cf3b6d9939b.jpg b/images/guides/table-calculations/add-table-calc-sql-20de5398dc117ef3d4087cf3b6d9939b.jpg similarity index 100% rename from images/references/add-table-calc-sql-20de5398dc117ef3d4087cf3b6d9939b.jpg rename to images/guides/table-calculations/add-table-calc-sql-20de5398dc117ef3d4087cf3b6d9939b.jpg diff --git a/images/references/create-new-table-calc-772add6d063a5122c0a25accf798fbb3.jpg b/images/guides/table-calculations/create-new-table-calc-772add6d063a5122c0a25accf798fbb3.jpg similarity index 100% rename from images/references/create-new-table-calc-772add6d063a5122c0a25accf798fbb3.jpg rename to images/guides/table-calculations/create-new-table-calc-772add6d063a5122c0a25accf798fbb3.jpg diff --git a/images/references/edit-delete-table-calc-520776c8631697527bf3760000dce88e.jpg b/images/guides/table-calculations/edit-delete-table-calc-520776c8631697527bf3760000dce88e.jpg similarity index 100% rename from images/references/edit-delete-table-calc-520776c8631697527bf3760000dce88e.jpg rename to images/guides/table-calculations/edit-delete-table-calc-520776c8631697527bf3760000dce88e.jpg diff --git a/images/references/format-table-calc-c0e3828b6681db084b4ddd6799ff2c96.jpg b/images/guides/table-calculations/format-table-calc-c0e3828b6681db084b4ddd6799ff2c96.jpg similarity index 100% rename from images/references/format-table-calc-c0e3828b6681db084b4ddd6799ff2c96.jpg rename to images/guides/table-calculations/format-table-calc-c0e3828b6681db084b4ddd6799ff2c96.jpg diff --git a/images/references/quick-table-calculation-b1e76d3e54d84ca3c5066ffdf989514b.png b/images/guides/table-calculations/quick-table-calculation-b1e76d3e54d84ca3c5066ffdf989514b.png similarity index 100% rename from images/references/quick-table-calculation-b1e76d3e54d84ca3c5066ffdf989514b.png rename to images/guides/table-calculations/quick-table-calculation-b1e76d3e54d84ca3c5066ffdf989514b.png diff --git a/images/references/table_calculation_filter_example.jpg b/images/guides/table-calculations/table_calculation_filter_example.jpg similarity index 100% rename from images/references/table_calculation_filter_example.jpg rename to images/guides/table-calculations/table_calculation_filter_example.jpg diff --git a/images/references/workspace/customizing-the-appearance-of-your-project/appearance/light-dark-mode-palette.gif b/images/references/workspace/customizing-the-appearance-of-your-project/light-dark-mode-palette.gif similarity index 100% rename from images/references/workspace/customizing-the-appearance-of-your-project/appearance/light-dark-mode-palette.gif rename to images/references/workspace/customizing-the-appearance-of-your-project/light-dark-mode-palette.gif diff --git a/images/references/workspace/customizing-the-appearance-of-your-project/appearance/list-color-palettes.png b/images/references/workspace/customizing-the-appearance-of-your-project/list-color-palettes.png similarity index 100% rename from images/references/workspace/customizing-the-appearance-of-your-project/appearance/list-color-palettes.png rename to images/references/workspace/customizing-the-appearance-of-your-project/list-color-palettes.png diff --git a/images/references/workspace/customizing-the-appearance-of-your-project/appearance/use-color-palette-chart.png b/images/references/workspace/customizing-the-appearance-of-your-project/use-color-palette-chart.png similarity index 100% rename from images/references/workspace/customizing-the-appearance-of-your-project/appearance/use-color-palette-chart.png rename to images/references/workspace/customizing-the-appearance-of-your-project/use-color-palette-chart.png diff --git a/images/references/workspace/customizing-the-appearance-of-your-project/appearance/use-theme.png b/images/references/workspace/customizing-the-appearance-of-your-project/use-theme.png similarity index 100% rename from images/references/workspace/customizing-the-appearance-of-your-project/appearance/use-theme.png rename to images/references/workspace/customizing-the-appearance-of-your-project/use-theme.png diff --git a/images/self-host/customize-deployment/assets/images/headless-browser-schema-62b496e0d9f5f705ae823c7d4fdec946.png b/images/self-host/customize-deployment/enable-headless-browser-for-lightdash/headless-browser-schema-62b496e0d9f5f705ae823c7d4fdec946.png similarity index 100% rename from images/self-host/customize-deployment/assets/images/headless-browser-schema-62b496e0d9f5f705ae823c7d4fdec946.png rename to images/self-host/customize-deployment/enable-headless-browser-for-lightdash/headless-browser-schema-62b496e0d9f5f705ae823c7d4fdec946.png diff --git a/references/workspace/customizing-the-appearance-of-your-project.mdx b/references/workspace/customizing-the-appearance-of-your-project.mdx index ae379ee5..5233bbc3 100644 --- a/references/workspace/customizing-the-appearance-of-your-project.mdx +++ b/references/workspace/customizing-the-appearance-of-your-project.mdx @@ -10,13 +10,13 @@ To customize the colors used by default in your organization, head to the `Organ You will see a list of color palettes that you can choose from for each mode. You can create your own palette by clicking on the `Add new palette` button. Here you can preview the palette before you save it. -![](/images/references/workspace/customizing-the-appearance-of-your-project/appearance/list-color-palettes.png) +![](/images/references/workspace/customizing-the-appearance-of-your-project/list-color-palettes.png) ### Color palettes for light and dark mode You can configure different color palettes for light mode and dark mode to ensure optimal contrast and readability in each theme. The appearance configuration includes separate tabs for both light mode and dark mode, allowing you to customize color schemes for each theme independently. -![](/images/references/workspace/customizing-the-appearance-of-your-project/appearance/light-dark-mode-palette.gif) +![](/images/references/workspace/customizing-the-appearance-of-your-project/light-dark-mode-palette.gif) Since dark mode is available as a user preference setting within the app, having separate palettes ensures your charts look great regardless of which theme users choose. Note that dark mode is only available when using the app directly - embedded charts, Slack integrations, and other external integrations will continue to display in light mode. @@ -25,10 +25,10 @@ Since dark mode is available as a user preference setting within the app, having Once you have created your palette, you can set it as the default palette for the current mode (light or dark) by clicking on the `Use this theme` button. You can always come back to this page to change the default palette. -![](/images/references/workspace/customizing-the-appearance-of-your-project/appearance/use-theme.png) +![](/images/references/workspace/customizing-the-appearance-of-your-project/use-theme.png) ## Use the palette in a chart The next time you build a chart, it will default to using these colors for the series, unless you override them in the chart config. -![](/images/references/workspace/customizing-the-appearance-of-your-project/appearance/use-color-palette-chart.png) +![](/images/references/workspace/customizing-the-appearance-of-your-project/use-color-palette-chart.png) diff --git a/self-host/customize-deployment/enable-headless-browser-for-lightdash.mdx b/self-host/customize-deployment/enable-headless-browser-for-lightdash.mdx index 46e0d355..0d71f0ea 100644 --- a/self-host/customize-deployment/enable-headless-browser-for-lightdash.mdx +++ b/self-host/customize-deployment/enable-headless-browser-for-lightdash.mdx @@ -12,7 +12,7 @@ If you are running Lightdash on self-hosting, you will also have to run this hea ## How it works - ![](/images/self-host/customize-deployment/assets/images/headless-browser-schema-62b496e0d9f5f705ae823c7d4fdec946.png) + ![](/images/self-host/customize-deployment/enable-headless-browser-for-lightdash/headless-browser-schema-62b496e0d9f5f705ae823c7d4fdec946.png) When Lightdash needs to generate an image, it will open a new socket connection to the headless browser on `ws://HEADLESS_BROWSER_HOST:HEADLESS_BROWSER_PORT` From 1caef2d69b02136aa4f91eef3e8d5a7c361ea199 Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Thu, 8 Jan 2026 16:37:57 -0500 Subject: [PATCH 8/9] validate script also runs when changes are committed to a PR (for Tori specifically) --- .github/workflows/validate-docs.yml | 41 +++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml index 0538c9df..cf5f2eb5 100644 --- a/.github/workflows/validate-docs.yml +++ b/.github/workflows/validate-docs.yml @@ -2,6 +2,7 @@ name: Validate Documentation on: pull_request: + types: [opened, synchronize, reopened] # Run on PR open AND every commit branches: - main paths: @@ -43,15 +44,24 @@ jobs: echo "::endgroup::" continue-on-error: true - - name: Post PR comment - if: steps.check-links.outputs.issues_found == 'true' || steps.check-images.outputs.issues_found == 'true' + - name: Post or update PR comment + if: always() # Always run to update comment even if issues are fixed uses: actions/github-script@v7 with: script: | const fs = require('fs'); + // Build comment body let comment = '## πŸ“‹ Documentation Validation Report\n\n'; - comment += '⚠️ Some issues were found in this PR. These are **informational warnings** and will not block merging.\n\n'; + + const hasIssues = '${{ steps.check-links.outputs.issues_found }}' === 'true' || + '${{ steps.check-images.outputs.issues_found }}' === 'true'; + + if (hasIssues) { + comment += '⚠️ Some issues were found in this PR. These are **informational warnings** and will not block merging.\n\n'; + } else { + comment += 'βœ… All validation checks passed!\n\n'; + } if ('${{ steps.check-links.outputs.issues_found }}' === 'true') { const linksOutput = fs.readFileSync('/tmp/check-links-output.txt', 'utf8'); @@ -90,13 +100,34 @@ jobs: comment += '- All internal links must point to existing files\n\n'; comment += 'πŸ“– Run `node scripts/check-links.js` and `node scripts/check-image-locations.js` locally for full details.\n'; - github.rest.issues.createComment({ + // Find existing comment from this bot + const { data: comments } = await github.rest.issues.listComments({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: comment }); + const botComment = comments.find(c => + c.user.type === 'Bot' && c.body.includes('πŸ“‹ Documentation Validation Report') + ); + + // Update existing or create new + if (botComment) { + await github.rest.issues.updateComment({ + comment_id: botComment.id, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } else { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } + - name: Report results if: always() run: | From c3e6cc0e2c2fba4352891bbd0f597a410c90a01f Mon Sep 17 00:00:00 2001 From: Tori Whaley Date: Tue, 13 Jan 2026 15:34:17 -0500 Subject: [PATCH 9/9] test update --- guides/ai-agents.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/ai-agents.mdx b/guides/ai-agents.mdx index b2117957..68ce39c2 100644 --- a/guides/ai-agents.mdx +++ b/guides/ai-agents.mdx @@ -57,4 +57,4 @@ This guide covers everything you need to know about AI agents in Lightdash: - **Multiple agents** - Create specialized agents for different teams or use cases - **Evaluations** - Create test suites to validate your agent’s answers and prevent regressions. - **Conversations** - Monitor all agent interactions in one place, review feedback, and build evaluation sets directly from real threads. -- **Self-improvement (Beta)**- Agents can propose changes to your semantic layer \ No newline at end of file +- **Self-improvement (Beta)**- Agents can propose changes to your semantic layer