mirror of
https://github.com/apache/superset.git
synced 2026-09-01 21:11:28 +00:00
861 lines
26 KiB
TypeScript
861 lines
26 KiB
TypeScript
/**
|
|
* 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, KnownIncompatibility } 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;
|
|
|
|
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>
|
|
);
|
|
|
|
// 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 (
|
|
<Card title="Drivers" style={{ marginBottom: 16 }}>
|
|
<Tabs
|
|
items={docs.drivers.map((driver, idx) => ({
|
|
key: String(idx),
|
|
label: (
|
|
<span>
|
|
{driver.name}
|
|
{driver.is_recommended && (
|
|
<Tag color="green" style={{ marginLeft: 8 }}>
|
|
Recommended
|
|
</Tag>
|
|
)}
|
|
</span>
|
|
),
|
|
children: (
|
|
<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>
|
|
),
|
|
}))}
|
|
/>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
// Render authentication methods
|
|
const renderAuthMethods = () => {
|
|
if (!docs?.authentication_methods?.length) return null;
|
|
|
|
return (
|
|
<Card
|
|
title={
|
|
<>
|
|
<KeyOutlined /> Authentication Methods
|
|
</>
|
|
}
|
|
style={{ marginBottom: 16 }}
|
|
>
|
|
<Collapse
|
|
accordion
|
|
items={docs.authentication_methods.map((auth, idx) => ({
|
|
key: String(idx),
|
|
label: auth.name,
|
|
children: (
|
|
<>
|
|
{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 />
|
|
)}
|
|
</>
|
|
),
|
|
}))}
|
|
/>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
// Render engine parameters
|
|
const renderEngineParams = () => {
|
|
if (!docs?.engine_parameters?.length) return null;
|
|
|
|
return (
|
|
<Card
|
|
title={
|
|
<>
|
|
<SettingOutlined /> Engine Parameters
|
|
</>
|
|
}
|
|
style={{ marginBottom: 16 }}
|
|
>
|
|
<Collapse
|
|
items={docs.engine_parameters.map((param, idx) => ({
|
|
key: String(idx),
|
|
label: param.name,
|
|
children: (
|
|
<>
|
|
{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>
|
|
)}
|
|
</>
|
|
),
|
|
}))}
|
|
/>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
// Render known incompatibilities with a Superset dependency (e.g. a driver
|
|
// that doesn't yet support SQLAlchemy 2.0). Shared between the top-level
|
|
// documentation and each compatible-database entry.
|
|
const renderKnownIncompatibilities = (
|
|
incompatibilities?: KnownIncompatibility[],
|
|
) => {
|
|
if (!incompatibilities?.length) return null;
|
|
|
|
return (
|
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|
{incompatibilities.map((incompat, idx) => (
|
|
<Alert
|
|
key={idx}
|
|
type="warning"
|
|
showIcon
|
|
message={incompat.dependency}
|
|
description={
|
|
<>
|
|
{incompat.reason && (
|
|
<Paragraph style={{ marginBottom: 4 }}>
|
|
{incompat.reason}
|
|
</Paragraph>
|
|
)}
|
|
<Space size="middle">
|
|
{incompat.tracking_url && (
|
|
<a
|
|
href={incompat.tracking_url}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
<LinkOutlined /> Tracking issue
|
|
</a>
|
|
)}
|
|
{incompat.since && (
|
|
<Text type="secondary">
|
|
Last confirmed: {incompat.since}
|
|
</Text>
|
|
)}
|
|
</Space>
|
|
</>
|
|
}
|
|
/>
|
|
))}
|
|
</Space>
|
|
);
|
|
};
|
|
|
|
// 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 (
|
|
<Card title="Compatible Databases" style={{ marginBottom: 16 }}>
|
|
<Paragraph>
|
|
The following databases are compatible with the {name} driver:
|
|
</Paragraph>
|
|
<Collapse
|
|
defaultActiveKey={allItemKeys}
|
|
items={docs.compatible_databases.map((compat, idx) => ({
|
|
key: String(idx),
|
|
label: (
|
|
<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>
|
|
),
|
|
children: (
|
|
<>
|
|
{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.known_incompatibilities?.length > 0 && (
|
|
<div style={{ marginTop: 16 }}>
|
|
<Text strong>Known Incompatibilities:</Text>
|
|
<div style={{ marginTop: 8 }}>
|
|
{renderKnownIncompatibilities(
|
|
compat.known_incompatibilities,
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{compat.notes && (
|
|
<Alert
|
|
message={compat.notes}
|
|
type="info"
|
|
showIcon
|
|
style={{ marginTop: 16 }}
|
|
/>
|
|
)}
|
|
</>
|
|
),
|
|
}))}
|
|
/>
|
|
</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>
|
|
);
|
|
};
|
|
|
|
// Render troubleshooting / custom errors section
|
|
const renderTroubleshooting = () => {
|
|
if (!docs?.custom_errors?.length) return null;
|
|
|
|
// Group errors by category
|
|
const errorsByCategory: Record<string, typeof docs.custom_errors> = {};
|
|
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<string, string> = {
|
|
Authentication: 'orange',
|
|
Connection: 'red',
|
|
Permissions: 'purple',
|
|
Query: 'blue',
|
|
Configuration: 'cyan',
|
|
General: 'default',
|
|
};
|
|
|
|
return (
|
|
<Card
|
|
title={
|
|
<>
|
|
<BugOutlined /> Troubleshooting
|
|
</>
|
|
}
|
|
style={{ marginBottom: 16 }}
|
|
>
|
|
<Paragraph type="secondary">
|
|
Common error messages you may encounter when connecting to or querying{' '}
|
|
{name}, along with their causes and solutions.
|
|
</Paragraph>
|
|
<Collapse
|
|
accordion
|
|
items={sortedCategories.map(category => ({
|
|
key: category,
|
|
label: (
|
|
<span>
|
|
<Tag color={categoryColors[category] || 'default'}>
|
|
{category}
|
|
</Tag>
|
|
{errorsByCategory[category].length} error
|
|
{errorsByCategory[category].length !== 1 ? 's' : ''}
|
|
</span>
|
|
),
|
|
children: (
|
|
<>
|
|
{errorsByCategory[category].map((error, idx) => (
|
|
<div
|
|
key={idx}
|
|
style={{
|
|
marginBottom:
|
|
idx < errorsByCategory[category].length - 1 ? 16 : 0,
|
|
paddingBottom:
|
|
idx < errorsByCategory[category].length - 1 ? 16 : 0,
|
|
borderBottom:
|
|
idx < errorsByCategory[category].length - 1
|
|
? '1px solid var(--ifm-color-emphasis-200)'
|
|
: 'none',
|
|
}}
|
|
>
|
|
<div style={{ marginBottom: 8 }}>
|
|
<Text strong>
|
|
{error.description || error.error_type}
|
|
</Text>
|
|
</div>
|
|
<Alert
|
|
message={error.message_template}
|
|
type="error"
|
|
style={{ marginBottom: 8 }}
|
|
/>
|
|
{error.invalid_fields &&
|
|
error.invalid_fields.length > 0 && (
|
|
<div style={{ marginBottom: 8 }}>
|
|
<Text type="secondary">Check these fields: </Text>
|
|
{error.invalid_fields.map(field => (
|
|
<Tag key={field} color="warning">
|
|
{field}
|
|
</Tag>
|
|
))}
|
|
</div>
|
|
)}
|
|
{error.issue_codes && error.issue_codes.length > 0 && (
|
|
<div>
|
|
<Text type="secondary">Related issue codes: </Text>
|
|
{error.issue_codes.map(code => (
|
|
<Tag key={code}>
|
|
<a
|
|
href={`/docs/using-superset/issue-codes#issue-${code}`}
|
|
style={{ color: 'inherit' }}
|
|
>
|
|
Issue {code}
|
|
</a>
|
|
</Tag>
|
|
))}
|
|
</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>
|
|
)}
|
|
|
|
{/* Known Incompatibilities */}
|
|
{docs?.known_incompatibilities?.length > 0 && (
|
|
<Card
|
|
title="Known Incompatibilities"
|
|
style={{ marginBottom: 16 }}
|
|
type="inner"
|
|
>
|
|
{renderKnownIncompatibilities(docs.known_incompatibilities)}
|
|
</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()}
|
|
|
|
{/* Troubleshooting / Custom Errors */}
|
|
{renderTroubleshooting()}
|
|
|
|
{/* 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/${databaseModuleFilename}`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
<EditOutlined /> Edit {databaseModuleFilename}
|
|
</a>
|
|
</Space>
|
|
</Card>
|
|
)}
|
|
|
|
<Divider />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DatabasePage;
|