mirror of
https://github.com/apache/superset.git
synced 2026-09-09 00:34:49 +00:00
feat(build): migrate from Prettier to Oxfmt for performant code formatting (#42434)
This commit is contained in:
@@ -39,7 +39,9 @@ interface FAQSchemaProps {
|
||||
* { question: "How do I install it?", answer: "You can install via..." }
|
||||
* ]} />
|
||||
*/
|
||||
export default function FAQSchema({ faqs }: FAQSchemaProps): JSX.Element | null {
|
||||
export default function FAQSchema({
|
||||
faqs,
|
||||
}: FAQSchemaProps): JSX.Element | null {
|
||||
// FAQPage schema requires a non-empty mainEntity array per schema.org specs
|
||||
if (!faqs || faqs.length === 0) {
|
||||
return null;
|
||||
@@ -48,7 +50,7 @@ export default function FAQSchema({ faqs }: FAQSchemaProps): JSX.Element | null
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: faqs.map((faq) => ({
|
||||
mainEntity: faqs.map(faq => ({
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: {
|
||||
|
||||
@@ -64,9 +64,7 @@ const Root = styled.div<{ $variant: 'hero' | 'navbar' }>`
|
||||
text-decoration: none;
|
||||
min-width: 0;
|
||||
${({ $variant }) =>
|
||||
$variant === 'hero'
|
||||
? `padding: 10px 10px;`
|
||||
: `padding: 7px 8px;`}
|
||||
$variant === 'hero' ? `padding: 10px 10px;` : `padding: 7px 8px;`}
|
||||
}
|
||||
|
||||
.split-main:hover {
|
||||
@@ -79,9 +77,7 @@ const Root = styled.div<{ $variant: 'hero' | 'navbar' }>`
|
||||
align-self: stretch;
|
||||
background: rgba(255, 255, 255, 0.38);
|
||||
${({ $variant }) =>
|
||||
$variant === 'hero'
|
||||
? `margin: 8px 0;`
|
||||
: `margin: 6px 0;`}
|
||||
$variant === 'hero' ? `margin: 8px 0;` : `margin: 6px 0;`}
|
||||
}
|
||||
|
||||
.split-dropdown-trigger {
|
||||
|
||||
@@ -72,7 +72,7 @@ function getProviders() {
|
||||
// Configure Ant Design to render portals (tooltips, dropdowns, etc.)
|
||||
// inside the closest .storybook-example container instead of document.body
|
||||
// This fixes positioning issues in the docs pages
|
||||
const getPopupContainer = (triggerNode) => {
|
||||
const getPopupContainer = triggerNode => {
|
||||
// Find the closest .storybook-example container
|
||||
const container = triggerNode?.closest?.('.storybook-example');
|
||||
return container || document.body;
|
||||
@@ -190,7 +190,11 @@ const CHILDREN_PROP_NAMES = ['label', 'children', 'text', 'content'];
|
||||
// Extract children from props based on common conventions
|
||||
function extractChildren(props) {
|
||||
for (const propName of CHILDREN_PROP_NAMES) {
|
||||
if (props[propName] !== undefined && props[propName] !== null && props[propName] !== '') {
|
||||
if (
|
||||
props[propName] !== undefined &&
|
||||
props[propName] !== null &&
|
||||
props[propName] !== ''
|
||||
) {
|
||||
const { [propName]: childContent, ...restProps } = props;
|
||||
return { children: childContent, restProps };
|
||||
}
|
||||
@@ -220,7 +224,11 @@ function generateSampleChildren(sampleChildren, sampleChildrenStyle) {
|
||||
return <ChildComponent key={i} {...item.props} />;
|
||||
}
|
||||
// Fallback if component not found
|
||||
return <div key={i}>{item.props?.children || `Unknown: ${item.component}`}</div>;
|
||||
return (
|
||||
<div key={i}>
|
||||
{item.props?.children || `Unknown: ${item.component}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Simple string
|
||||
return (
|
||||
@@ -252,7 +260,16 @@ function generateSampleChildren(sampleChildren, sampleChildrenStyle) {
|
||||
// renderComponent allows overriding which component to actually render (useful when the named
|
||||
// component is a namespace object like Icons, not a React component)
|
||||
// triggerProp: for components like Modal that need a trigger, specify the boolean prop that controls visibility
|
||||
function StoryWithControlsInner({ component, renderComponent, props, controls, sampleChildren, sampleChildrenStyle, triggerProp, onHideProp }) {
|
||||
function StoryWithControlsInner({
|
||||
component,
|
||||
renderComponent,
|
||||
props,
|
||||
controls,
|
||||
sampleChildren,
|
||||
sampleChildrenStyle,
|
||||
triggerProp,
|
||||
onHideProp,
|
||||
}) {
|
||||
// Use renderComponent if provided, otherwise use the main component name
|
||||
const componentToRender = renderComponent || component;
|
||||
const Component = resolveComponent(componentToRender);
|
||||
@@ -274,7 +291,7 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
: extractChildren(stateProps);
|
||||
// Filter out undefined values so they don't override component defaults
|
||||
const filteredProps = Object.fromEntries(
|
||||
Object.entries(restProps).filter(([, v]) => v !== undefined)
|
||||
Object.entries(restProps).filter(([, v]) => v !== undefined),
|
||||
);
|
||||
|
||||
// Resolve any prop values that are component descriptors
|
||||
@@ -283,7 +300,12 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
// e.g., items: [{ id: 'x', element: { component: 'div', props: { children: 'text' } } }]
|
||||
Object.keys(filteredProps).forEach(key => {
|
||||
const value = filteredProps[key];
|
||||
if (value && typeof value === 'object' && !Array.isArray(value) && value.component) {
|
||||
if (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
!Array.isArray(value) &&
|
||||
value.component
|
||||
) {
|
||||
const PropComponent = resolveComponent(value.component);
|
||||
if (PropComponent) {
|
||||
filteredProps[key] = <PropComponent {...value.props} />;
|
||||
@@ -295,10 +317,18 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
const resolved = { ...item };
|
||||
Object.keys(resolved).forEach(field => {
|
||||
const fieldValue = resolved[field];
|
||||
if (fieldValue && typeof fieldValue === 'object' && !Array.isArray(fieldValue) && fieldValue.component) {
|
||||
if (
|
||||
fieldValue &&
|
||||
typeof fieldValue === 'object' &&
|
||||
!Array.isArray(fieldValue) &&
|
||||
fieldValue.component
|
||||
) {
|
||||
const FieldComponent = resolveComponent(fieldValue.component);
|
||||
if (FieldComponent) {
|
||||
resolved[field] = React.createElement(FieldComponent, { key: `${key}-${idx}`, ...fieldValue.props });
|
||||
resolved[field] = React.createElement(FieldComponent, {
|
||||
key: `${key}-${idx}`,
|
||||
...fieldValue.props,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -312,14 +342,16 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
// For List-like components with dataSource but no renderItem, provide a default
|
||||
if (filteredProps.dataSource && !filteredProps.renderItem) {
|
||||
const ListItem = resolveComponent('List')?.Item;
|
||||
filteredProps.renderItem = (item) =>
|
||||
filteredProps.renderItem = item =>
|
||||
ListItem
|
||||
? React.createElement(ListItem, null, String(item))
|
||||
: React.createElement('div', null, String(item));
|
||||
}
|
||||
|
||||
// Use sample children if provided, otherwise use props children
|
||||
const children = generateSampleChildren(sampleChildren, sampleChildrenStyle) || propsChildren;
|
||||
const children =
|
||||
generateSampleChildren(sampleChildren, sampleChildrenStyle) ||
|
||||
propsChildren;
|
||||
|
||||
// For components with a trigger (like Modal with show/onHide), add handlers.
|
||||
// onHideProp supports comma-separated names for components with multiple close
|
||||
@@ -356,7 +388,9 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
Open {component}
|
||||
</ButtonComponent>
|
||||
)}
|
||||
<Component {...filteredProps} {...triggerProps}>{children}</Component>
|
||||
<Component {...filteredProps} {...triggerProps}>
|
||||
{children}
|
||||
</Component>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ color: '#999' }}>
|
||||
@@ -384,7 +418,9 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
{control.type === 'select' ? (
|
||||
<select
|
||||
value={stateProps[control.name] ?? ''}
|
||||
onChange={e => updateProp(control.name, e.target.value || undefined)}
|
||||
onChange={e =>
|
||||
updateProp(control.name, e.target.value || undefined)
|
||||
}
|
||||
style={{ width: '100%', padding: '5px' }}
|
||||
>
|
||||
<option value="">— None —</option>
|
||||
@@ -394,19 +430,28 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : control.type === 'inline-radio' || control.type === 'radio' ? (
|
||||
<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
|
||||
) : control.type === 'inline-radio' ||
|
||||
control.type === 'radio' ? (
|
||||
<div
|
||||
style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}
|
||||
>
|
||||
{control.options?.map(option => (
|
||||
<label
|
||||
key={option}
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '4px' }}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={control.name}
|
||||
value={option}
|
||||
checked={stateProps[control.name] === option}
|
||||
onChange={e => updateProp(control.name, e.target.value)}
|
||||
onChange={e =>
|
||||
updateProp(control.name, e.target.value)
|
||||
}
|
||||
/>
|
||||
{option}
|
||||
</label>
|
||||
@@ -422,7 +467,9 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
<input
|
||||
type="number"
|
||||
value={stateProps[control.name]}
|
||||
onChange={e => updateProp(control.name, Number(e.target.value))}
|
||||
onChange={e =>
|
||||
updateProp(control.name, Number(e.target.value))
|
||||
}
|
||||
style={{ width: '100%', padding: '5px' }}
|
||||
/>
|
||||
) : control.type === 'color' ? (
|
||||
@@ -457,7 +504,16 @@ function StoryWithControlsInner({ component, renderComponent, props, controls, s
|
||||
// A simple component to display a story with controls
|
||||
// renderComponent: optional override for which component to render (e.g., 'Icons.InfoCircleOutlined' when component='Icons')
|
||||
// triggerProp/onHideProp: for components like Modal that need a button to open (e.g., triggerProp="show", onHideProp="onHide")
|
||||
export function StoryWithControls({ component: Component, renderComponent, props = {}, controls = [], sampleChildren, sampleChildrenStyle, triggerProp, onHideProp }) {
|
||||
export function StoryWithControls({
|
||||
component: Component,
|
||||
renderComponent,
|
||||
props = {},
|
||||
controls = [],
|
||||
sampleChildren,
|
||||
sampleChildrenStyle,
|
||||
triggerProp,
|
||||
onHideProp,
|
||||
}) {
|
||||
return (
|
||||
<BrowserOnly fallback={<LoadingPlaceholder />}>
|
||||
{() => (
|
||||
@@ -477,7 +533,13 @@ export function StoryWithControls({ component: Component, renderComponent, props
|
||||
}
|
||||
|
||||
// Inner component for ComponentGallery (browser-only)
|
||||
function ComponentGalleryInner({ component, sizes, styles, sizeProp, styleProp }) {
|
||||
function ComponentGalleryInner({
|
||||
component,
|
||||
sizes,
|
||||
styles,
|
||||
sizeProp,
|
||||
styleProp,
|
||||
}) {
|
||||
const Component = resolveComponent(component);
|
||||
const Providers = getProviders();
|
||||
|
||||
@@ -495,7 +557,14 @@ function ComponentGalleryInner({ component, sizes, styles, sizeProp, styleProp }
|
||||
{sizes.map(size => (
|
||||
<div key={size} style={{ marginBottom: 40 }}>
|
||||
<h4 style={{ marginBottom: 16, color: '#666' }}>{size}</h4>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{styles.map(style => (
|
||||
<Component
|
||||
key={`${style}_${size}`}
|
||||
@@ -513,7 +582,13 @@ function ComponentGalleryInner({ component, sizes, styles, sizeProp, styleProp }
|
||||
}
|
||||
|
||||
// A component to display a gallery of all variants (sizes x styles)
|
||||
export function ComponentGallery({ component, sizes = [], styles = [], sizeProp = 'size', styleProp = 'variant' }) {
|
||||
export function ComponentGallery({
|
||||
component,
|
||||
sizes = [],
|
||||
styles = [],
|
||||
sizeProp = 'size',
|
||||
styleProp = 'variant',
|
||||
}) {
|
||||
return (
|
||||
<BrowserOnly fallback={<LoadingPlaceholder />}>
|
||||
{() => (
|
||||
|
||||
@@ -18,7 +18,17 @@
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { Card, Row, Col, Statistic, Table, Tag, Input, Select, Tooltip } from 'antd';
|
||||
import {
|
||||
Card,
|
||||
Row,
|
||||
Col,
|
||||
Statistic,
|
||||
Table,
|
||||
Tag,
|
||||
Input,
|
||||
Select,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import {
|
||||
DatabaseOutlined,
|
||||
CheckCircleOutlined,
|
||||
@@ -37,7 +47,7 @@ interface DatabaseIndexProps {
|
||||
// Type for table entries (includes both regular DBs and compatible DBs)
|
||||
interface TableEntry {
|
||||
name: string;
|
||||
categories: string[]; // Multiple categories supported
|
||||
categories: string[]; // Multiple categories supported
|
||||
score: number;
|
||||
max_score: number;
|
||||
timeGrainCount: number;
|
||||
@@ -66,20 +76,20 @@ interface TableEntry {
|
||||
|
||||
// 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',
|
||||
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
|
||||
@@ -98,7 +108,7 @@ const CATEGORY_COLORS: Record<string, string> = {
|
||||
// Licensing categories
|
||||
'Open Source': 'geekblue',
|
||||
'Hosted Open Source': 'cyan',
|
||||
'Proprietary': 'default',
|
||||
Proprietary: 'default',
|
||||
};
|
||||
|
||||
// Convert category constant to display name
|
||||
@@ -110,7 +120,7 @@ function getCategoryDisplayName(cat: string): string {
|
||||
// Falls back to name-based inference for compatible databases without categories
|
||||
function getCategories(
|
||||
name: string,
|
||||
documentationCategories?: string[]
|
||||
documentationCategories?: string[],
|
||||
): string[] {
|
||||
// Prefer categories from documentation metadata (computed by Python)
|
||||
if (documentationCategories && documentationCategories.length > 0) {
|
||||
@@ -221,10 +231,11 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
categories: getCategories(name, db.documentation?.categories),
|
||||
timeGrainCount: countTimeGrains(db),
|
||||
hasDrivers: (db.documentation?.drivers?.length ?? 0) > 0,
|
||||
hasAuthMethods: (db.documentation?.authentication_methods?.length ?? 0) > 0,
|
||||
hasAuthMethods:
|
||||
(db.documentation?.authentication_methods?.length ?? 0) > 0,
|
||||
hasConnectionString: Boolean(
|
||||
db.documentation?.connection_string ||
|
||||
(db.documentation?.drivers?.length ?? 0) > 0
|
||||
(db.documentation?.drivers?.length ?? 0) > 0,
|
||||
),
|
||||
hasCustomErrors: (db.documentation?.custom_errors?.length ?? 0) > 0,
|
||||
customErrorCount: db.documentation?.custom_errors?.length ?? 0,
|
||||
@@ -233,10 +244,10 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
|
||||
// Add compatible databases from this database's documentation
|
||||
const compatibleDbs = db.documentation?.compatible_databases ?? [];
|
||||
compatibleDbs.forEach((compat) => {
|
||||
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()
|
||||
dbName => dbName.toLowerCase() === compat.name.toLowerCase(),
|
||||
);
|
||||
|
||||
if (!existsAsMain) {
|
||||
@@ -277,14 +288,15 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
// Filter and sort databases
|
||||
const filteredDatabases = useMemo(() => {
|
||||
return databaseList
|
||||
.filter((db) => {
|
||||
.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);
|
||||
const matchesCategory =
|
||||
!categoryFilter || db.categories.includes(categoryFilter);
|
||||
return matchesSearch && matchesCategory;
|
||||
})
|
||||
.sort((a, b) => b.score - a.score);
|
||||
@@ -293,9 +305,9 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
// Get unique categories and counts for filter
|
||||
const { categories, categoryCounts } = useMemo(() => {
|
||||
const counts: Record<string, number> = {};
|
||||
databaseList.forEach((db) => {
|
||||
databaseList.forEach(db => {
|
||||
// Count each category the database belongs to
|
||||
db.categories.forEach((cat) => {
|
||||
db.categories.forEach(cat => {
|
||||
counts[cat] = (counts[cat] || 0) + 1;
|
||||
});
|
||||
});
|
||||
@@ -314,12 +326,17 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
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, '');
|
||||
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)}`;
|
||||
const linkTarget =
|
||||
record.isCompatible && record.compatibleWith
|
||||
? `/docs/databases/supported/${toSlug(record.compatibleWith)}`
|
||||
: `/docs/databases/supported/${toSlug(name)}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -337,7 +354,9 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
)}
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
{record.documentation?.description?.slice(0, 80)}
|
||||
{(record.documentation?.description?.length ?? 0) > 80 ? '...' : ''}
|
||||
{(record.documentation?.description?.length ?? 0) > 80
|
||||
? '...'
|
||||
: ''}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -348,13 +367,15 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
dataIndex: 'categories',
|
||||
key: 'categories',
|
||||
width: 220,
|
||||
filters: categories.map((cat) => ({ text: cat, value: cat })),
|
||||
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>
|
||||
{cats.map(cat => (
|
||||
<Tag key={cat} color={CATEGORY_COLORS[cat] || 'default'}>
|
||||
{cat}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
@@ -382,16 +403,26 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
dataIndex: 'timeGrainCount',
|
||||
key: 'timeGrainCount',
|
||||
width: 100,
|
||||
sorter: (a: TableEntry, b: TableEntry) => a.timeGrainCount - b.timeGrainCount,
|
||||
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
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '4px',
|
||||
maxWidth: 280,
|
||||
}}
|
||||
>
|
||||
{grains.map(grain => (
|
||||
<Tag key={grain} style={{ margin: 0 }}>
|
||||
{grain}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
@@ -450,13 +481,17 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
<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_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.user_impersonation && (
|
||||
<Tag color="magenta">Impersonation</Tag>
|
||||
)}
|
||||
{record.sql_validation && <Tag color="lime">SQL Validation</Tag>}
|
||||
</div>
|
||||
),
|
||||
@@ -545,7 +580,7 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
placeholder="Search databases..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
</Col>
|
||||
@@ -556,7 +591,7 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
value={categoryFilter}
|
||||
onChange={setCategoryFilter}
|
||||
allowClear
|
||||
options={categories.map((cat) => ({
|
||||
options={categories.map(cat => ({
|
||||
label: (
|
||||
<span>
|
||||
<Tag
|
||||
@@ -578,11 +613,15 @@ const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
|
||||
<Table
|
||||
dataSource={filteredDatabases}
|
||||
columns={columns}
|
||||
rowKey={(record) => record.isCompatible ? `${record.compatibleWith}-${record.name}` : record.name}
|
||||
rowKey={record =>
|
||||
record.isCompatible
|
||||
? `${record.compatibleWith}-${record.name}`
|
||||
: record.name
|
||||
}
|
||||
pagination={{
|
||||
defaultPageSize: 20,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `${total} databases`,
|
||||
showTotal: total => `${total} databases`,
|
||||
}}
|
||||
size="middle"
|
||||
/>
|
||||
|
||||
@@ -35,7 +35,10 @@ const databases = Object.entries(typedData.databases)
|
||||
.map(([name, db]) => ({
|
||||
name,
|
||||
logo: db.documentation.logo!,
|
||||
docPath: `/user-docs/databases/supported/${name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}`,
|
||||
docPath: `/user-docs/databases/supported/${name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')}`,
|
||||
}));
|
||||
|
||||
export default function DatabaseLogoWall(): React.JSX.Element {
|
||||
|
||||
@@ -77,7 +77,7 @@ export interface CompatibleDatabase {
|
||||
description?: string;
|
||||
logo?: string;
|
||||
homepage_url?: string;
|
||||
categories?: string[]; // Category classifications (e.g., ["TRADITIONAL_RDBMS", "OPEN_SOURCE"])
|
||||
categories?: string[]; // Category classifications (e.g., ["TRADITIONAL_RDBMS", "OPEN_SOURCE"])
|
||||
pypi_packages?: string[];
|
||||
connection_string?: string;
|
||||
parameters?: Record<string, string>;
|
||||
@@ -87,27 +87,27 @@ export interface CompatibleDatabase {
|
||||
}
|
||||
|
||||
export interface CustomError {
|
||||
error_type: string; // e.g., "CONNECTION_INVALID_USERNAME_ERROR"
|
||||
message_template: string; // e.g., 'The username "%(username)s" does not exist.'
|
||||
regex_pattern?: string; // The regex pattern that matches this error (optional, for reference)
|
||||
regex_name?: string; // The name of the regex constant (e.g., "CONNECTION_INVALID_USERNAME_REGEX")
|
||||
invalid_fields?: string[]; // Fields that are invalid, e.g., ["username", "password"]
|
||||
issue_codes?: number[]; // Related issue codes from ISSUE_CODES mapping
|
||||
category?: string; // Error category: "Authentication", "Connection", "Query", etc.
|
||||
description?: string; // Human-readable short description of the error type
|
||||
error_type: string; // e.g., "CONNECTION_INVALID_USERNAME_ERROR"
|
||||
message_template: string; // e.g., 'The username "%(username)s" does not exist.'
|
||||
regex_pattern?: string; // The regex pattern that matches this error (optional, for reference)
|
||||
regex_name?: string; // The name of the regex constant (e.g., "CONNECTION_INVALID_USERNAME_REGEX")
|
||||
invalid_fields?: string[]; // Fields that are invalid, e.g., ["username", "password"]
|
||||
issue_codes?: number[]; // Related issue codes from ISSUE_CODES mapping
|
||||
category?: string; // Error category: "Authentication", "Connection", "Query", etc.
|
||||
description?: string; // Human-readable short description of the error type
|
||||
}
|
||||
|
||||
export interface DatabaseDocumentation {
|
||||
description?: string;
|
||||
logo?: string;
|
||||
homepage_url?: string;
|
||||
categories?: string[]; // Category classifications (e.g., ["TRADITIONAL_RDBMS", "OPEN_SOURCE"])
|
||||
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
|
||||
limitations?: string[]; // Known limitations or caveats
|
||||
connection_examples?: ConnectionExample[];
|
||||
host_examples?: HostExample[];
|
||||
drivers?: Driver[];
|
||||
@@ -122,7 +122,7 @@ export interface DatabaseDocumentation {
|
||||
sqlalchemy_docs_url?: string;
|
||||
advanced_features?: Record<string, string>;
|
||||
compatible_databases?: CompatibleDatabase[];
|
||||
custom_errors?: CustomError[]; // Database-specific error messages and troubleshooting info
|
||||
custom_errors?: CustomError[]; // Database-specific error messages and troubleshooting info
|
||||
}
|
||||
|
||||
export interface TimeGrains {
|
||||
|
||||
@@ -52,7 +52,7 @@ const ComponentIndex: React.FC<ComponentIndexProps> = ({ data }) => {
|
||||
|
||||
const filteredComponents = useMemo(() => {
|
||||
return components
|
||||
.filter((comp) => {
|
||||
.filter(comp => {
|
||||
const matchesSearch =
|
||||
!searchText ||
|
||||
comp.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
@@ -67,7 +67,7 @@ const ComponentIndex: React.FC<ComponentIndexProps> = ({ data }) => {
|
||||
|
||||
const { categories, categoryCounts } = useMemo(() => {
|
||||
const counts: Record<string, number> = {};
|
||||
components.forEach((comp) => {
|
||||
components.forEach(comp => {
|
||||
counts[comp.category] = (counts[comp.category] || 0) + 1;
|
||||
});
|
||||
return {
|
||||
@@ -103,7 +103,7 @@ const ComponentIndex: React.FC<ComponentIndexProps> = ({ data }) => {
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
width: 120,
|
||||
filters: categories.map((cat) => ({
|
||||
filters: categories.map(cat => ({
|
||||
text: CATEGORY_LABELS[cat] || cat,
|
||||
value: cat,
|
||||
})),
|
||||
@@ -120,9 +120,7 @@ const ComponentIndex: React.FC<ComponentIndexProps> = ({ data }) => {
|
||||
dataIndex: 'package',
|
||||
key: 'package',
|
||||
width: 220,
|
||||
render: (pkg: string) => (
|
||||
<code style={{ fontSize: '12px' }}>{pkg}</code>
|
||||
),
|
||||
render: (pkg: string) => <code style={{ fontSize: '12px' }}>{pkg}</code>,
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
@@ -215,7 +213,7 @@ const ComponentIndex: React.FC<ComponentIndexProps> = ({ data }) => {
|
||||
placeholder="Search components..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
</Col>
|
||||
@@ -226,7 +224,7 @@ const ComponentIndex: React.FC<ComponentIndexProps> = ({ data }) => {
|
||||
value={categoryFilter}
|
||||
onChange={setCategoryFilter}
|
||||
allowClear
|
||||
options={categories.map((cat) => ({
|
||||
options={categories.map(cat => ({
|
||||
label: (
|
||||
<span>
|
||||
<Tag
|
||||
@@ -251,7 +249,7 @@ const ComponentIndex: React.FC<ComponentIndexProps> = ({ data }) => {
|
||||
pagination={{
|
||||
defaultPageSize: 20,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `${total} components`,
|
||||
showTotal: total => `${total} components`,
|
||||
}}
|
||||
size="middle"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user