mirror of
https://github.com/apache/superset.git
synced 2026-04-16 22:55:52 +00:00
feat(docs): auto-generate database documentation from lib.py (#36805)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,7 @@ interface SectionHeaderProps {
|
||||
title: string;
|
||||
subtitle?: string | ReactNode;
|
||||
dark?: boolean;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
const SectionHeader = ({
|
||||
@@ -105,15 +106,24 @@ const SectionHeader = ({
|
||||
title,
|
||||
subtitle,
|
||||
dark,
|
||||
link,
|
||||
}: SectionHeaderProps) => {
|
||||
const Heading = level;
|
||||
|
||||
const StyledRoot =
|
||||
level === 'h1' ? StyledSectionHeaderH1 : StyledSectionHeaderH2;
|
||||
|
||||
const titleContent = link ? (
|
||||
<a href={link} style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||
{title}
|
||||
</a>
|
||||
) : (
|
||||
title
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledRoot dark={!!dark}>
|
||||
<Heading className="title">{title}</Heading>
|
||||
<Heading className="title">{titleContent}</Heading>
|
||||
<img className="line" src="/img/community/line.png" alt="line" />
|
||||
{subtitle && <div className="subtitle">{subtitle}</div>}
|
||||
</StyledRoot>
|
||||
|
||||
578
docs/src/components/databases/DatabaseIndex.tsx
Normal file
578
docs/src/components/databases/DatabaseIndex.tsx
Normal file
@@ -0,0 +1,578 @@
|
||||
/**
|
||||
* 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, { useState, useMemo } from 'react';
|
||||
import { Card, Row, Col, Statistic, Table, Tag, Input, Select, Tooltip } from 'antd';
|
||||
import {
|
||||
DatabaseOutlined,
|
||||
CheckCircleOutlined,
|
||||
ApiOutlined,
|
||||
KeyOutlined,
|
||||
SearchOutlined,
|
||||
LinkOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { DatabaseData, DatabaseInfo, TimeGrains } from './types';
|
||||
|
||||
interface DatabaseIndexProps {
|
||||
data: DatabaseData;
|
||||
}
|
||||
|
||||
// Type for table entries (includes both regular DBs and compatible DBs)
|
||||
interface TableEntry {
|
||||
name: string;
|
||||
categories: string[]; // Multiple categories supported
|
||||
score: number;
|
||||
max_score: number;
|
||||
timeGrainCount: number;
|
||||
time_grains?: TimeGrains;
|
||||
hasDrivers: boolean;
|
||||
hasAuthMethods: boolean;
|
||||
hasConnectionString: boolean;
|
||||
joins?: boolean;
|
||||
subqueries?: boolean;
|
||||
supports_dynamic_schema?: boolean;
|
||||
supports_catalog?: boolean;
|
||||
ssh_tunneling?: boolean;
|
||||
supports_file_upload?: boolean;
|
||||
query_cancelation?: boolean;
|
||||
query_cost_estimation?: boolean;
|
||||
user_impersonation?: boolean;
|
||||
sql_validation?: boolean;
|
||||
documentation?: DatabaseInfo['documentation'];
|
||||
// For compatible databases
|
||||
isCompatible?: boolean;
|
||||
compatibleWith?: string;
|
||||
compatibleDescription?: string;
|
||||
}
|
||||
|
||||
// Map category constant names to display names
|
||||
const CATEGORY_DISPLAY_NAMES: Record<string, string> = {
|
||||
'CLOUD_AWS': 'Cloud - AWS',
|
||||
'CLOUD_GCP': 'Cloud - Google',
|
||||
'CLOUD_AZURE': 'Cloud - Azure',
|
||||
'CLOUD_DATA_WAREHOUSES': 'Cloud Data Warehouses',
|
||||
'APACHE_PROJECTS': 'Apache Projects',
|
||||
'TRADITIONAL_RDBMS': 'Traditional RDBMS',
|
||||
'ANALYTICAL_DATABASES': 'Analytical Databases',
|
||||
'SEARCH_NOSQL': 'Search & NoSQL',
|
||||
'QUERY_ENGINES': 'Query Engines',
|
||||
'TIME_SERIES': 'Time Series Databases',
|
||||
'OTHER': 'Other Databases',
|
||||
'OPEN_SOURCE': 'Open Source',
|
||||
'HOSTED_OPEN_SOURCE': 'Hosted Open Source',
|
||||
'PROPRIETARY': 'Proprietary',
|
||||
};
|
||||
|
||||
// Category colors for visual distinction
|
||||
const CATEGORY_COLORS: Record<string, string> = {
|
||||
'Cloud - AWS': 'orange',
|
||||
'Cloud - Google': 'blue',
|
||||
'Cloud - Azure': 'cyan',
|
||||
'Cloud Data Warehouses': 'purple',
|
||||
'Apache Projects': 'red',
|
||||
'Traditional RDBMS': 'green',
|
||||
'Analytical Databases': 'magenta',
|
||||
'Search & NoSQL': 'gold',
|
||||
'Query Engines': 'lime',
|
||||
'Time Series Databases': 'volcano',
|
||||
'Other Databases': 'default',
|
||||
// Licensing categories
|
||||
'Open Source': 'geekblue',
|
||||
'Hosted Open Source': 'cyan',
|
||||
'Proprietary': 'default',
|
||||
};
|
||||
|
||||
// Convert category constant to display name
|
||||
function getCategoryDisplayName(cat: string): string {
|
||||
return CATEGORY_DISPLAY_NAMES[cat] || cat;
|
||||
}
|
||||
|
||||
// Get categories for a database - uses categories from metadata when available
|
||||
// Falls back to name-based inference for compatible databases without categories
|
||||
function getCategories(
|
||||
name: string,
|
||||
documentationCategories?: string[]
|
||||
): string[] {
|
||||
// Prefer categories from documentation metadata (computed by Python)
|
||||
if (documentationCategories && documentationCategories.length > 0) {
|
||||
return documentationCategories.map(getCategoryDisplayName);
|
||||
}
|
||||
|
||||
// Fallback: infer from name (for compatible databases without categories)
|
||||
const nameLower = name.toLowerCase();
|
||||
|
||||
if (nameLower.includes('aws') || nameLower.includes('amazon'))
|
||||
return ['Cloud - AWS'];
|
||||
if (nameLower.includes('google') || nameLower.includes('bigquery'))
|
||||
return ['Cloud - Google'];
|
||||
if (nameLower.includes('azure') || nameLower.includes('microsoft'))
|
||||
return ['Cloud - Azure'];
|
||||
if (nameLower.includes('snowflake') || nameLower.includes('databricks'))
|
||||
return ['Cloud Data Warehouses'];
|
||||
if (
|
||||
nameLower.includes('apache') ||
|
||||
nameLower.includes('druid') ||
|
||||
nameLower.includes('hive') ||
|
||||
nameLower.includes('spark')
|
||||
)
|
||||
return ['Apache Projects'];
|
||||
if (
|
||||
nameLower.includes('postgres') ||
|
||||
nameLower.includes('mysql') ||
|
||||
nameLower.includes('sqlite') ||
|
||||
nameLower.includes('mariadb')
|
||||
)
|
||||
return ['Traditional RDBMS'];
|
||||
if (
|
||||
nameLower.includes('clickhouse') ||
|
||||
nameLower.includes('vertica') ||
|
||||
nameLower.includes('starrocks')
|
||||
)
|
||||
return ['Analytical Databases'];
|
||||
if (
|
||||
nameLower.includes('elastic') ||
|
||||
nameLower.includes('solr') ||
|
||||
nameLower.includes('couchbase')
|
||||
)
|
||||
return ['Search & NoSQL'];
|
||||
if (nameLower.includes('trino') || nameLower.includes('presto'))
|
||||
return ['Query Engines'];
|
||||
|
||||
return ['Other Databases'];
|
||||
}
|
||||
|
||||
// Count supported time grains
|
||||
function countTimeGrains(db: DatabaseInfo): number {
|
||||
if (!db.time_grains) return 0;
|
||||
return Object.values(db.time_grains).filter(Boolean).length;
|
||||
}
|
||||
|
||||
// Format time grain name for display (e.g., FIVE_MINUTES -> "5 min")
|
||||
function formatTimeGrain(grain: string): string {
|
||||
const mapping: Record<string, string> = {
|
||||
SECOND: 'Second',
|
||||
FIVE_SECONDS: '5 sec',
|
||||
THIRTY_SECONDS: '30 sec',
|
||||
MINUTE: 'Minute',
|
||||
FIVE_MINUTES: '5 min',
|
||||
TEN_MINUTES: '10 min',
|
||||
FIFTEEN_MINUTES: '15 min',
|
||||
THIRTY_MINUTES: '30 min',
|
||||
HALF_HOUR: '30 min',
|
||||
HOUR: 'Hour',
|
||||
SIX_HOURS: '6 hours',
|
||||
DAY: 'Day',
|
||||
WEEK: 'Week',
|
||||
WEEK_STARTING_SUNDAY: 'Week (Sun)',
|
||||
WEEK_STARTING_MONDAY: 'Week (Mon)',
|
||||
WEEK_ENDING_SATURDAY: 'Week (→Sat)',
|
||||
WEEK_ENDING_SUNDAY: 'Week (→Sun)',
|
||||
MONTH: 'Month',
|
||||
QUARTER: 'Quarter',
|
||||
QUARTER_YEAR: 'Quarter',
|
||||
YEAR: 'Year',
|
||||
};
|
||||
return mapping[grain] || grain;
|
||||
}
|
||||
|
||||
// Get list of supported time grains for tooltip
|
||||
function getSupportedTimeGrains(timeGrains?: TimeGrains): string[] {
|
||||
if (!timeGrains) return [];
|
||||
return Object.entries(timeGrains)
|
||||
.filter(([, supported]) => supported)
|
||||
.map(([grain]) => formatTimeGrain(grain));
|
||||
}
|
||||
|
||||
const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [categoryFilter, setCategoryFilter] = useState<string | null>(null);
|
||||
|
||||
const { statistics, databases } = data;
|
||||
|
||||
// Convert databases object to array, including compatible databases
|
||||
const databaseList = useMemo(() => {
|
||||
const entries: TableEntry[] = [];
|
||||
|
||||
Object.entries(databases).forEach(([name, db]) => {
|
||||
// Add the main database
|
||||
// Use categories from documentation metadata (computed by Python) when available
|
||||
entries.push({
|
||||
...db,
|
||||
name,
|
||||
categories: getCategories(name, db.documentation?.categories),
|
||||
timeGrainCount: countTimeGrains(db),
|
||||
hasDrivers: (db.documentation?.drivers?.length ?? 0) > 0,
|
||||
hasAuthMethods: (db.documentation?.authentication_methods?.length ?? 0) > 0,
|
||||
hasConnectionString: Boolean(
|
||||
db.documentation?.connection_string ||
|
||||
(db.documentation?.drivers?.length ?? 0) > 0
|
||||
),
|
||||
isCompatible: false,
|
||||
});
|
||||
|
||||
// Add compatible databases from this database's documentation
|
||||
const compatibleDbs = db.documentation?.compatible_databases ?? [];
|
||||
compatibleDbs.forEach((compat) => {
|
||||
// Check if this compatible DB already exists as a main entry
|
||||
const existsAsMain = Object.keys(databases).some(
|
||||
(dbName) => dbName.toLowerCase() === compat.name.toLowerCase()
|
||||
);
|
||||
|
||||
if (!existsAsMain) {
|
||||
// Compatible databases: use their categories if defined, or infer from name
|
||||
entries.push({
|
||||
name: compat.name,
|
||||
categories: getCategories(compat.name, compat.categories),
|
||||
// Compatible DBs inherit scores from parent
|
||||
score: db.score,
|
||||
max_score: db.max_score,
|
||||
timeGrainCount: countTimeGrains(db),
|
||||
hasDrivers: false,
|
||||
hasAuthMethods: false,
|
||||
hasConnectionString: Boolean(compat.connection_string),
|
||||
joins: db.joins,
|
||||
subqueries: db.subqueries,
|
||||
supports_dynamic_schema: db.supports_dynamic_schema,
|
||||
supports_catalog: db.supports_catalog,
|
||||
ssh_tunneling: db.ssh_tunneling,
|
||||
documentation: {
|
||||
description: compat.description,
|
||||
connection_string: compat.connection_string,
|
||||
pypi_packages: compat.pypi_packages,
|
||||
},
|
||||
isCompatible: true,
|
||||
compatibleWith: name,
|
||||
compatibleDescription: `Uses ${name} driver`,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return entries;
|
||||
}, [databases]);
|
||||
|
||||
// Filter and sort databases
|
||||
const filteredDatabases = useMemo(() => {
|
||||
return databaseList
|
||||
.filter((db) => {
|
||||
const matchesSearch =
|
||||
!searchText ||
|
||||
db.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
db.documentation?.description
|
||||
?.toLowerCase()
|
||||
.includes(searchText.toLowerCase());
|
||||
const matchesCategory = !categoryFilter || db.categories.includes(categoryFilter);
|
||||
return matchesSearch && matchesCategory;
|
||||
})
|
||||
.sort((a, b) => b.score - a.score);
|
||||
}, [databaseList, searchText, categoryFilter]);
|
||||
|
||||
// Get unique categories and counts for filter
|
||||
const { categories, categoryCounts } = useMemo(() => {
|
||||
const counts: Record<string, number> = {};
|
||||
databaseList.forEach((db) => {
|
||||
// Count each category the database belongs to
|
||||
db.categories.forEach((cat) => {
|
||||
counts[cat] = (counts[cat] || 0) + 1;
|
||||
});
|
||||
});
|
||||
return {
|
||||
categories: Object.keys(counts).sort(),
|
||||
categoryCounts: counts,
|
||||
};
|
||||
}, [databaseList]);
|
||||
|
||||
// Table columns
|
||||
const columns = [
|
||||
{
|
||||
title: 'Database',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
sorter: (a: TableEntry, b: TableEntry) => a.name.localeCompare(b.name),
|
||||
render: (name: string, record: TableEntry) => {
|
||||
// Convert name to URL slug
|
||||
const toSlug = (n: string) => n.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
||||
|
||||
// Link to parent for compatible DBs, otherwise to own page
|
||||
const linkTarget = record.isCompatible && record.compatibleWith
|
||||
? `/docs/databases/supported/${toSlug(record.compatibleWith)}`
|
||||
: `/docs/databases/supported/${toSlug(name)}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<a href={linkTarget}>
|
||||
<strong>{name}</strong>
|
||||
</a>
|
||||
{record.isCompatible && record.compatibleWith && (
|
||||
<Tag
|
||||
icon={<LinkOutlined />}
|
||||
color="geekblue"
|
||||
style={{ marginLeft: 8, fontSize: '11px' }}
|
||||
>
|
||||
{record.compatibleWith} compatible
|
||||
</Tag>
|
||||
)}
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
{record.documentation?.description?.slice(0, 80)}
|
||||
{(record.documentation?.description?.length ?? 0) > 80 ? '...' : ''}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Categories',
|
||||
dataIndex: 'categories',
|
||||
key: 'categories',
|
||||
width: 220,
|
||||
filters: categories.map((cat) => ({ text: cat, value: cat })),
|
||||
onFilter: (value: React.Key | boolean, record: TableEntry) =>
|
||||
record.categories.includes(value as string),
|
||||
render: (cats: string[]) => (
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
{cats.map((cat) => (
|
||||
<Tag key={cat} color={CATEGORY_COLORS[cat] || 'default'}>{cat}</Tag>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Score',
|
||||
dataIndex: 'score',
|
||||
key: 'score',
|
||||
width: 80,
|
||||
sorter: (a: TableEntry, b: TableEntry) => a.score - b.score,
|
||||
defaultSortOrder: 'descend' as const,
|
||||
render: (score: number, record: TableEntry) => (
|
||||
<span
|
||||
style={{
|
||||
color: score > 150 ? '#52c41a' : score > 100 ? '#1890ff' : '#666',
|
||||
fontWeight: score > 150 ? 'bold' : 'normal',
|
||||
}}
|
||||
>
|
||||
{score}/{record.max_score}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Time Grains',
|
||||
dataIndex: 'timeGrainCount',
|
||||
key: 'timeGrainCount',
|
||||
width: 100,
|
||||
sorter: (a: TableEntry, b: TableEntry) => a.timeGrainCount - b.timeGrainCount,
|
||||
render: (count: number, record: TableEntry) => {
|
||||
if (count === 0) return <span>-</span>;
|
||||
const grains = getSupportedTimeGrains(record.time_grains);
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px', maxWidth: 280 }}>
|
||||
{grains.map((grain) => (
|
||||
<Tag key={grain} style={{ margin: 0 }}>{grain}</Tag>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<span style={{ cursor: 'help', borderBottom: '1px dotted #999' }}>
|
||||
{count} grains
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Features',
|
||||
key: 'features',
|
||||
width: 280,
|
||||
filters: [
|
||||
{ text: 'JOINs', value: 'joins' },
|
||||
{ text: 'Subqueries', value: 'subqueries' },
|
||||
{ text: 'Dynamic Schema', value: 'dynamic_schema' },
|
||||
{ text: 'Catalog', value: 'catalog' },
|
||||
{ text: 'SSH Tunneling', value: 'ssh' },
|
||||
{ text: 'File Upload', value: 'file_upload' },
|
||||
{ text: 'Query Cancel', value: 'query_cancel' },
|
||||
{ text: 'Cost Estimation', value: 'cost_estimation' },
|
||||
{ text: 'User Impersonation', value: 'impersonation' },
|
||||
{ text: 'SQL Validation', value: 'sql_validation' },
|
||||
],
|
||||
onFilter: (value: React.Key | boolean, record: TableEntry) => {
|
||||
switch (value) {
|
||||
case 'joins':
|
||||
return Boolean(record.joins);
|
||||
case 'subqueries':
|
||||
return Boolean(record.subqueries);
|
||||
case 'dynamic_schema':
|
||||
return Boolean(record.supports_dynamic_schema);
|
||||
case 'catalog':
|
||||
return Boolean(record.supports_catalog);
|
||||
case 'ssh':
|
||||
return Boolean(record.ssh_tunneling);
|
||||
case 'file_upload':
|
||||
return Boolean(record.supports_file_upload);
|
||||
case 'query_cancel':
|
||||
return Boolean(record.query_cancelation);
|
||||
case 'cost_estimation':
|
||||
return Boolean(record.query_cost_estimation);
|
||||
case 'impersonation':
|
||||
return Boolean(record.user_impersonation);
|
||||
case 'sql_validation':
|
||||
return Boolean(record.sql_validation);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
},
|
||||
render: (_: unknown, record: TableEntry) => (
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
{record.joins && <Tag color="green">JOINs</Tag>}
|
||||
{record.subqueries && <Tag color="green">Subqueries</Tag>}
|
||||
{record.supports_dynamic_schema && <Tag color="blue">Dynamic Schema</Tag>}
|
||||
{record.supports_catalog && <Tag color="purple">Catalog</Tag>}
|
||||
{record.ssh_tunneling && <Tag color="cyan">SSH</Tag>}
|
||||
{record.supports_file_upload && <Tag color="orange">File Upload</Tag>}
|
||||
{record.query_cancelation && <Tag color="volcano">Query Cancel</Tag>}
|
||||
{record.query_cost_estimation && <Tag color="gold">Cost Est.</Tag>}
|
||||
{record.user_impersonation && <Tag color="magenta">Impersonation</Tag>}
|
||||
{record.sql_validation && <Tag color="lime">SQL Validation</Tag>}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
key: 'docs',
|
||||
width: 150,
|
||||
render: (_: unknown, record: TableEntry) => (
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
{record.hasConnectionString && (
|
||||
<Tag icon={<ApiOutlined />} color="default">
|
||||
Connection
|
||||
</Tag>
|
||||
)}
|
||||
{record.hasDrivers && (
|
||||
<Tag icon={<DatabaseOutlined />} color="default">
|
||||
Drivers
|
||||
</Tag>
|
||||
)}
|
||||
{record.hasAuthMethods && (
|
||||
<Tag icon={<KeyOutlined />} color="default">
|
||||
Auth
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="database-index">
|
||||
{/* Statistics Cards */}
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="Total Databases"
|
||||
value={statistics.totalDatabases}
|
||||
prefix={<DatabaseOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="With Documentation"
|
||||
value={statistics.withDocumentation}
|
||||
prefix={<CheckCircleOutlined />}
|
||||
suffix={`/ ${statistics.totalDatabases}`}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="Multiple Drivers"
|
||||
value={statistics.withDrivers}
|
||||
prefix={<ApiOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="Auth Methods"
|
||||
value={statistics.withAuthMethods}
|
||||
prefix={<KeyOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Filters */}
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Input
|
||||
placeholder="Search databases..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12}>
|
||||
<Select
|
||||
placeholder="Filter by category"
|
||||
style={{ width: '100%' }}
|
||||
value={categoryFilter}
|
||||
onChange={setCategoryFilter}
|
||||
allowClear
|
||||
options={categories.map((cat) => ({
|
||||
label: (
|
||||
<span>
|
||||
<Tag
|
||||
color={CATEGORY_COLORS[cat] || 'default'}
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
{categoryCounts[cat] || 0}
|
||||
</Tag>
|
||||
{cat}
|
||||
</span>
|
||||
),
|
||||
value: cat,
|
||||
}))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Database Table */}
|
||||
<Table
|
||||
dataSource={filteredDatabases}
|
||||
columns={columns}
|
||||
rowKey={(record) => record.isCompatible ? `${record.compatibleWith}-${record.name}` : record.name}
|
||||
pagination={{
|
||||
pageSize: 20,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `${total} databases`,
|
||||
}}
|
||||
size="middle"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatabaseIndex;
|
||||
634
docs/src/components/databases/DatabasePage.tsx
Normal file
634
docs/src/components/databases/DatabasePage.tsx
Normal file
@@ -0,0 +1,634 @@
|
||||
/**
|
||||
* 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,
|
||||
} from '@ant-design/icons';
|
||||
import type { DatabaseInfo } from './types';
|
||||
|
||||
// Simple code block component for connection strings
|
||||
const CodeBlock: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<pre
|
||||
style={{
|
||||
background: 'var(--ifm-code-background)',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '4px',
|
||||
overflow: 'auto',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'var(--ifm-font-family-monospace)',
|
||||
}}
|
||||
>
|
||||
<code>{children}</code>
|
||||
</pre>
|
||||
);
|
||||
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
const { Panel } = Collapse;
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
interface DatabasePageProps {
|
||||
database: DatabaseInfo;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// Feature badge component
|
||||
const FeatureBadge: React.FC<{ supported: boolean; label: string }> = ({
|
||||
supported,
|
||||
label,
|
||||
}) => (
|
||||
<Tag
|
||||
icon={supported ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
|
||||
color={supported ? 'success' : 'default'}
|
||||
>
|
||||
{label}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
// Time grain badge
|
||||
const TimeGrainBadge: React.FC<{ supported: boolean; grain: string }> = ({
|
||||
supported,
|
||||
grain,
|
||||
}) => (
|
||||
<Tag color={supported ? 'blue' : 'default'} style={{ margin: '2px' }}>
|
||||
{grain}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
|
||||
const { documentation: docs } = database;
|
||||
|
||||
// Helper to render connection string with copy button
|
||||
const renderConnectionString = (connStr: string, description?: string) => (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
{description && (
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 4 }}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
<CodeBlock>{connStr}</CodeBlock>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Render driver information
|
||||
const renderDrivers = () => {
|
||||
if (!docs?.drivers?.length) return null;
|
||||
|
||||
return (
|
||||
<Card title="Drivers" style={{ marginBottom: 16 }}>
|
||||
<Tabs>
|
||||
{docs.drivers.map((driver, idx) => (
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
{driver.name}
|
||||
{driver.is_recommended && (
|
||||
<Tag color="green" style={{ marginLeft: 8 }}>
|
||||
Recommended
|
||||
</Tag>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
key={idx}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{driver.pypi_package && (
|
||||
<div>
|
||||
<Text strong>PyPI Package: </Text>
|
||||
<code>{driver.pypi_package}</code>
|
||||
</div>
|
||||
)}
|
||||
{driver.connection_string &&
|
||||
renderConnectionString(driver.connection_string)}
|
||||
{driver.notes && (
|
||||
<Alert message={driver.notes} type="info" showIcon />
|
||||
)}
|
||||
{driver.docs_url && (
|
||||
<a href={driver.docs_url} target="_blank" rel="noreferrer">
|
||||
<LinkOutlined /> Documentation
|
||||
</a>
|
||||
)}
|
||||
</Space>
|
||||
</TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// Render authentication methods
|
||||
const renderAuthMethods = () => {
|
||||
if (!docs?.authentication_methods?.length) return null;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
<>
|
||||
<KeyOutlined /> Authentication Methods
|
||||
</>
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<Collapse accordion>
|
||||
{docs.authentication_methods.map((auth, idx) => (
|
||||
<Panel header={auth.name} key={idx}>
|
||||
{auth.description && <Paragraph>{auth.description}</Paragraph>}
|
||||
{auth.requirements && (
|
||||
<Alert
|
||||
message="Requirements"
|
||||
description={auth.requirements}
|
||||
type="warning"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
{auth.connection_string &&
|
||||
renderConnectionString(
|
||||
auth.connection_string,
|
||||
'Connection String'
|
||||
)}
|
||||
{auth.secure_extra && (
|
||||
<div>
|
||||
<Text strong>Secure Extra Configuration:</Text>
|
||||
<CodeBlock>
|
||||
{JSON.stringify(auth.secure_extra, null, 2)}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
)}
|
||||
{auth.engine_parameters && (
|
||||
<div>
|
||||
<Text strong>Engine Parameters:</Text>
|
||||
<CodeBlock>
|
||||
{JSON.stringify(auth.engine_parameters, null, 2)}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
)}
|
||||
{auth.notes && (
|
||||
<Alert message={auth.notes} type="info" showIcon />
|
||||
)}
|
||||
</Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// Render engine parameters
|
||||
const renderEngineParams = () => {
|
||||
if (!docs?.engine_parameters?.length) return null;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
<>
|
||||
<SettingOutlined /> Engine Parameters
|
||||
</>
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<Collapse>
|
||||
{docs.engine_parameters.map((param, idx) => (
|
||||
<Panel header={param.name} key={idx}>
|
||||
{param.description && <Paragraph>{param.description}</Paragraph>}
|
||||
{param.json && (
|
||||
<CodeBlock>
|
||||
{JSON.stringify(param.json, null, 2)}
|
||||
</CodeBlock>
|
||||
)}
|
||||
{param.docs_url && (
|
||||
<a href={param.docs_url} target="_blank" rel="noreferrer">
|
||||
<LinkOutlined /> Learn more
|
||||
</a>
|
||||
)}
|
||||
</Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// Render compatible databases (for PostgreSQL, etc.)
|
||||
const renderCompatibleDatabases = () => {
|
||||
if (!docs?.compatible_databases?.length) return null;
|
||||
|
||||
// Create array of all panel keys to expand by default
|
||||
const allPanelKeys = docs.compatible_databases.map((_, idx) => idx);
|
||||
|
||||
return (
|
||||
<Card title="Compatible Databases" style={{ marginBottom: 16 }}>
|
||||
<Paragraph>
|
||||
The following databases are compatible with the {name} driver:
|
||||
</Paragraph>
|
||||
<Collapse defaultActiveKey={allPanelKeys}>
|
||||
{docs.compatible_databases.map((compat, idx) => (
|
||||
<Panel
|
||||
header={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
{compat.logo && (
|
||||
<img
|
||||
src={`/img/databases/${compat.logo}`}
|
||||
alt={compat.name}
|
||||
style={{
|
||||
width: 28,
|
||||
height: 28,
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span>{compat.name}</span>
|
||||
</div>
|
||||
}
|
||||
key={idx}
|
||||
>
|
||||
{compat.description && (
|
||||
<Paragraph>{compat.description}</Paragraph>
|
||||
)}
|
||||
{compat.connection_string &&
|
||||
renderConnectionString(compat.connection_string)}
|
||||
{compat.parameters && (
|
||||
<div>
|
||||
<Text strong>Parameters:</Text>
|
||||
<Table
|
||||
dataSource={Object.entries(compat.parameters).map(
|
||||
([key, value]) => ({
|
||||
key,
|
||||
parameter: key,
|
||||
description: value,
|
||||
})
|
||||
)}
|
||||
columns={[
|
||||
{ title: 'Parameter', dataIndex: 'parameter', key: 'p' },
|
||||
{
|
||||
title: 'Description',
|
||||
dataIndex: 'description',
|
||||
key: 'd',
|
||||
},
|
||||
]}
|
||||
pagination={false}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{compat.notes && (
|
||||
<Alert
|
||||
message={compat.notes}
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
)}
|
||||
</Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<Card title="Supported Features" style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||
{features.map(({ key, label }) => (
|
||||
<FeatureBadge
|
||||
key={key}
|
||||
supported={Boolean(database[key])}
|
||||
label={label}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{database.score > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Text>
|
||||
Feature Score:{' '}
|
||||
<Text strong>
|
||||
{database.score}/{database.max_score}
|
||||
</Text>
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<Card title="Time Grains" style={{ marginBottom: 16 }}>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Text strong>Common Time Grains:</Text>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
{commonGrains.map((grain) => (
|
||||
<TimeGrainBadge
|
||||
key={grain}
|
||||
grain={grain}
|
||||
supported={Boolean(
|
||||
database.time_grains[grain as keyof typeof database.time_grains]
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{extendedGrains.length > 0 && (
|
||||
<div>
|
||||
<Text strong>Extended Time Grains:</Text>
|
||||
<div style={{ marginTop: 8 }}>
|
||||
{extendedGrains.map((grain) => (
|
||||
<TimeGrainBadge
|
||||
key={grain}
|
||||
grain={grain}
|
||||
supported={Boolean(
|
||||
database.time_grains[grain as keyof typeof database.time_grains]
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="database-page"
|
||||
id={name.toLowerCase().replace(/\s+/g, '-')}
|
||||
>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
{docs?.logo && (
|
||||
<img
|
||||
src={`/img/databases/${docs.logo}`}
|
||||
alt={name}
|
||||
style={{
|
||||
height: 120,
|
||||
objectFit: 'contain',
|
||||
marginBottom: 12,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Title level={1} style={{ margin: 0 }}>{name}</Title>
|
||||
{docs?.homepage_url && (
|
||||
<a
|
||||
href={docs.homepage_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ fontSize: 14 }}
|
||||
>
|
||||
<LinkOutlined /> {docs.homepage_url}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{docs?.description && <Paragraph>{docs.description}</Paragraph>}
|
||||
|
||||
{/* Warnings */}
|
||||
{docs?.warnings?.map((warning, idx) => (
|
||||
<Alert
|
||||
key={idx}
|
||||
message={warning}
|
||||
type="warning"
|
||||
icon={<WarningOutlined />}
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Known Limitations */}
|
||||
{docs?.limitations?.length > 0 && (
|
||||
<Card
|
||||
title="Known Limitations"
|
||||
style={{ marginBottom: 16 }}
|
||||
type="inner"
|
||||
>
|
||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||
{docs.limitations.map((limitation, idx) => (
|
||||
<li key={idx}>{limitation}</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Installation */}
|
||||
{(docs?.pypi_packages?.length || docs?.install_instructions) && (
|
||||
<Card title="Installation" style={{ marginBottom: 16 }}>
|
||||
{docs.pypi_packages?.length > 0 && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Text strong>Required packages: </Text>
|
||||
{docs.pypi_packages.map((pkg) => (
|
||||
<Tag key={pkg} color="blue">
|
||||
{pkg}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{docs.version_requirements && (
|
||||
<Alert
|
||||
message={`Version requirement: ${docs.version_requirements}`}
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
{docs.install_instructions && (
|
||||
<CodeBlock>{docs.install_instructions}</CodeBlock>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Basic Connection */}
|
||||
{docs?.connection_string && !docs?.drivers?.length && (
|
||||
<Card title="Connection String" style={{ marginBottom: 16 }}>
|
||||
{renderConnectionString(docs.connection_string)}
|
||||
{docs.parameters && (
|
||||
<Table
|
||||
dataSource={Object.entries(docs.parameters).map(
|
||||
([key, value]) => ({
|
||||
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 && (
|
||||
<Text type="secondary">Default port: {docs.default_port}</Text>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Drivers */}
|
||||
{renderDrivers()}
|
||||
|
||||
{/* Connection Examples */}
|
||||
{docs?.connection_examples?.length > 0 && (
|
||||
<Card title="Connection Examples" style={{ marginBottom: 16 }}>
|
||||
{docs.connection_examples.map((example, idx) => (
|
||||
<div key={idx}>
|
||||
{renderConnectionString(
|
||||
example.connection_string,
|
||||
example.description
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Authentication Methods */}
|
||||
{renderAuthMethods()}
|
||||
|
||||
{/* Engine Parameters */}
|
||||
{renderEngineParams()}
|
||||
|
||||
{/* Features */}
|
||||
{renderFeatures()}
|
||||
|
||||
{/* Time Grains */}
|
||||
{renderTimeGrains()}
|
||||
|
||||
{/* Compatible Databases */}
|
||||
{renderCompatibleDatabases()}
|
||||
|
||||
{/* Notes */}
|
||||
{docs?.notes && (
|
||||
<Alert
|
||||
message="Notes"
|
||||
description={docs.notes}
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* External Links */}
|
||||
{(docs?.docs_url || docs?.tutorials?.length) && (
|
||||
<Card
|
||||
title={
|
||||
<>
|
||||
<BookOutlined /> Resources
|
||||
</>
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<Space direction="vertical">
|
||||
{docs.docs_url && (
|
||||
<a href={docs.docs_url} target="_blank" rel="noreferrer">
|
||||
<LinkOutlined /> Official Documentation
|
||||
</a>
|
||||
)}
|
||||
{docs.sqlalchemy_docs_url && (
|
||||
<a href={docs.sqlalchemy_docs_url} target="_blank" rel="noreferrer">
|
||||
<LinkOutlined /> SQLAlchemy Dialect Documentation
|
||||
</a>
|
||||
)}
|
||||
{docs.tutorials?.map((tutorial, idx) => (
|
||||
<a key={idx} href={tutorial} target="_blank" rel="noreferrer">
|
||||
<LinkOutlined /> Tutorial {idx + 1}
|
||||
</a>
|
||||
))}
|
||||
</Space>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Edit link */}
|
||||
{database.module && (
|
||||
<Card
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
background: 'var(--ifm-background-surface-color)',
|
||||
borderStyle: 'dashed',
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<Space>
|
||||
<GithubOutlined />
|
||||
<Text type="secondary">
|
||||
Help improve this documentation by editing the engine spec:
|
||||
</Text>
|
||||
<a
|
||||
href={`https://github.com/apache/superset/edit/master/superset/db_engine_specs/${database.module}.py`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<EditOutlined /> Edit {database.module}.py
|
||||
</a>
|
||||
</Space>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatabasePage;
|
||||
22
docs/src/components/databases/index.ts
Normal file
22
docs/src/components/databases/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { default as DatabaseIndex } from './DatabaseIndex';
|
||||
export { default as DatabasePage } from './DatabasePage';
|
||||
export * from './types';
|
||||
243
docs/src/components/databases/types.ts
Normal file
243
docs/src/components/databases/types.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* TypeScript types for database documentation data
|
||||
* Generated from superset/db_engine_specs/lib.py
|
||||
*/
|
||||
|
||||
export interface Driver {
|
||||
name: string;
|
||||
pypi_package?: string;
|
||||
connection_string?: string;
|
||||
is_recommended?: boolean;
|
||||
notes?: string;
|
||||
docs_url?: string;
|
||||
default_port?: number;
|
||||
odbc_driver_paths?: Record<string, string>;
|
||||
environment_variables?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ConnectionExample {
|
||||
description: string;
|
||||
connection_string: string;
|
||||
}
|
||||
|
||||
export interface HostExample {
|
||||
platform: string;
|
||||
host: string;
|
||||
}
|
||||
|
||||
export interface AuthenticationMethod {
|
||||
name: string;
|
||||
description?: string;
|
||||
requirements?: string;
|
||||
connection_string?: string;
|
||||
secure_extra?: Record<string, unknown>;
|
||||
secure_extra_body?: Record<string, unknown>;
|
||||
secure_extra_path?: Record<string, unknown>;
|
||||
engine_parameters?: Record<string, unknown>;
|
||||
config_example?: Record<string, unknown>;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface EngineParameter {
|
||||
name: string;
|
||||
description?: string;
|
||||
json?: Record<string, unknown>;
|
||||
secure_extra?: Record<string, unknown>;
|
||||
docs_url?: string;
|
||||
}
|
||||
|
||||
export interface SSLConfiguration {
|
||||
custom_certificate?: string;
|
||||
disable_ssl_verification?: {
|
||||
engine_params?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CompatibleDatabase {
|
||||
name: string;
|
||||
description?: string;
|
||||
logo?: string;
|
||||
homepage_url?: string;
|
||||
categories?: string[]; // Category classifications (e.g., ["TRADITIONAL_RDBMS", "OPEN_SOURCE"])
|
||||
pypi_packages?: string[];
|
||||
connection_string?: string;
|
||||
parameters?: Record<string, string>;
|
||||
connection_examples?: ConnectionExample[];
|
||||
notes?: string;
|
||||
docs_url?: string;
|
||||
}
|
||||
|
||||
export interface DatabaseDocumentation {
|
||||
description?: string;
|
||||
logo?: string;
|
||||
homepage_url?: string;
|
||||
categories?: string[]; // Category classifications (e.g., ["TRADITIONAL_RDBMS", "OPEN_SOURCE"])
|
||||
pypi_packages?: string[];
|
||||
connection_string?: string;
|
||||
default_port?: number;
|
||||
parameters?: Record<string, string>;
|
||||
notes?: string;
|
||||
limitations?: string[]; // Known limitations or caveats
|
||||
connection_examples?: ConnectionExample[];
|
||||
host_examples?: HostExample[];
|
||||
drivers?: Driver[];
|
||||
authentication_methods?: AuthenticationMethod[];
|
||||
engine_parameters?: EngineParameter[];
|
||||
ssl_configuration?: SSLConfiguration;
|
||||
version_requirements?: string;
|
||||
install_instructions?: string;
|
||||
warnings?: string[];
|
||||
tutorials?: string[];
|
||||
docs_url?: string;
|
||||
sqlalchemy_docs_url?: string;
|
||||
advanced_features?: Record<string, string>;
|
||||
compatible_databases?: CompatibleDatabase[];
|
||||
}
|
||||
|
||||
export interface TimeGrains {
|
||||
SECOND?: boolean;
|
||||
MINUTE?: boolean;
|
||||
HOUR?: boolean;
|
||||
DAY?: boolean;
|
||||
WEEK?: boolean;
|
||||
MONTH?: boolean;
|
||||
QUARTER?: boolean;
|
||||
YEAR?: boolean;
|
||||
FIVE_SECONDS?: boolean;
|
||||
THIRTY_SECONDS?: boolean;
|
||||
FIVE_MINUTES?: boolean;
|
||||
TEN_MINUTES?: boolean;
|
||||
FIFTEEN_MINUTES?: boolean;
|
||||
THIRTY_MINUTES?: boolean;
|
||||
HALF_HOUR?: boolean;
|
||||
SIX_HOURS?: boolean;
|
||||
WEEK_STARTING_SUNDAY?: boolean;
|
||||
WEEK_STARTING_MONDAY?: boolean;
|
||||
WEEK_ENDING_SATURDAY?: boolean;
|
||||
WEEK_ENDING_SUNDAY?: boolean;
|
||||
QUARTER_YEAR?: boolean;
|
||||
}
|
||||
|
||||
export interface DatabaseInfo {
|
||||
engine: string;
|
||||
engine_name: string;
|
||||
engine_aliases?: string[];
|
||||
default_driver?: string;
|
||||
module?: string;
|
||||
documentation: DatabaseDocumentation;
|
||||
|
||||
// Diagnostics from lib.py diagnose() function
|
||||
time_grains: TimeGrains;
|
||||
score: number;
|
||||
max_score: number;
|
||||
|
||||
// SQL capabilities
|
||||
joins: boolean;
|
||||
subqueries: boolean;
|
||||
alias_in_select?: boolean;
|
||||
alias_in_orderby?: boolean;
|
||||
cte_in_subquery?: boolean;
|
||||
sql_comments?: boolean;
|
||||
escaped_colons?: boolean;
|
||||
time_groupby_inline?: boolean;
|
||||
alias_to_source_column?: boolean;
|
||||
order_by_not_in_select?: boolean;
|
||||
expressions_in_orderby?: boolean;
|
||||
|
||||
// Platform features
|
||||
limit_method?: string;
|
||||
limit_clause?: boolean;
|
||||
max_column_name?: number;
|
||||
supports_file_upload?: boolean;
|
||||
supports_dynamic_schema?: boolean;
|
||||
supports_catalog?: boolean;
|
||||
supports_dynamic_catalog?: boolean;
|
||||
|
||||
// Advanced features
|
||||
user_impersonation?: boolean;
|
||||
ssh_tunneling?: boolean;
|
||||
query_cancelation?: boolean;
|
||||
expand_data?: boolean;
|
||||
query_cost_estimation?: boolean;
|
||||
sql_validation?: boolean;
|
||||
get_metrics?: boolean;
|
||||
where_latest_partition?: boolean;
|
||||
get_extra_table_metadata?: boolean;
|
||||
dbapi_exception_mapping?: boolean;
|
||||
custom_errors?: boolean;
|
||||
masked_encrypted_extra?: boolean;
|
||||
column_type_mapping?: boolean;
|
||||
function_names?: boolean;
|
||||
}
|
||||
|
||||
export interface Statistics {
|
||||
totalDatabases: number;
|
||||
withDocumentation: number;
|
||||
withConnectionString: number;
|
||||
withDrivers: number;
|
||||
withAuthMethods: number;
|
||||
supportsJoins: number;
|
||||
supportsSubqueries: number;
|
||||
supportsDynamicSchema: number;
|
||||
supportsCatalog: number;
|
||||
averageScore: number;
|
||||
maxScore: number;
|
||||
byCategory: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export interface DatabaseData {
|
||||
generated: string;
|
||||
statistics: Statistics;
|
||||
databases: Record<string, DatabaseInfo>;
|
||||
}
|
||||
|
||||
// Helper type for sorting databases
|
||||
export type SortField = 'name' | 'score' | 'category';
|
||||
export type SortDirection = 'asc' | 'desc';
|
||||
|
||||
// Helper to get common time grains
|
||||
export const COMMON_TIME_GRAINS = [
|
||||
'SECOND',
|
||||
'MINUTE',
|
||||
'HOUR',
|
||||
'DAY',
|
||||
'WEEK',
|
||||
'MONTH',
|
||||
'QUARTER',
|
||||
'YEAR',
|
||||
] as const;
|
||||
|
||||
export const EXTENDED_TIME_GRAINS = [
|
||||
'FIVE_SECONDS',
|
||||
'THIRTY_SECONDS',
|
||||
'FIVE_MINUTES',
|
||||
'TEN_MINUTES',
|
||||
'FIFTEEN_MINUTES',
|
||||
'THIRTY_MINUTES',
|
||||
'HALF_HOUR',
|
||||
'SIX_HOURS',
|
||||
'WEEK_STARTING_SUNDAY',
|
||||
'WEEK_STARTING_MONDAY',
|
||||
'WEEK_ENDING_SATURDAY',
|
||||
'WEEK_ENDING_SUNDAY',
|
||||
'QUARTER_YEAR',
|
||||
] as const;
|
||||
Reference in New Issue
Block a user