diff --git a/.github/workflows/sync-tutorial-styles.yml b/.github/workflows/sync-tutorial-styles.yml new file mode 100644 index 00000000000000..e18a80994e35c1 --- /dev/null +++ b/.github/workflows/sync-tutorial-styles.yml @@ -0,0 +1,98 @@ +name: Sync Tutorial Component Styles + +on: + # Run when DataHub web-react styles are updated + push: + paths: + - "datahub-web-react/src/alchemy-components/theme/**" + - "datahub-web-react/src/alchemy-components/theme/foundations/colors.ts" + - "datahub-web-react/src/alchemy-components/theme/semantic-tokens.ts" + + # Allow manual triggering + workflow_dispatch: + + # Run weekly to catch any missed updates + schedule: + - cron: "0 2 * * 1" # Every Monday at 2 AM UTC + +jobs: + sync-styles: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "18" + cache: "yarn" + cache-dependency-path: docs-website/yarn.lock + + - name: Install dependencies + working-directory: docs-website + run: yarn install --frozen-lockfile + + - name: Sync DataHub styles + working-directory: docs-website + run: yarn sync-datahub-styles + + - name: Check for changes + id: changes + run: | + if git diff --quiet; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Commit and push changes + if: steps.changes.outputs.changed == 'true' + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add docs-website/src/components/*/styles.module.css + git commit -m "🎨 Auto-sync tutorial component styles with DataHub UI + + - Updated design tokens from DataHub web-react + - Synced colors, spacing, and styling + - Ensures tutorial components match actual DataHub UI" + git push + + - name: Create PR comment (if applicable) + if: steps.changes.outputs.changed == 'true' && github.event_name == 'push' + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const sha = context.sha; + + // Find associated PR + const prs = await github.rest.pulls.list({ + owner, + repo, + state: 'open', + head: `${owner}:${context.ref.replace('refs/heads/', '')}` + }); + + if (prs.data.length > 0) { + const pr = prs.data[0]; + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pr.number, + body: `🎨 **Tutorial styles auto-updated!** + + The tutorial components have been automatically updated to match the latest DataHub UI styling changes. + + **What changed:** + - Design tokens synced from \`datahub-web-react\` + - Component colors and styling updated + - Tutorial components now match production DataHub UI + + This ensures users see consistent styling between tutorials and the actual DataHub interface.` + }); + } diff --git a/docs-website/README.md b/docs-website/README.md index d0129131be26de..6b5966637633a3 100644 --- a/docs-website/README.md +++ b/docs-website/README.md @@ -177,7 +177,7 @@ You can format all files by running: or by allowing pre-commit hooks to run when you commit your changes. -Warning: When using [admonitions](https://docusaurus.io/docs/markdown-features/admonitions#usage-with-prettier) (e.g. `:::note`), +Warning: When using [admonitions](https://docusaurus.io/docs/markdown-features/admonitions#usage-with-prettier) (e.g. `:::note`), you may need to add newlines around the inner text to avoid formatting issues. See the link for details. ## Docs site generation process diff --git a/docs-website/build.gradle b/docs-website/build.gradle index ee3cffe2e4cc71..d67f69e6b58415 100644 --- a/docs-website/build.gradle +++ b/docs-website/build.gradle @@ -110,7 +110,38 @@ task yarnInstall(type: YarnTask) { outputs.dir('node_modules') } -task yarnGenerate(type: YarnTask, dependsOn: [yarnInstall, +// Sync tutorial component styles with DataHub UI at build time +task syncDataHubStyles(type: YarnTask, dependsOn: [yarnInstall]) { + description = 'Sync tutorial component styles with actual DataHub UI design tokens' + + // Input: DataHub design token files + inputs.files( + file('../datahub-web-react/src/alchemy-components/theme/foundations/colors.ts'), + file('../datahub-web-react/src/alchemy-components/theme/semantic-tokens.ts'), + file('scripts/sync-datahub-styles.js') + ) + + // Output: Component style files + outputs.files( + file('src/components/DataHubEntityCard/styles.module.css'), + file('src/components/DataHubLineageNode/styles.module.css') + ) + + // Cache the sync result for performance + outputs.cacheIf { true } + + args = ['run', 'sync-datahub-styles'] + + doFirst { + logger.info('🎨 Syncing tutorial component styles with DataHub UI...') + } + + doLast { + logger.info('✅ Tutorial component styles synced successfully') + } +} + +task yarnGenerate(type: YarnTask, dependsOn: [yarnInstall, syncDataHubStyles, generateGraphQLSchema, generateJsonSchema, ':metadata-ingestion:modelDocGen', ':metadata-ingestion:docGen', ]) { @@ -142,8 +173,9 @@ task yarnLintFix(type: YarnTask, dependsOn: [yarnInstall]) { args = ['run', 'lint-fix'] } -task serve(type: YarnTask, dependsOn: [yarnInstall] ) { - args = ['run', 'serve'] +// Development server with hot reloads (recommended for development) +task dev(type: YarnTask, dependsOn: [yarnInstall, yarnGenerate, downloadHistoricalVersions]) { + args = ['run', 'start'] } @@ -167,6 +199,12 @@ task yarnBuild(type: YarnTask, dependsOn: [yarnLint, yarnGenerate, downloadHisto args = ['run', 'build'] } + +// Serve built site (requires build first) +task serve(type: YarnTask, dependsOn: [yarnBuild] ) { + args = ['run', 'serve'] +} + task yarnClear(type: YarnTask) { args = ['run','clear'] } diff --git a/docs-website/docusaurus.config.js b/docs-website/docusaurus.config.js index 0fd79f39701e29..d69fe1788e0a77 100644 --- a/docs-website/docusaurus.config.js +++ b/docs-website/docusaurus.config.js @@ -12,14 +12,17 @@ module.exports = { organizationName: "datahub-project", // Usually your GitHub org/user name. projectName: "datahub", // Usually your repo name. staticDirectories: ["static"], - stylesheets: ["https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;700&display=swap"], + stylesheets: [ + "https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;700&display=swap", + ], headTags: [ { - tagName: 'meta', + tagName: "meta", attributes: { - httpEquiv: 'Content-Security-Policy', - content: "frame-ancestors 'self' https://*.acryl.io https://acryldata.io http://localhost:*" - } + httpEquiv: "Content-Security-Policy", + content: + "frame-ancestors 'self' https://*.acryl.io https://acryldata.io http://localhost:*", + }, }, ], scripts: [ @@ -34,11 +37,11 @@ module.exports = { defer: true, }, { - src: "https://app.revenuehero.io/scheduler.min.js" + src: "https://app.revenuehero.io/scheduler.min.js", }, { src: "https://tag.clearbitscripts.com/v1/pk_2e321cabe30432a5c44c0424781aa35f/tags.js", - referrerPolicy: "strict-origin-when-cross-origin" + referrerPolicy: "strict-origin-when-cross-origin", }, { src: "/scripts/reo.js", @@ -54,7 +57,8 @@ module.exports = { "runllm-keyboard-shortcut": "Mod+j", "runllm-preset": "docusaurus", "runllm-theme-color": "#1890FF", - "runllm-brand-logo": "https://docs.datahub.com/img/datahub-logo-color-mark.svg", + "runllm-brand-logo": + "https://docs.datahub.com/img/datahub-logo-color-mark.svg", "runllm-community-url": "https://datahub.com/slack", "runllm-community-type": "slack", "runllm-disable-ask-a-person": "true", @@ -107,7 +111,7 @@ module.exports = { }, colorMode: { // Only support light mode. - defaultMode: 'light', + defaultMode: "light", disableSwitch: true, respectPrefersColorScheme: false, }, @@ -138,12 +142,13 @@ module.exports = { dropdownActiveClassDisabled: true, dropdownItemsAfter: [ { - type: 'html', + type: "html", value: '', }, { - type: 'html', - value: '', + type: "html", + value: + '', }, { value: ` @@ -339,7 +344,7 @@ module.exports = { versions: { current: { label: "Next", - banner: 'none', + banner: "none", }, }, path: "genDocs", @@ -355,7 +360,8 @@ module.exports = { blog: { blogTitle: "DataHub Learn", blogSidebarTitle: "DataHub Learn", - blogDescription: "Learn about the hot topics in the data ecosystem and how DataHub can help you with your data journey.", + blogDescription: + "Learn about the hot topics in the data ecosystem and how DataHub can help you with your data journey.", path: "src/learn", routeBasePath: "learn", postsPerPage: "ALL", @@ -363,7 +369,9 @@ module.exports = { }, theme: { customCss: [ - isSaas ? require.resolve("./src/styles/acryl.scss") : require.resolve("./src/styles/datahub.scss"), + isSaas + ? require.resolve("./src/styles/acryl.scss") + : require.resolve("./src/styles/datahub.scss"), require.resolve("./src/styles/global.scss"), require.resolve("./src/styles/sphinx.scss"), require.resolve("./src/styles/config-table.scss"), @@ -374,7 +382,7 @@ module.exports = { mdxPageComponent: "@theme/MDXPage", }, googleTagManager: { - containerId: 'GTM-5M8T9HNN', + containerId: "GTM-5M8T9HNN", }, gtag: { trackingID: "G-PKGVLETT4C", @@ -384,29 +392,32 @@ module.exports = { ], plugins: [ [ - '@docusaurus/plugin-client-redirects', + "@docusaurus/plugin-client-redirects", { createRedirects(existingPath) { - if (existingPath.includes('/docs')) { + if (existingPath.includes("/docs")) { return [ - existingPath.replace('/docs', '/docs/next'), - existingPath.replace('/docs', '/docs/0.13.0'), - existingPath.replace('/docs', '/docs/0.12.1'), - existingPath.replace('/docs', '/docs/0.11.0'), - existingPath.replace('/docs', '/docs/0.10.5'), + existingPath.replace("/docs", "/docs/next"), + existingPath.replace("/docs", "/docs/0.13.0"), + existingPath.replace("/docs", "/docs/0.12.1"), + existingPath.replace("/docs", "/docs/0.11.0"), + existingPath.replace("/docs", "/docs/0.10.5"), ]; } return undefined; // Return a falsy value: no redirect created }, redirects: [ { - from: '/docs/managed-datahub/operator-guide/setting-up-remote-ingestion-executor', - to: '/docs/managed-datahub/remote-executor/about', + from: "/docs/managed-datahub/operator-guide/setting-up-remote-ingestion-executor", + to: "/docs/managed-datahub/remote-executor/about", }, ], }, ], - ["@docusaurus/plugin-ideal-image", { quality: 100, sizes: [320, 640, 1280, 1440, 1600] }], + [ + "@docusaurus/plugin-ideal-image", + { quality: 100, sizes: [320, 640, 1280, 1440, 1600] }, + ], "docusaurus-plugin-sass", [ "docusaurus-graphql-plugin", diff --git a/docs-website/generateDocsDir.ts b/docs-website/generateDocsDir.ts index dbae4b5ee1e77f..b695ff60f66501 100644 --- a/docs-website/generateDocsDir.ts +++ b/docs-website/generateDocsDir.ts @@ -199,6 +199,7 @@ const allowed_broken_links = [ "docs/how/graph-onboarding.md", "docs/how/search-onboarding.md", "docs/how/build-metadata-service.md", + "docs/learn-datahub/overview.md", ]; function markdown_guess_title( diff --git a/docs-website/package.json b/docs-website/package.json index 5501cfc2a5d718..aad7485c55ef90 100644 --- a/docs-website/package.json +++ b/docs-website/package.json @@ -20,7 +20,8 @@ "lint-check": "prettier -l generateDocsDir.ts sidebars.js src/pages/index.js", "lint-fix": "prettier --write generateDocsDir.ts sidebars.js src/pages/index.js", "_list-link-check-files": "find ./genDocs -name '*.md' -not \\( -path './genDocs/python-sdk/*' -o -path './genDocs/releases.md' \\)", - "check-links": "yarn run -s _list-link-check-files -print0 | xargs -0 -n1 -t markdown-link-check -q -c markdown-link-check-config.json" + "check-links": "yarn run -s _list-link-check-files -print0 | xargs -0 -n1 -t markdown-link-check -q -c markdown-link-check-config.json", + "sync-datahub-styles": "node scripts/sync-datahub-styles.js" }, "dependencies": { "@ant-design/icons": "^4.7.0", @@ -48,6 +49,7 @@ "react": "^18.2.0", "react-dom": "18.2.0", "react-use-draggable-scroll": "^0.4.7", + "reactflow": "^11.11.4", "sass": "^1.43.2", "swc-loader": "^0.2.6", "swiper": "^11.1.4", diff --git a/docs-website/scripts/sync-datahub-styles.js b/docs-website/scripts/sync-datahub-styles.js new file mode 100644 index 00000000000000..87982cb6de52d4 --- /dev/null +++ b/docs-website/scripts/sync-datahub-styles.js @@ -0,0 +1,265 @@ +#!/usr/bin/env node + +/** + * Sync DataHub Styles Script + * + * This script automatically extracts design tokens from the DataHub web-react + * codebase and updates the tutorial component styles to match the actual UI. + * + * Usage: node scripts/sync-datahub-styles.js + */ + +const fs = require("fs"); +const path = require("path"); + +// Paths +const DATAHUB_COLORS_PATH = + "../../datahub-web-react/src/alchemy-components/theme/foundations/colors.ts"; +const DATAHUB_SEMANTIC_TOKENS_PATH = + "../../datahub-web-react/src/alchemy-components/theme/semantic-tokens.ts"; +const DOCS_COMPONENTS_DIR = "./src/components"; + +/** + * Extract color values from DataHub's colors.ts file + */ +function extractDataHubColors() { + try { + const colorsFile = fs.readFileSync( + path.resolve(__dirname, DATAHUB_COLORS_PATH), + "utf8", + ); + + // Extract color definitions using regex + const colorMatches = colorsFile.match(/(\w+):\s*{([^}]+)}/g) || []; + const singleColorMatches = colorsFile.match(/(\w+):\s*'([^']+)'/g) || []; + + const colors = {}; + + // Parse nested color objects (e.g., gray: { 100: '#EBECF0', ... }) + colorMatches.forEach((match) => { + const [, colorName, colorValues] = match.match(/(\w+):\s*{([^}]+)}/); + const values = {}; + + const valueMatches = colorValues.match(/(\d+):\s*'([^']+)'/g) || []; + valueMatches.forEach((valueMatch) => { + const [, key, value] = valueMatch.match(/(\d+):\s*'([^']+)'/); + values[key] = value; + }); + + colors[colorName] = values; + }); + + // Parse single color values (e.g., white: '#FFFFFF') + singleColorMatches.forEach((match) => { + const [, colorName, colorValue] = match.match(/(\w+):\s*'([^']+)'/); + colors[colorName] = colorValue; + }); + + return colors; + } catch (error) { + console.warn("Could not read DataHub colors file:", error.message); + return null; + } +} + +/** + * Extract semantic tokens from DataHub's semantic-tokens.ts file + */ +function extractSemanticTokens() { + try { + const semanticFile = fs.readFileSync( + path.resolve(__dirname, DATAHUB_SEMANTIC_TOKENS_PATH), + "utf8", + ); + + // Extract semantic token mappings + const tokenMatches = + semanticFile.match(/'([^']+)':\s*colors\.([^,\s]+)/g) || []; + const tokens = {}; + + tokenMatches.forEach((match) => { + const [, tokenName, colorPath] = match.match( + /'([^']+)':\s*colors\.([^,\s]+)/, + ); + tokens[tokenName] = colorPath; + }); + + return tokens; + } catch (error) { + console.warn("Could not read DataHub semantic tokens file:", error.message); + return null; + } +} + +/** + * Generate CSS variables from DataHub colors + */ +function generateCSSVariables(colors, semanticTokens) { + if (!colors) return ""; + + let cssVars = `/* Auto-generated DataHub Design Tokens */\n:root {\n`; + + // Core color mappings based on DataHub's actual usage + const colorMappings = { + "datahub-primary": + colors.primary?.[500] || colors.violet?.[500] || "#533FD1", + "datahub-primary-dark": + colors.primary?.[600] || colors.violet?.[600] || "#4C39BE", + "datahub-primary-light": + colors.primary?.[400] || colors.violet?.[400] || "#7565DA", + "datahub-primary-lightest": + colors.primary?.[0] || colors.violet?.[0] || "#F1F3FD", + "datahub-gray-100": colors.gray?.[100] || "#EBECF0", + "datahub-gray-600": colors.gray?.[600] || "#374066", + "datahub-gray-1700": colors.gray?.[1700] || "#5F6685", + "datahub-gray-1800": colors.gray?.[1800] || "#8088A3", + "datahub-gray-1500": colors.gray?.[1500] || "#F9FAFC", + "datahub-white": colors.white || "#FFFFFF", + "datahub-success": colors.green?.[500] || "#77B750", + "datahub-warning": colors.yellow?.[500] || "#EEAE09", + "datahub-error": colors.red?.[500] || "#CD0D24", + "datahub-border": colors.gray?.[1400] || "#E9EAEE", + }; + + // Add CSS variables + Object.entries(colorMappings).forEach(([varName, value]) => { + cssVars += ` --${varName}: ${value};\n`; + }); + + // Add shadows and other design tokens + cssVars += ` --datahub-shadow: 0px 1px 2px 0px rgba(33, 23, 95, 0.07);\n`; + cssVars += ` --datahub-shadow-hover: 0 2px 8px rgba(83, 63, 209, 0.15);\n`; + cssVars += ` --datahub-node-width: 320px;\n`; + cssVars += ` --datahub-node-height: 90px;\n`; + cssVars += ` --datahub-transformation-size: 40px;\n`; + cssVars += `}\n\n`; + + // Dark mode variables + cssVars += `/* Dark mode colors */\n[data-theme='dark'] {\n`; + const darkMappings = { + "datahub-primary": + colors.primary?.[400] || colors.violet?.[400] || "#7565DA", + "datahub-primary-dark": + colors.primary?.[500] || colors.violet?.[500] || "#533FD1", + "datahub-primary-light": + colors.primary?.[300] || colors.violet?.[300] || "#8C7EE0", + "datahub-primary-lightest": + colors.primary?.[800] || colors.violet?.[800] || "#2E2373", + "datahub-gray-100": colors.gray?.[700] || "#2F3657", + "datahub-gray-600": colors.gray?.[200] || "#CFD1DA", + "datahub-gray-1700": colors.gray?.[300] || "#A9ADBD", + "datahub-gray-1800": colors.gray?.[400] || "#81879F", + "datahub-gray-1500": colors.gray?.[2000] || "#1E2338", + "datahub-white": colors.gray?.[800] || "#272D48", + "datahub-border": colors.gray?.[600] || "#374066", + }; + + Object.entries(darkMappings).forEach(([varName, value]) => { + cssVars += ` --${varName}: ${value};\n`; + }); + + cssVars += `}\n\n`; + + return cssVars; +} + +/** + * Update component CSS files with new design tokens + */ +function updateComponentStyles(cssVariables) { + const componentDirs = ["DataHubEntityCard", "DataHubLineageNode"]; + + componentDirs.forEach((componentDir) => { + const styleFile = path.join( + DOCS_COMPONENTS_DIR, + componentDir, + "styles.module.css", + ); + + try { + let content = fs.readFileSync(styleFile, "utf8"); + + // Replace the CSS variables section + const variableRegex = + /\/\* Auto-generated DataHub Design Tokens \*\/[\s\S]*?}\s*\n\s*\n/; + + if (variableRegex.test(content)) { + content = content.replace(variableRegex, cssVariables); + } else { + // If no existing variables section, add at the top + content = cssVariables + content; + } + + fs.writeFileSync(styleFile, content); + console.log(`✅ Updated ${componentDir} styles`); + } catch (error) { + console.error(`❌ Failed to update ${componentDir}:`, error.message); + } + }); +} + +/** + * Main execution + */ +function main() { + console.log("🔄 Syncing DataHub styles...\n"); + + const colors = extractDataHubColors(); + const semanticTokens = extractSemanticTokens(); + + if (!colors) { + console.warn("⚠️ Could not extract DataHub colors from source files."); + console.log( + " Using fallback design tokens to ensure build continues...\n", + ); + + // Use fallback colors to ensure build doesn't fail + const fallbackColors = { + primary: { 500: "#533FD1", 600: "#4C39BE", 400: "#7565DA", 0: "#F1F3FD" }, + gray: { + 100: "#EBECF0", + 600: "#374066", + 1700: "#5F6685", + 1800: "#8088A3", + 1500: "#F9FAFC", + }, + white: "#FFFFFF", + green: { 500: "#77B750" }, + yellow: { 500: "#EEAE09" }, + red: { 500: "#CD0D24" }, + }; + + const cssVariables = generateCSSVariables(fallbackColors, null); + updateComponentStyles(cssVariables); + + console.log( + "✅ Applied fallback styling - components will use default DataHub colors", + ); + return; + } + + console.log("📊 Extracted DataHub design tokens"); + console.log(` - Colors: ${Object.keys(colors).length} palettes`); + console.log( + ` - Semantic tokens: ${semanticTokens ? Object.keys(semanticTokens).length : 0} mappings\n`, + ); + + const cssVariables = generateCSSVariables(colors, semanticTokens); + updateComponentStyles(cssVariables); + + console.log("\n🎉 DataHub styles sync completed!"); + console.log( + " Tutorial components now match the latest DataHub UI styling.", + ); +} + +// Run the script +if (require.main === module) { + main(); +} + +module.exports = { + extractDataHubColors, + generateCSSVariables, + updateComponentStyles, +}; diff --git a/docs-website/sidebars.js b/docs-website/sidebars.js index 27d538bea639f8..82d146451624a3 100644 --- a/docs-website/sidebars.js +++ b/docs-website/sidebars.js @@ -34,6 +34,176 @@ module.exports = { }, ], }, + { + type: "category", + label: "Learn DataHub", + collapsed: false, + link: { type: "doc", id: "docs/learn-datahub/overview" }, + items: [ + { + type: "category", + label: "DataHub Quickstart (30 min)", + collapsed: true, + link: { type: "doc", id: "docs/learn-datahub/quickstart/overview" }, + items: [ + { + type: "doc", + label: "Setup DataHub (5 min)", + id: "docs/learn-datahub/quickstart/setup", + }, + { + type: "doc", + label: "First Data Ingestion (10 min)", + id: "docs/learn-datahub/quickstart/first-ingestion", + }, + { + type: "doc", + label: "Discovery Basics (10 min)", + id: "docs/learn-datahub/quickstart/discovery-basics", + }, + { + type: "doc", + label: "Your First Lineage (5 min)", + id: "docs/learn-datahub/quickstart/first-lineage", + }, + ], + }, + { + type: "category", + label: "Data Discovery & Search (45 min)", + collapsed: true, + link: { type: "doc", id: "docs/learn-datahub/discovery/overview" }, + items: [ + { + type: "doc", + label: "Advanced Search Techniques (15 min)", + id: "docs/learn-datahub/discovery/advanced-search", + }, + { + type: "doc", + label: "Understanding Dataset Profiles (20 min)", + id: "docs/learn-datahub/discovery/dataset-profiles", + }, + { + type: "doc", + label: "Collaborative Discovery (10 min)", + id: "docs/learn-datahub/discovery/collaborative-discovery", + }, + ], + }, + { + type: "category", + label: "Data Lineage & Impact Analysis (40 min)", + collapsed: true, + link: { type: "doc", id: "docs/learn-datahub/lineage/overview" }, + items: [ + { + type: "doc", + label: "Reading Lineage Graphs (15 min)", + id: "docs/learn-datahub/lineage/reading-lineage", + }, + { + type: "doc", + label: "Performing Impact Analysis (15 min)", + id: "docs/learn-datahub/lineage/impact-analysis", + }, + { + type: "doc", + label: "Lineage Troubleshooting (10 min)", + id: "docs/learn-datahub/lineage/troubleshooting", + }, + ], + }, + { + type: "category", + label: "Data Governance Fundamentals (50 min)", + collapsed: true, + link: { type: "doc", id: "docs/learn-datahub/governance/overview" }, + items: [ + { + type: "doc", + label: "Ownership Management (12 min)", + id: "docs/learn-datahub/governance/ownership-management", + }, + { + type: "doc", + label: "Data Classification (15 min)", + id: "docs/learn-datahub/governance/data-classification", + }, + { + type: "doc", + label: "Business Glossary (12 min)", + id: "docs/learn-datahub/governance/business-glossary", + }, + { + type: "doc", + label: "Governance Policies (11 min)", + id: "docs/learn-datahub/governance/governance-policies", + }, + ], + }, + { + type: "category", + label: "Data Quality & Monitoring (45 min)", + collapsed: true, + link: { type: "doc", id: "docs/learn-datahub/quality/overview" }, + items: [ + { + type: "doc", + label: "Data Assertions (15 min)", + id: "docs/learn-datahub/quality/data-assertions", + }, + { + type: "doc", + label: "Quality Monitoring (12 min)", + id: "docs/learn-datahub/quality/quality-monitoring", + }, + { + type: "doc", + label: "Incident Management (10 min)", + id: "docs/learn-datahub/quality/incident-management", + }, + { + type: "doc", + label: "Quality Automation (8 min)", + id: "docs/learn-datahub/quality/quality-automation", + }, + ], + }, + { + type: "category", + label: "Data Ingestion Mastery (60 min)", + collapsed: true, + link: { type: "doc", id: "docs/learn-datahub/ingestion/overview" }, + items: [ + // Stubs or to-be-created pages can be added later + ], + }, + { + type: "category", + label: "Privacy & Compliance (35 min)", + collapsed: true, + link: { type: "doc", id: "docs/learn-datahub/privacy/overview" }, + items: [ + { + type: "doc", + label: "PII Detection (12 min)", + id: "docs/learn-datahub/privacy/pii-detection", + }, + { + type: "doc", + label: "Privacy Controls (12 min)", + id: "docs/learn-datahub/privacy/privacy-controls", + }, + { + type: "doc", + label: "Compliance Workflows (11 min)", + id: "docs/learn-datahub/privacy/compliance-workflows", + }, + ], + }, + ], + }, { type: "category", label: "Features", diff --git a/docs-website/src/components/ArchitectureDiagram/index.jsx b/docs-website/src/components/ArchitectureDiagram/index.jsx new file mode 100644 index 00000000000000..1e76aaf79279c4 --- /dev/null +++ b/docs-website/src/components/ArchitectureDiagram/index.jsx @@ -0,0 +1,112 @@ +import React from "react"; +import styles from "./styles.module.css"; + +const ArchitectureDiagram = ({ type = "integration" }) => { + if (type === "integration") { + return ( +
+
+ DataHub Integration Architecture +
+ +
+ {/* Source Systems Layer */} +
+
Source Systems
+
+
+
🗄️
+
Kafka Streams
+
Real-time Events
+
+
+
🏢
+
Hive Tables
+
Data Warehouse
+
+
+
📁
+
HDFS Files
+
Data Lake
+
+
+
+ + {/* Arrows */} +
+
+
+
+
+ + {/* DataHub Core Layer */} +
+
DataHub Core
+
+
+
🔗
+
Metadata API
+
GraphQL & REST
+
+
+
🕸️
+
Graph Database
+
Relationships
+
+
+
🔍
+
Search Index
+
Elasticsearch
+
+
+
+ + {/* Arrows */} +
+
+
+
+
+ + {/* User Interface Layer */} +
+
User Interface
+
+
+
🔎
+
Search & Browse
+
Data Discovery
+
+
+
🌐
+
Lineage View
+
Data Flow
+
+
+
📊
+
Data Profiles
+
Quality Metrics
+
+
+
+
+ +
+
+ Data Flow: + Extract Metadata + + Process & Store + + Search & Discover +
+
+
+ ); + } + + // Add other diagram types as needed + return null; +}; + +export default ArchitectureDiagram; diff --git a/docs-website/src/components/ArchitectureDiagram/styles.module.css b/docs-website/src/components/ArchitectureDiagram/styles.module.css new file mode 100644 index 00000000000000..27bee346df9831 --- /dev/null +++ b/docs-website/src/components/ArchitectureDiagram/styles.module.css @@ -0,0 +1,221 @@ +/* Architecture Diagram Styles */ +.architectureDiagram { + background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); + border: 2px solid var(--ifm-color-primary-light); + border-radius: 12px; + padding: 24px; + margin: 24px 0; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.diagramTitle { + text-align: center; + font-size: 1.4rem; + font-weight: 600; + color: var(--ifm-color-primary-dark); + margin-bottom: 24px; + padding-bottom: 12px; + border-bottom: 2px solid var(--ifm-color-primary-lightest); +} + +.diagramContainer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + flex-wrap: wrap; +} + +.layer { + flex: 1; + min-width: 200px; +} + +.layerTitle { + text-align: center; + font-size: 1.1rem; + font-weight: 600; + color: var(--ifm-color-emphasis-700); + margin-bottom: 16px; + padding: 8px 12px; + background: var(--ifm-color-primary-lightest); + border-radius: 6px; + border: 1px solid var(--ifm-color-primary-light); +} + +.nodeGroup { + display: flex; + flex-direction: column; + gap: 12px; +} + +.node { + background: white; + border: 2px solid; + border-radius: 8px; + padding: 12px; + text-align: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + transition: all 0.3s ease; + cursor: pointer; +} + +.node:hover { + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.sourceNode { + border-color: #10b981; + background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%); +} + +.sourceNode:hover { + border-color: #059669; + background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%); +} + +.coreNode { + border-color: #3b82f6; + background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); +} + +.coreNode:hover { + border-color: #2563eb; + background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%); +} + +.uiNode { + border-color: #8b5cf6; + background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%); +} + +.uiNode:hover { + border-color: #7c3aed; + background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%); +} + +.nodeIcon { + font-size: 1.5rem; + margin-bottom: 8px; +} + +.nodeLabel { + font-weight: 600; + font-size: 0.9rem; + color: var(--ifm-color-emphasis-800); + margin-bottom: 4px; +} + +.nodeSubtext { + font-size: 0.75rem; + color: var(--ifm-color-emphasis-600); + font-style: italic; +} + +.arrowLayer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 12px; + flex-shrink: 0; +} + +.arrow { + font-size: 1.5rem; + color: var(--ifm-color-primary); + font-weight: bold; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, + 100% { + opacity: 0.7; + } + 50% { + opacity: 1; + } +} + +.diagramFooter { + margin-top: 24px; + padding-top: 16px; + border-top: 1px solid var(--ifm-color-primary-lightest); +} + +.dataFlow { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + flex-wrap: wrap; +} + +.flowLabel { + font-weight: 600; + color: var(--ifm-color-primary-dark); +} + +.flowStep { + background: var(--ifm-color-primary-lightest); + padding: 6px 12px; + border-radius: 20px; + font-size: 0.85rem; + font-weight: 500; + color: var(--ifm-color-primary-dark); + border: 1px solid var(--ifm-color-primary-light); +} + +.flowArrow { + color: var(--ifm-color-primary); + font-weight: bold; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .diagramContainer { + flex-direction: column; + } + + .arrowLayer { + flex-direction: row; + transform: rotate(90deg); + } + + .arrow { + transform: rotate(90deg); + } + + .dataFlow { + flex-direction: column; + gap: 8px; + } +} + +/* Dark mode support */ +[data-theme="dark"] .architectureDiagram { + background: linear-gradient(135deg, #1e293b 0%, #334155 100%); + border-color: var(--ifm-color-primary-dark); +} + +[data-theme="dark"] .node { + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-emphasis-800); +} + +[data-theme="dark"] .sourceNode { + background: linear-gradient(135deg, #064e3b 0%, #065f46 100%); + color: #ecfdf5; +} + +[data-theme="dark"] .coreNode { + background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%); + color: #eff6ff; +} + +[data-theme="dark"] .uiNode { + background: linear-gradient(135deg, #581c87 0%, #6b21a8 100%); + color: #f3f4f6; +} diff --git a/docs-website/src/components/CardDropdown/CardDropdown.tsx b/docs-website/src/components/CardDropdown/CardDropdown.tsx index 5f896e1ecd8c04..1ac26329e9a31d 100644 --- a/docs-website/src/components/CardDropdown/CardDropdown.tsx +++ b/docs-website/src/components/CardDropdown/CardDropdown.tsx @@ -1,19 +1,22 @@ -import React, {useState, useRef, useEffect} from 'react'; -import clsx from 'clsx'; +import React, { useState, useRef, useEffect } from "react"; +import clsx from "clsx"; import { isRegexpStringMatch, useCollapsible, Collapsible, -} from '@docusaurus/theme-common'; -import {isSamePath, useLocalPathname} from '@docusaurus/theme-common/internal'; -import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink'; -import NavbarItem, {type LinkLikeNavbarItemProps} from '@theme/NavbarItem'; +} from "@docusaurus/theme-common"; +import { + isSamePath, + useLocalPathname, +} from "@docusaurus/theme-common/internal"; +import NavbarNavLink from "@theme/NavbarItem/NavbarNavLink"; +import NavbarItem, { type LinkLikeNavbarItemProps } from "@theme/NavbarItem"; import type { DesktopOrMobileNavBarItemProps, Props, -} from '@theme/NavbarItem/DropdownNavbarItem'; -import styles from './styles.module.scss'; -import Link from '@docusaurus/Link'; +} from "@theme/NavbarItem/DropdownNavbarItem"; +import styles from "./styles.module.scss"; +import Link from "@docusaurus/Link"; function isItemActive( item: LinkLikeNavbarItemProps, @@ -47,7 +50,7 @@ function DropdownNavbarItemDesktop({ ...props }: DesktopOrMobileNavBarItemProps) { const dropdownRef = useRef(null); - const [showDropdown, setShowDropdown] = useState(false); + const [showDropdown, setShowDropdown] = useState(false); useEffect(() => { const handleClickOutside = ( @@ -62,24 +65,25 @@ function DropdownNavbarItemDesktop({ setShowDropdown(false); }; - document.addEventListener('mousedown', handleClickOutside); - document.addEventListener('touchstart', handleClickOutside); - document.addEventListener('focusin', handleClickOutside); + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("touchstart", handleClickOutside); + document.addEventListener("focusin", handleClickOutside); return () => { - document.removeEventListener('mousedown', handleClickOutside); - document.removeEventListener('touchstart', handleClickOutside); - document.removeEventListener('focusin', handleClickOutside); + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("touchstart", handleClickOutside); + document.removeEventListener("focusin", handleClickOutside); }; }, [dropdownRef]); return (
+ className={clsx("navbar__item", "dropdown", "dropdown--hoverable", { + "dropdown--right": position === "right", + "dropdown--show": showDropdown, + })} + > tag focusable in case no link target // See https://github.com/facebook/docusaurus/pull/6003 // There's probably a better solution though... - href={props.to ? undefined : '#'} - className={clsx('navbar__link', className)} + href={props.to ? undefined : "#"} + className={clsx("navbar__link", className)} {...props} onClick={props.to ? undefined : (e) => e.preventDefault()} onKeyDown={(e) => { - if (e.key === 'Enter') { + if (e.key === "Enter") { e.preventDefault(); setShowDropdown(!showDropdown); } - }}> + }} + > {props.children ?? props.label}