/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import React from 'react'; import { Card, Collapse, Table, Tag, Typography, Alert, Space, Divider, Tabs, } from 'antd'; import { CheckCircleOutlined, CloseCircleOutlined, WarningOutlined, LinkOutlined, KeyOutlined, SettingOutlined, BookOutlined, EditOutlined, GithubOutlined, BugOutlined, } from '@ant-design/icons'; import type { DatabaseInfo } from './types'; // Simple code block component for connection strings const CodeBlock: React.FC<{ children: React.ReactNode }> = ({ children }) => (
    {children}
  
); const { Title, Paragraph, Text } = Typography; interface DatabasePageProps { database: DatabaseInfo; name: string; } // Feature badge component const FeatureBadge: React.FC<{ supported: boolean; label: string }> = ({ supported, label, }) => ( : } color={supported ? 'success' : 'default'} > {label} ); // Time grain badge const TimeGrainBadge: React.FC<{ supported: boolean; grain: string }> = ({ supported, grain, }) => ( {grain} ); const DatabasePage: React.FC = ({ database, name }) => { const { documentation: docs } = database; // Helper to render connection string with copy button const renderConnectionString = (connStr: string, description?: string) => (
{description && ( {description} )} {connStr}
); // Ensure db filename can be obtained regardless of how db doc gets generated // by either Flask app (superset.db_engine_specs.postgres) or fallback mode (postgres) const databaseModuleFilename = `${database.module?.split('.').pop()}.py`; // Render driver information const renderDrivers = () => { if (!docs?.drivers?.length) return null; return ( ({ key: String(idx), label: ( {driver.name} {driver.is_recommended && ( Recommended )} ), children: ( {driver.pypi_package && (
PyPI Package: {driver.pypi_package}
)} {driver.connection_string && renderConnectionString(driver.connection_string)} {driver.notes && ( )} {driver.docs_url && ( Documentation )}
), }))} />
); }; // Render authentication methods const renderAuthMethods = () => { if (!docs?.authentication_methods?.length) return null; return ( Authentication Methods } style={{ marginBottom: 16 }} > ({ key: String(idx), label: auth.name, children: ( <> {auth.description && {auth.description}} {auth.requirements && ( )} {auth.connection_string && renderConnectionString( auth.connection_string, 'Connection String', )} {auth.secure_extra && (
Secure Extra Configuration: {JSON.stringify(auth.secure_extra, null, 2)}
)} {auth.engine_parameters && (
Engine Parameters: {JSON.stringify(auth.engine_parameters, null, 2)}
)} {auth.notes && ( )} ), }))} />
); }; // Render engine parameters const renderEngineParams = () => { if (!docs?.engine_parameters?.length) return null; return ( Engine Parameters } style={{ marginBottom: 16 }} > ({ key: String(idx), label: param.name, children: ( <> {param.description && ( {param.description} )} {param.json && ( {JSON.stringify(param.json, null, 2)} )} {param.docs_url && ( Learn more )} ), }))} /> ); }; // Render compatible databases (for PostgreSQL, etc.) const renderCompatibleDatabases = () => { if (!docs?.compatible_databases?.length) return null; // Create array of all item keys to expand by default const allItemKeys = docs.compatible_databases.map((_, idx) => String(idx)); return ( The following databases are compatible with the {name} driver: ({ key: String(idx), label: (
{compat.logo && ( {compat.name} )} {compat.name}
), children: ( <> {compat.description && ( {compat.description} )} {compat.connection_string && renderConnectionString(compat.connection_string)} {compat.parameters && (
Parameters: ({ key, parameter: key, description: value, }), )} columns={[ { title: 'Parameter', dataIndex: 'parameter', key: 'p', }, { title: 'Description', dataIndex: 'description', key: 'd', }, ]} pagination={false} size="small" /> )} {compat.notes && ( )} ), }))} /> ); }; // Render feature matrix const renderFeatures = () => { const features: Array<{ key: keyof DatabaseInfo; label: string }> = [ { key: 'joins', label: 'JOINs' }, { key: 'subqueries', label: 'Subqueries' }, { key: 'supports_dynamic_schema', label: 'Dynamic Schema' }, { key: 'supports_catalog', label: 'Catalog Support' }, { key: 'supports_dynamic_catalog', label: 'Dynamic Catalog' }, { key: 'ssh_tunneling', label: 'SSH Tunneling' }, { key: 'query_cancelation', label: 'Query Cancellation' }, { key: 'supports_file_upload', label: 'File Upload' }, { key: 'user_impersonation', label: 'User Impersonation' }, { key: 'query_cost_estimation', label: 'Cost Estimation' }, { key: 'sql_validation', label: 'SQL Validation' }, ]; return (
{features.map(({ key, label }) => ( ))}
{database.score > 0 && (
Feature Score:{' '} {database.score}/{database.max_score}
)}
); }; // Render time grains const renderTimeGrains = () => { if (!database.time_grains) return null; const commonGrains = [ 'SECOND', 'MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'QUARTER', 'YEAR', ]; const extendedGrains = Object.keys(database.time_grains).filter( g => !commonGrains.includes(g), ); return (
Common Time Grains:
{commonGrains.map(grain => ( ))}
{extendedGrains.length > 0 && (
Extended Time Grains:
{extendedGrains.map(grain => ( ))}
)}
); }; // Render troubleshooting / custom errors section const renderTroubleshooting = () => { if (!docs?.custom_errors?.length) return null; // Group errors by category const errorsByCategory: Record = {}; for (const error of docs.custom_errors) { const category = error.category || 'General'; if (!errorsByCategory[category]) { errorsByCategory[category] = []; } errorsByCategory[category].push(error); } // Define category order for consistent display const categoryOrder = [ 'Authentication', 'Connection', 'Permissions', 'Query', 'Configuration', 'General', ]; const sortedCategories = Object.keys(errorsByCategory).sort((a, b) => { const aIdx = categoryOrder.indexOf(a); const bIdx = categoryOrder.indexOf(b); if (aIdx === -1 && bIdx === -1) return a.localeCompare(b); if (aIdx === -1) return 1; if (bIdx === -1) return -1; return aIdx - bIdx; }); // Category colors const categoryColors: Record = { Authentication: 'orange', Connection: 'red', Permissions: 'purple', Query: 'blue', Configuration: 'cyan', General: 'default', }; return ( Troubleshooting } style={{ marginBottom: 16 }} > Common error messages you may encounter when connecting to or querying{' '} {name}, along with their causes and solutions. ({ key: category, label: ( {category} {errorsByCategory[category].length} error {errorsByCategory[category].length !== 1 ? 's' : ''} ), children: ( <> {errorsByCategory[category].map((error, idx) => (
{error.description || error.error_type}
{error.invalid_fields && error.invalid_fields.length > 0 && (
Check these fields: {error.invalid_fields.map(field => ( {field} ))}
)} {error.issue_codes && error.issue_codes.length > 0 && (
Related issue codes: {error.issue_codes.map(code => ( Issue {code} ))}
)}
))} ), }))} />
); }; return (
{docs?.logo && ( {name} )} {name} {docs?.homepage_url && ( {docs.homepage_url} )}
{docs?.description && {docs.description}} {/* Warnings */} {docs?.warnings?.map((warning, idx) => ( } showIcon style={{ marginBottom: 16 }} /> ))} {/* Known Limitations */} {docs?.limitations?.length > 0 && (
    {docs.limitations.map((limitation, idx) => (
  • {limitation}
  • ))}
)} {/* Installation */} {(docs?.pypi_packages?.length || docs?.install_instructions) && ( {docs.pypi_packages?.length > 0 && (
Required packages: {docs.pypi_packages.map(pkg => ( {pkg} ))}
)} {docs.version_requirements && ( )} {docs.install_instructions && ( {docs.install_instructions} )}
)} {/* Basic Connection */} {docs?.connection_string && !docs?.drivers?.length && ( {renderConnectionString(docs.connection_string)} {docs.parameters && (
({ key, parameter: key, description: value, }), )} columns={[ { title: 'Parameter', dataIndex: 'parameter', key: 'p' }, { title: 'Description', dataIndex: 'description', key: 'd' }, ]} pagination={false} size="small" /> )} {docs.default_port && ( Default port: {docs.default_port} )} )} {/* Drivers */} {renderDrivers()} {/* Connection Examples */} {docs?.connection_examples?.length > 0 && ( {docs.connection_examples.map((example, idx) => (
{renderConnectionString( example.connection_string, example.description, )}
))}
)} {/* Authentication Methods */} {renderAuthMethods()} {/* Engine Parameters */} {renderEngineParams()} {/* Features */} {renderFeatures()} {/* Time Grains */} {renderTimeGrains()} {/* Troubleshooting / Custom Errors */} {renderTroubleshooting()} {/* Compatible Databases */} {renderCompatibleDatabases()} {/* Notes */} {docs?.notes && ( )} {/* External Links */} {(docs?.docs_url || docs?.tutorials?.length) && ( Resources } style={{ marginBottom: 16 }} > {docs.docs_url && ( Official Documentation )} {docs.sqlalchemy_docs_url && ( SQLAlchemy Dialect Documentation )} {docs.tutorials?.map((tutorial, idx) => ( Tutorial {idx + 1} ))} )} {/* Edit link */} {database.module && ( Help improve this documentation by editing the engine spec: Edit {databaseModuleFilename} )} ); }; export default DatabasePage;