mirror of
https://github.com/apache/superset.git
synced 2026-04-09 11:25:23 +00:00
465 lines
10 KiB
Markdown
465 lines
10 KiB
Markdown
---
|
|
title: Extension Examples
|
|
sidebar_position: 1
|
|
hide_title: true
|
|
---
|
|
|
|
<!--
|
|
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.
|
|
-->
|
|
|
|
# Extension Examples
|
|
|
|
Learn from real-world extension implementations that showcase different capabilities of the Superset extension system.
|
|
|
|
## Dataset References Panel
|
|
|
|
A SQL Lab panel that analyzes queries and displays information about referenced tables.
|
|
|
|
### Features
|
|
- Parses SQL to extract table references
|
|
- Shows table owners and permissions
|
|
- Displays last partition information
|
|
- Provides row count estimates
|
|
|
|
### Key Implementation
|
|
|
|
```typescript
|
|
// Parse SQL and extract tables
|
|
function extractTables(sql: string): TableReference[] {
|
|
const tables = [];
|
|
const tableRegex = /FROM\s+(\w+\.?\w+)/gi;
|
|
let match;
|
|
|
|
while ((match = tableRegex.exec(sql)) !== null) {
|
|
tables.push({
|
|
schema: match[1].split('.')[0],
|
|
table: match[1].split('.')[1] || match[1],
|
|
});
|
|
}
|
|
|
|
return tables;
|
|
}
|
|
|
|
// Register panel
|
|
export function activate(context: ExtensionContext) {
|
|
const panel = context.core.registerView('dataset-references.panel', () => (
|
|
<DatasetReferencesPanel />
|
|
));
|
|
|
|
// Listen for query changes
|
|
const listener = context.sqlLab.onDidChangeEditorContent((content) => {
|
|
const tables = extractTables(content);
|
|
updatePanelWithTables(tables);
|
|
});
|
|
|
|
context.subscriptions.push(panel, listener);
|
|
}
|
|
```
|
|
|
|
### Manifest
|
|
|
|
```json
|
|
{
|
|
"name": "dataset-references",
|
|
"contributions": {
|
|
"views": {
|
|
"sqllab.panels": [{
|
|
"id": "dataset-references.panel",
|
|
"name": "Dataset References",
|
|
"icon": "DatabaseOutlined",
|
|
"location": "right"
|
|
}]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Query Optimizer
|
|
|
|
Analyzes SQL queries and suggests optimizations.
|
|
|
|
### Features
|
|
- Detects missing indexes
|
|
- Suggests query rewrites
|
|
- Identifies expensive operations
|
|
- Provides execution plan analysis
|
|
|
|
### Implementation Highlights
|
|
|
|
```typescript
|
|
// Register optimization command
|
|
const optimizeCommand = context.commands.registerCommand('query-optimizer.analyze', {
|
|
title: 'Analyze Query Performance',
|
|
icon: 'ThunderboltOutlined',
|
|
execute: async () => {
|
|
const query = context.sqlLab.getCurrentQuery();
|
|
const database = context.sqlLab.getCurrentDatabase();
|
|
|
|
// Get execution plan
|
|
const plan = await getExecutionPlan(database.id, query);
|
|
|
|
// Analyze and suggest improvements
|
|
const suggestions = analyzeExecutionPlan(plan);
|
|
|
|
// Show results in panel
|
|
showOptimizationResults(suggestions);
|
|
}
|
|
});
|
|
|
|
// Add to editor menu
|
|
"menus": {
|
|
"sqllab.editor": {
|
|
"primary": [{
|
|
"command": "query-optimizer.analyze",
|
|
"when": "editorHasContent"
|
|
}]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Natural Language to SQL
|
|
|
|
Converts natural language questions to SQL queries using LLM integration.
|
|
|
|
### Features
|
|
- Natural language input
|
|
- Context-aware SQL generation
|
|
- Query validation
|
|
- History tracking
|
|
|
|
### Key Components
|
|
|
|
```typescript
|
|
// Backend API endpoint
|
|
@rest_api.route('/nl2sql/generate')
|
|
def generate_sql(prompt: str, context: dict):
|
|
# Use LLM to generate SQL
|
|
sql = llm_client.generate(
|
|
prompt=prompt,
|
|
schema=context['schema'],
|
|
examples=context['examples']
|
|
)
|
|
|
|
# Validate generated SQL
|
|
validation = validate_sql(sql)
|
|
|
|
return {
|
|
'sql': sql,
|
|
'valid': validation.is_valid,
|
|
'errors': validation.errors
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
// Frontend integration
|
|
function NL2SQLPanel() {
|
|
const [prompt, setPrompt] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const generateSQL = async () => {
|
|
setLoading(true);
|
|
|
|
const response = await context.network.api.post('/extensions/nl2sql/generate', {
|
|
prompt,
|
|
context: {
|
|
database: context.sqlLab.getCurrentDatabase(),
|
|
schema: await context.sqlLab.getCurrentSchema(),
|
|
}
|
|
});
|
|
|
|
if (response.valid) {
|
|
// Insert SQL into editor
|
|
context.sqlLab.insertText(response.sql);
|
|
}
|
|
|
|
setLoading(false);
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<Input.TextArea
|
|
value={prompt}
|
|
onChange={(e) => setPrompt(e.target.value)}
|
|
placeholder="Describe what data you want..."
|
|
/>
|
|
<Button onClick={generateSQL} loading={loading}>
|
|
Generate SQL
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Schema Visualizer
|
|
|
|
Interactive database schema visualization.
|
|
|
|
### Features
|
|
- Visual ERD diagram
|
|
- Table relationships
|
|
- Column details on hover
|
|
- Export to image
|
|
|
|
### Implementation
|
|
|
|
```typescript
|
|
import { Graph } from '@antv/g6';
|
|
|
|
function SchemaVisualizer() {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [graph, setGraph] = useState<Graph>();
|
|
|
|
useEffect(() => {
|
|
if (!containerRef.current) return;
|
|
|
|
const g = new Graph({
|
|
container: containerRef.current,
|
|
layout: {
|
|
type: 'dagre',
|
|
rankdir: 'LR',
|
|
},
|
|
defaultNode: {
|
|
type: 'sql-table-node',
|
|
},
|
|
defaultEdge: {
|
|
type: 'sql-relation-edge',
|
|
},
|
|
});
|
|
|
|
setGraph(g);
|
|
loadSchemaData(g);
|
|
|
|
return () => g.destroy();
|
|
}, []);
|
|
|
|
const loadSchemaData = async (g: Graph) => {
|
|
const tables = await context.sqlLab.getTables();
|
|
const nodes = tables.map(table => ({
|
|
id: table.name,
|
|
label: table.name,
|
|
columns: table.columns,
|
|
}));
|
|
|
|
const edges = extractRelationships(tables);
|
|
|
|
g.data({ nodes, edges });
|
|
g.render();
|
|
};
|
|
|
|
return <div ref={containerRef} style={{ height: '100%' }} />;
|
|
}
|
|
```
|
|
|
|
## SQL Formatter
|
|
|
|
Formats and beautifies SQL code with customizable rules.
|
|
|
|
### Features
|
|
- Multiple formatting styles
|
|
- Custom rule configuration
|
|
- Batch formatting
|
|
- Format on save
|
|
|
|
### Simple Implementation
|
|
|
|
```typescript
|
|
import { format } from 'sql-formatter';
|
|
|
|
const formatCommand = context.commands.registerCommand('sql-formatter.format', {
|
|
title: 'Format SQL',
|
|
execute: () => {
|
|
const sql = context.sqlLab.getCurrentQuery();
|
|
|
|
const formatted = format(sql, {
|
|
language: 'sql',
|
|
indent: ' ',
|
|
uppercase: true,
|
|
linesBetweenQueries: 2,
|
|
});
|
|
|
|
context.sqlLab.replaceQuery(formatted);
|
|
}
|
|
});
|
|
|
|
// Auto-format on save
|
|
context.sqlLab.onWillSaveQuery((event) => {
|
|
if (context.storage.local.get('autoFormat')) {
|
|
const formatted = format(event.query);
|
|
event.waitUntil(Promise.resolve(formatted));
|
|
}
|
|
});
|
|
```
|
|
|
|
## Query History Search
|
|
|
|
Enhanced query history with advanced search and filtering.
|
|
|
|
### Features
|
|
- Full-text search
|
|
- Filter by date, user, database
|
|
- Query statistics
|
|
- Export capabilities
|
|
|
|
### UI Component
|
|
|
|
```typescript
|
|
function QueryHistoryPanel() {
|
|
const [queries, setQueries] = useState<Query[]>([]);
|
|
const [filters, setFilters] = useState<Filters>({});
|
|
|
|
useEffect(() => {
|
|
loadQueries();
|
|
}, [filters]);
|
|
|
|
const loadQueries = async () => {
|
|
const history = await context.network.api.get('/api/v1/query', {
|
|
params: {
|
|
...filters,
|
|
page_size: 100,
|
|
}
|
|
});
|
|
|
|
setQueries(history.result);
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<SearchFilters onChange={setFilters} />
|
|
<Table
|
|
dataSource={queries}
|
|
columns={[
|
|
{ title: 'Query', dataIndex: 'sql', ellipsis: true },
|
|
{ title: 'Database', dataIndex: 'database' },
|
|
{ title: 'Status', dataIndex: 'status' },
|
|
{ title: 'Duration', dataIndex: 'duration' },
|
|
{ title: 'User', dataIndex: 'user' },
|
|
{
|
|
title: 'Actions',
|
|
render: (query) => (
|
|
<Button
|
|
icon={<CopyOutlined />}
|
|
onClick={() => context.sqlLab.insertText(query.sql)}
|
|
/>
|
|
),
|
|
},
|
|
]}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Git Integration
|
|
|
|
Version control for SQL queries and dashboards.
|
|
|
|
### Features
|
|
- Save queries to Git
|
|
- Track changes
|
|
- Collaborative editing
|
|
- Branch management
|
|
|
|
### Backend Integration
|
|
|
|
```python
|
|
from git import Repo
|
|
|
|
class GitExtension:
|
|
def __init__(self, repo_path):
|
|
self.repo = Repo(repo_path)
|
|
|
|
def save_query(self, query, message):
|
|
# Save query to file
|
|
path = f"queries/{query.name}.sql"
|
|
with open(path, 'w') as f:
|
|
f.write(query.sql)
|
|
|
|
# Commit to Git
|
|
self.repo.index.add([path])
|
|
self.repo.index.commit(message)
|
|
|
|
return {
|
|
'status': 'success',
|
|
'commit': self.repo.head.commit.hexsha
|
|
}
|
|
```
|
|
|
|
## Best Practices from Examples
|
|
|
|
### 1. User Experience
|
|
- Provide clear feedback for async operations
|
|
- Handle errors gracefully
|
|
- Include loading states
|
|
- Add keyboard shortcuts
|
|
|
|
### 2. Performance
|
|
- Debounce expensive operations
|
|
- Cache API responses
|
|
- Use virtual scrolling for large lists
|
|
- Lazy load heavy components
|
|
|
|
### 3. Integration
|
|
- Respect Superset's theme
|
|
- Use provided UI components
|
|
- Follow existing UX patterns
|
|
- Integrate with existing menus
|
|
|
|
### 4. Code Organization
|
|
```
|
|
extension/
|
|
├── frontend/
|
|
│ ├── src/
|
|
│ │ ├── components/ # UI components
|
|
│ │ ├── hooks/ # Custom hooks
|
|
│ │ ├── services/ # API services
|
|
│ │ ├── utils/ # Utilities
|
|
│ │ └── index.tsx # Entry point
|
|
│ └── tests/
|
|
├── backend/
|
|
│ ├── src/
|
|
│ │ ├── api/ # REST endpoints
|
|
│ │ ├── models/ # Data models
|
|
│ │ ├── services/ # Business logic
|
|
│ │ └── entrypoint.py
|
|
│ └── tests/
|
|
```
|
|
|
|
### 5. Testing
|
|
```typescript
|
|
// Test example
|
|
describe('DatasetReferences', () => {
|
|
it('should extract tables from SQL', () => {
|
|
const sql = 'SELECT * FROM users JOIN orders ON users.id = orders.user_id';
|
|
const tables = extractTables(sql);
|
|
|
|
expect(tables).toEqual([
|
|
{ schema: 'public', table: 'users' },
|
|
{ schema: 'public', table: 'orders' },
|
|
]);
|
|
});
|
|
});
|
|
```
|
|
|
|
## Learn More
|
|
|
|
- [API Reference](../api/frontend)
|
|
- [Architecture Overview](../architecture/overview)
|
|
- [Getting Started Guide](../getting-started)
|
|
- [CLI Documentation](../cli/overview)
|