mirror of
https://github.com/apache/superset.git
synced 2026-06-13 19:49:18 +00:00
Compare commits
5 Commits
mcp_servic
...
examples
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0796aa1c6d | ||
|
|
14e6ec7d9f | ||
|
|
14ffa69e0b | ||
|
|
ef4cf2b430 | ||
|
|
48d8c91b19 |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
// Extend the base configuration
|
||||
"extends": "../devcontainer-base.json",
|
||||
|
||||
"name": "Apache Superset Development (Default)",
|
||||
|
||||
// Forward ports for development
|
||||
"forwardPorts": [9001],
|
||||
"portsAttributes": {
|
||||
"9001": {
|
||||
"label": "Superset (via Webpack Dev Server)",
|
||||
"onAutoForward": "notify",
|
||||
"visibility": "public"
|
||||
}
|
||||
},
|
||||
|
||||
// Auto-start Superset on Codespace resume
|
||||
"postStartCommand": ".devcontainer/start-superset.sh"
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "Apache Superset Development",
|
||||
// Keep this in sync with the base image in Dockerfile (ARG PY_VER)
|
||||
// Using the same base as Dockerfile, but non-slim for dev tools
|
||||
"image": "python:3.11.13-bookworm",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"moby": true,
|
||||
"dockerDashComposeVersion": "v2"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "20"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git:1": {},
|
||||
"ghcr.io/devcontainers/features/common-utils:2": {
|
||||
"configureZshAsDefaultShell": true
|
||||
},
|
||||
"ghcr.io/devcontainers/features/sshd:1": {
|
||||
"version": "latest"
|
||||
}
|
||||
},
|
||||
|
||||
// Run commands after container is created
|
||||
"postCreateCommand": "chmod +x .devcontainer/setup-dev.sh && .devcontainer/setup-dev.sh",
|
||||
|
||||
// VS Code customizations
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"charliermarsh.ruff",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,6 @@
|
||||
echo "🚀 Starting Superset in Codespaces..."
|
||||
echo "🌐 Frontend will be available at port 9001"
|
||||
|
||||
# Check if MCP is enabled
|
||||
if [ "$ENABLE_MCP" = "true" ]; then
|
||||
echo "🤖 MCP Service will be available at port 5008"
|
||||
fi
|
||||
|
||||
# Find the workspace directory (Codespaces clones as 'superset', not 'superset-2')
|
||||
WORKSPACE_DIR=$(find /workspaces -maxdepth 1 -name "superset*" -type d | head -1)
|
||||
if [ -n "$WORKSPACE_DIR" ]; then
|
||||
@@ -26,7 +21,7 @@ fi
|
||||
|
||||
# Clean up any existing containers
|
||||
echo "🧹 Cleaning up existing containers..."
|
||||
docker-compose -f docker-compose-light.yml --profile mcp down
|
||||
docker-compose -f docker-compose-light.yml down
|
||||
|
||||
# Start services
|
||||
echo "🏗️ Building and starting services..."
|
||||
@@ -38,12 +33,7 @@ echo ""
|
||||
echo "📋 Running in foreground with live logs (Ctrl+C to stop)..."
|
||||
|
||||
# Run docker-compose and capture exit code
|
||||
if [ "$ENABLE_MCP" = "true" ]; then
|
||||
echo "🤖 Starting with MCP Service enabled..."
|
||||
docker-compose -f docker-compose-light.yml --profile mcp up
|
||||
else
|
||||
docker-compose -f docker-compose-light.yml up
|
||||
fi
|
||||
docker-compose -f docker-compose-light.yml up
|
||||
EXIT_CODE=$?
|
||||
|
||||
# If it failed, provide helpful instructions
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
// Extend the base configuration
|
||||
"extends": "../devcontainer-base.json",
|
||||
|
||||
"name": "Apache Superset Development with MCP",
|
||||
|
||||
// Forward ports for development
|
||||
"forwardPorts": [9001, 5008],
|
||||
"portsAttributes": {
|
||||
"9001": {
|
||||
"label": "Superset (via Webpack Dev Server)",
|
||||
"onAutoForward": "notify",
|
||||
"visibility": "public"
|
||||
},
|
||||
"5008": {
|
||||
"label": "MCP Service (Model Context Protocol)",
|
||||
"onAutoForward": "notify",
|
||||
"visibility": "private"
|
||||
}
|
||||
},
|
||||
|
||||
// Auto-start Superset with MCP on Codespace resume
|
||||
"postStartCommand": "ENABLE_MCP=true .devcontainer/start-superset.sh",
|
||||
|
||||
// Environment variables
|
||||
"containerEnv": {
|
||||
"ENABLE_MCP": "true"
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ esm/*
|
||||
tsconfig.tsbuildinfo
|
||||
.*ipynb
|
||||
.*yml
|
||||
.*yaml
|
||||
.*iml
|
||||
.esprintrc
|
||||
.prettierignore
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
# Chart Metadata API Reference
|
||||
|
||||
The Superset MCP service provides rich metadata alongside chart generation to enable better UI integration and user experiences.
|
||||
|
||||
## Background & Design Philosophy
|
||||
|
||||
Modern chart systems need to provide more than just visual output. Inspired by contemporary web standards and LLM integration patterns, this metadata system addresses several key needs:
|
||||
|
||||
**Accessibility-First Design**: Following WCAG guidelines and `aria-*` attribute patterns, charts include semantic descriptions and accessibility metadata to ensure inclusive experiences.
|
||||
|
||||
**Rich Context for AI Systems**: Similar to how platforms like social media generate rich previews (OpenGraph, Twitter Cards), charts provide semantic understanding beyond just visual representation - enabling AI agents to reason about and describe visualizations meaningfully.
|
||||
|
||||
**Performance-Aware Integration**: Modern web APIs emphasize performance transparency (Core Web Vitals, etc.). Charts include execution metrics and optimization suggestions to help UIs make informed decisions about rendering and user feedback.
|
||||
|
||||
**Capability-Driven UX**: Rather than requiring UIs to hardcode chart type behaviors, the system exposes what each chart can actually do - enabling dynamic, contextual interfaces that adapt to chart capabilities.
|
||||
|
||||
## Overview
|
||||
|
||||
When generating charts via `generate_chart`, the response includes structured metadata that helps UIs:
|
||||
- Present appropriate controls and interactions
|
||||
- Generate accessible descriptions
|
||||
- Optimize rendering performance
|
||||
- Guide user workflows
|
||||
|
||||
## Metadata Types
|
||||
|
||||
### ChartCapabilities
|
||||
|
||||
Describes what interactions and features the chart supports.
|
||||
|
||||
```python
|
||||
{
|
||||
"supports_interaction": bool, # User can interact (zoom, pan, hover)
|
||||
"supports_real_time": bool, # Chart can update with live data
|
||||
"supports_drill_down": bool, # Can navigate to more detailed views
|
||||
"supports_export": bool, # Can be exported to other formats
|
||||
"optimal_formats": [ # Recommended preview formats
|
||||
"url", # Static image URL
|
||||
"interactive", # HTML with JavaScript controls
|
||||
"ascii", # Text-based representation
|
||||
"vega_lite" # Vega-Lite specification
|
||||
],
|
||||
"data_types": [ # Types of data visualized
|
||||
"time_series", # Time-based data
|
||||
"categorical", # Discrete categories
|
||||
"metric" # Numeric measurements
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**UI Integration:**
|
||||
- Show/hide interaction controls based on `supports_interaction`
|
||||
- Enable real-time updates if `supports_real_time`
|
||||
- Display drill-down options for `supports_drill_down`
|
||||
- Choose optimal preview format from `optimal_formats`
|
||||
|
||||
### ChartSemantics
|
||||
|
||||
Provides semantic understanding of what the chart represents and reveals.
|
||||
|
||||
```python
|
||||
{
|
||||
"primary_insight": "Shows trends and changes over time",
|
||||
"data_story": "This line chart analyzes sales, revenue over Q1-Q4",
|
||||
"recommended_actions": [
|
||||
"Review data patterns and trends",
|
||||
"Consider filtering for more detail",
|
||||
"Export chart for reporting"
|
||||
],
|
||||
"anomalies": [], # Notable outliers (future enhancement)
|
||||
"statistical_summary": {} # Key statistics (future enhancement)
|
||||
}
|
||||
```
|
||||
|
||||
**UI Integration:**
|
||||
- Display `primary_insight` as chart description
|
||||
- Use `data_story` for accessibility and tooltips
|
||||
- Show `recommended_actions` as suggested next steps
|
||||
- Highlight `anomalies` in the visualization
|
||||
|
||||
### AccessibilityMetadata
|
||||
|
||||
Information for creating inclusive, accessible chart experiences.
|
||||
|
||||
```python
|
||||
{
|
||||
"color_blind_safe": bool, # Uses colorblind-friendly palette
|
||||
"alt_text": "Chart showing Sales Data over time",
|
||||
"high_contrast_available": bool # High contrast version available
|
||||
}
|
||||
```
|
||||
|
||||
**UI Integration:**
|
||||
- Use `alt_text` for screen readers
|
||||
- Show accessibility indicators if `color_blind_safe`
|
||||
- Offer high contrast mode if available
|
||||
|
||||
### PerformanceMetadata
|
||||
|
||||
Performance information for optimization and user feedback.
|
||||
|
||||
```python
|
||||
{
|
||||
"query_duration_ms": 1250, # Time to generate chart data
|
||||
"cache_status": "hit|miss|error", # Whether data came from cache
|
||||
"optimization_suggestions": [ # Performance improvement tips
|
||||
"Consider adding date filters to reduce data volume",
|
||||
"Chart complexity may impact load time"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**UI Integration:**
|
||||
- Show loading indicators based on `query_duration_ms`
|
||||
- Display cache status for debugging
|
||||
- Present `optimization_suggestions` to users
|
||||
- Warn about slow queries
|
||||
|
||||
## Example Response
|
||||
|
||||
```json
|
||||
{
|
||||
"chart": {
|
||||
"id": 123,
|
||||
"slice_name": "Sales Trends Q1-Q4",
|
||||
"viz_type": "echarts_timeseries_line",
|
||||
"url": "/explore/?slice_id=123"
|
||||
},
|
||||
"capabilities": {
|
||||
"supports_interaction": true,
|
||||
"supports_real_time": false,
|
||||
"supports_drill_down": false,
|
||||
"supports_export": true,
|
||||
"optimal_formats": ["url", "interactive", "ascii"],
|
||||
"data_types": ["time_series", "metric"]
|
||||
},
|
||||
"semantics": {
|
||||
"primary_insight": "Shows trends and changes over time",
|
||||
"data_story": "This line chart analyzes sales over Q1-Q4",
|
||||
"recommended_actions": [
|
||||
"Review seasonal patterns",
|
||||
"Export for quarterly report"
|
||||
]
|
||||
},
|
||||
"accessibility": {
|
||||
"color_blind_safe": true,
|
||||
"alt_text": "Line chart showing sales trends from Q1 to Q4",
|
||||
"high_contrast_available": false
|
||||
},
|
||||
"performance": {
|
||||
"query_duration_ms": 450,
|
||||
"cache_status": "miss",
|
||||
"optimization_suggestions": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### React Component Integration
|
||||
|
||||
```jsx
|
||||
function ChartComponent({ chartData }) {
|
||||
const { capabilities, semantics, accessibility, performance } = chartData;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Accessibility */}
|
||||
<img
|
||||
src={chartData.chart.url}
|
||||
alt={accessibility.alt_text}
|
||||
aria-describedby="chart-description"
|
||||
/>
|
||||
|
||||
{/* Semantic description */}
|
||||
<p id="chart-description">{semantics.primary_insight}</p>
|
||||
|
||||
{/* Conditional controls based on capabilities */}
|
||||
{capabilities.supports_interaction && (
|
||||
<InteractiveControls />
|
||||
)}
|
||||
|
||||
{capabilities.supports_export && (
|
||||
<ExportButton />
|
||||
)}
|
||||
|
||||
{/* Performance feedback */}
|
||||
{performance.query_duration_ms > 2000 && (
|
||||
<SlowQueryWarning suggestions={performance.optimization_suggestions} />
|
||||
)}
|
||||
|
||||
{/* Recommended actions */}
|
||||
<ActionSuggestions actions={semantics.recommended_actions} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Chart Type Mapping
|
||||
|
||||
Different chart types provide different capabilities:
|
||||
|
||||
| Chart Type | Interaction | Real-time | Drill-down | Optimal Formats |
|
||||
|------------|------------|-----------|------------|-----------------|
|
||||
| `echarts_timeseries_line` | ✅ | ✅ | ❌ | url, interactive, ascii |
|
||||
| `echarts_timeseries_bar` | ✅ | ✅ | ❌ | url, interactive, ascii |
|
||||
| `table` | ❌ | ❌ | ✅ | url, table, ascii |
|
||||
| `pie` | ✅ | ❌ | ❌ | url, interactive |
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- **Statistical Summary**: Automatic calculation of mean, median, trends
|
||||
- **Anomaly Detection**: Identification of outliers and unusual patterns
|
||||
- **Smart Recommendations**: ML-powered suggestions for chart improvements
|
||||
- **Accessibility Scoring**: Automated accessibility compliance checking
|
||||
1
LLMS.md
1
LLMS.md
@@ -180,7 +180,6 @@ pre-commit run eslint # Frontend linting
|
||||
|
||||
## Platform-Specific Instructions
|
||||
|
||||
- **[LLMS.md](LLMS.md)** - General LLM development guide (READ THIS FIRST)
|
||||
- **[CLAUDE.md](CLAUDE.md)** - For Claude/Anthropic tools
|
||||
- **[.github/copilot-instructions.md](.github/copilot-instructions.md)** - For GitHub Copilot
|
||||
- **[GEMINI.md](GEMINI.md)** - For Google Gemini tools
|
||||
|
||||
@@ -23,6 +23,8 @@ This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
- [34346](https://github.com/apache/superset/pull/34346) The examples system has been migrated from Python-based scripts to YAML configuration files. The CLI command `superset load-examples` has been deprecated in favor of `superset examples load`. The old command still works but will show a deprecation warning. Additional example management commands are available under `superset examples` including `clear-old` and `reload`. If you have old Python-based examples loaded, the new YAML-based examples will not load automatically to preserve your existing data. To migrate to the new examples, run `superset examples clear-old --confirm` followed by `superset examples load`.
|
||||
**Note**: This change affects Cypress tests that rely on specific chart names from the old examples (e.g., "Num Births Trend", "Daily Totals"). These charts may not exist in the new YAML examples, causing test failures. Consider updating your Cypress tests or creating test-specific fixtures.
|
||||
- [33084](https://github.com/apache/superset/pull/33084) The DISALLOWED_SQL_FUNCTIONS configuration now includes additional potentially sensitive database functions across PostgreSQL, MySQL, SQLite, MS SQL Server, and ClickHouse. Existing queries using these functions may now be blocked. Review your SQL Lab queries and dashboards if you encounter "disallowed function" errors after upgrading
|
||||
- [34235](https://github.com/apache/superset/pull/34235) CSV exports now use `utf-8-sig` encoding by default to include a UTF-8 BOM, improving compatibility with Excel.
|
||||
- [34258](https://github.com/apache/superset/pull/34258) changing the default in Dockerfile to INCLUDE_CHROMIUM="false" (from "true") in the past. This ensures the `lean` layer is lean by default, and people can opt-in to the `chromium` layer by setting the build arg `INCLUDE_CHROMIUM=true`. This is a breaking change for anyone using the `lean` layer, as it will no longer include Chromium by default.
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
# If you choose to use this type of deployment make sure to
|
||||
# create you own docker environment file (docker/.env) with your own
|
||||
# unique random secure passwords and SECRET_KEY.
|
||||
#
|
||||
# For verbose logging during development:
|
||||
# - Set SUPERSET_LOG_LEVEL=debug in docker/.env-local for detailed Superset logs
|
||||
# -----------------------------------------------------------------------
|
||||
x-superset-image: &superset-image apachesuperset.docker.scarf.sh/apache/superset:${TAG:-latest-dev}
|
||||
x-superset-volumes:
|
||||
|
||||
@@ -25,12 +25,6 @@
|
||||
# - Volumes are isolated by project name (e.g., project1_db_home_light, project2_db_home_light)
|
||||
# - Database name is intentionally different (superset_light) to prevent accidental cross-connections
|
||||
#
|
||||
# MCP Service (Model Context Protocol):
|
||||
# - Optional service for LLM agent integration, available under 'mcp' profile
|
||||
# - To include MCP: docker-compose -f docker-compose-light.yml --profile mcp up
|
||||
# - MCP runs on port 5008 by default (customize with MCP_PORT=5009)
|
||||
# - Enable SQL debugging with MCP_SQL_DEBUG=true
|
||||
#
|
||||
# For verbose logging during development:
|
||||
# - Set SUPERSET_LOG_LEVEL=debug in docker/.env-local for detailed Superset logs
|
||||
# -----------------------------------------------------------------------
|
||||
@@ -156,37 +150,6 @@ services:
|
||||
required: false
|
||||
volumes: *superset-volumes
|
||||
|
||||
superset-mcp-light:
|
||||
profiles:
|
||||
- mcp
|
||||
build:
|
||||
<<: *common-build
|
||||
command: ["/app/docker/docker-bootstrap.sh", "mcp"]
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:${MCP_PORT:-5008}:5008" # Parameterized port
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
user: *superset-user
|
||||
depends_on:
|
||||
superset-init-light:
|
||||
condition: service_completed_successfully
|
||||
volumes: *superset-volumes
|
||||
env_file:
|
||||
- path: docker/.env # default
|
||||
required: true
|
||||
- path: docker/.env-local # optional override
|
||||
required: false
|
||||
environment:
|
||||
# Override DB connection for light service
|
||||
DATABASE_HOST: db-light
|
||||
DATABASE_DB: superset_light
|
||||
POSTGRES_DB: superset_light
|
||||
# Use light-specific config that disables Redis
|
||||
SUPERSET_CONFIG_PATH: /app/docker/pythonpath_dev/superset_config_docker_light.py
|
||||
# Enable SQL debugging for MCP if needed
|
||||
SQLALCHEMY_DEBUG: ${MCP_SQL_DEBUG:-false}
|
||||
|
||||
volumes:
|
||||
superset_home_light:
|
||||
external: false
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
# If you choose to use this type of deployment make sure to
|
||||
# create you own docker environment file (docker/.env) with your own
|
||||
# unique random secure passwords and SECRET_KEY.
|
||||
#
|
||||
# For verbose logging during development:
|
||||
# - Set SUPERSET_LOG_LEVEL=debug in docker/.env-local for detailed Superset logs
|
||||
# -----------------------------------------------------------------------
|
||||
x-superset-volumes:
|
||||
&superset-volumes # /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
# If you choose to use this type of deployment make sure to
|
||||
# create you own docker environment file (docker/.env) with your own
|
||||
# unique random secure passwords and SECRET_KEY.
|
||||
#
|
||||
# For verbose logging during development:
|
||||
# - Set SUPERSET_LOG_LEVEL=debug in docker/.env-local for detailed Superset logs
|
||||
# -----------------------------------------------------------------------
|
||||
x-superset-user: &superset-user root
|
||||
x-superset-volumes: &superset-volumes
|
||||
|
||||
@@ -53,12 +53,7 @@ PYTHONPATH=/app/pythonpath:/app/docker/pythonpath_dev
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Development and logging configuration
|
||||
# FLASK_DEBUG: Enables Flask dev features (auto-reload, better error pages) - keep 'true' for development
|
||||
FLASK_DEBUG=true
|
||||
# SUPERSET_LOG_LEVEL: Controls Superset application logging verbosity (debug, info, warning, error, critical)
|
||||
SUPERSET_LOG_LEVEL=info
|
||||
|
||||
SUPERSET_APP_ROOT="/"
|
||||
SUPERSET_ENV=development
|
||||
SUPERSET_LOAD_EXAMPLES=yes
|
||||
@@ -71,3 +66,4 @@ SUPERSET_SECRET_KEY=TEST_NON_DEV_SECRET
|
||||
ENABLE_PLAYWRIGHT=false
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
BUILD_SUPERSET_FRONTEND_IN_DOCKER=true
|
||||
SUPERSET_LOG_LEVEL=info
|
||||
|
||||
@@ -78,10 +78,6 @@ case "${1}" in
|
||||
echo "Starting web app..."
|
||||
/usr/bin/run-server.sh
|
||||
;;
|
||||
mcp)
|
||||
echo "Starting MCP service..."
|
||||
superset mcp run --host 0.0.0.0 --port ${MCP_PORT:-5008} --debug
|
||||
;;
|
||||
*)
|
||||
echo "Unknown Operation!!!"
|
||||
;;
|
||||
|
||||
1
docker/pythonpath_dev/.gitignore
vendored
1
docker/pythonpath_dev/.gitignore
vendored
@@ -20,5 +20,4 @@
|
||||
# DON'T ignore the .gitignore
|
||||
!.gitignore
|
||||
!superset_config.py
|
||||
!superset_config_docker_light.py
|
||||
!superset_config_local.example
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# mypy: disable-error-code="assignment,misc"
|
||||
#
|
||||
# This file is included in the final Docker image and SHOULD be overridden when
|
||||
# deploying the image to prod. Settings configured here are intended for use in local
|
||||
|
||||
@@ -348,7 +348,7 @@ superset init
|
||||
|
||||
# Load some data to play with.
|
||||
# Note: you MUST have previously created an admin user with the username `admin` for this command to work.
|
||||
superset load-examples
|
||||
superset examples load
|
||||
|
||||
# Start the Flask dev web server from inside your virtualenv.
|
||||
# Note that your page may not have CSS at this point.
|
||||
|
||||
@@ -26,14 +26,11 @@ Superset locally is using Docker Compose on a Linux or Mac OSX
|
||||
computer. Superset does not have official support for Windows. It's also the easiest
|
||||
way to launch a fully functioning **development environment** quickly.
|
||||
|
||||
Note that there are 4 major ways we support to run `docker compose`:
|
||||
Note that there are 3 major ways we support to run `docker compose`:
|
||||
|
||||
1. **docker-compose.yml:** for interactive development, where we mount your local folder with the
|
||||
frontend/backend files that you can edit and experience the changes you
|
||||
make in the app in real time
|
||||
1. **docker-compose-light.yml:** a lightweight configuration with minimal services (database,
|
||||
Superset app, and frontend dev server) for development. Uses in-memory caching instead of Redis
|
||||
and is designed for running multiple instances simultaneously
|
||||
1. **docker-compose-non-dev.yml** where we just build a more immutable image based on the
|
||||
local branch and get all the required images running. Changes in the local branch
|
||||
at the time you fire this up will be reflected, but changes to the code
|
||||
@@ -47,7 +44,7 @@ Note that there are 4 major ways we support to run `docker compose`:
|
||||
The `dev` builds include the `psycopg2-binary` required to connect
|
||||
to the Postgres database launched as part of the `docker compose` builds.
|
||||
|
||||
More on these approaches after setting up the requirements for either.
|
||||
More on these two approaches after setting up the requirements for either.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -106,36 +103,13 @@ and help you start fresh. In the context of `docker compose` setting
|
||||
from within docker. This will slow down the startup, but will fix various npm-related issues.
|
||||
:::
|
||||
|
||||
### Option #2 - lightweight development with multiple instances
|
||||
|
||||
For a lighter development setup that uses fewer resources and supports running multiple instances:
|
||||
|
||||
```bash
|
||||
# Single lightweight instance (default port 9001)
|
||||
docker compose -f docker-compose-light.yml up
|
||||
|
||||
# Multiple instances with different ports
|
||||
NODE_PORT=9001 docker compose -p superset-1 -f docker-compose-light.yml up
|
||||
NODE_PORT=9002 docker compose -p superset-2 -f docker-compose-light.yml up
|
||||
NODE_PORT=9003 docker compose -p superset-3 -f docker-compose-light.yml up
|
||||
```
|
||||
|
||||
This configuration includes:
|
||||
- PostgreSQL database (internal network only)
|
||||
- Superset application server
|
||||
- Frontend development server with webpack hot reloading
|
||||
- In-memory caching (no Redis)
|
||||
- Isolated volumes and networks per instance
|
||||
|
||||
Access each instance at `http://localhost:{NODE_PORT}` (e.g., `http://localhost:9001`).
|
||||
|
||||
### Option #3 - build a set of immutable images from the local branch
|
||||
### Option #2 - build a set of immutable images from the local branch
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose-non-dev.yml up
|
||||
```
|
||||
|
||||
### Option #4 - boot up an official release
|
||||
### Option #3 - boot up an official release
|
||||
|
||||
```bash
|
||||
# Set the version you want to run
|
||||
|
||||
@@ -151,7 +151,7 @@ Finish installing by running through the following commands:
|
||||
superset fab create-admin
|
||||
|
||||
# Load some data to play with
|
||||
superset load_examples
|
||||
superset examples load
|
||||
|
||||
# Create default roles and permissions
|
||||
superset init
|
||||
|
||||
@@ -1,741 +0,0 @@
|
||||
---
|
||||
title: API Reference
|
||||
sidebar_position: 3
|
||||
version: 1
|
||||
---
|
||||
|
||||
# MCP Tools API Reference
|
||||
|
||||
Complete reference for all 16 MCP tools with request/response examples.
|
||||
|
||||
> 🚀 **First time here?** Start with [Dashboard Tools](#dashboard-tools) or [Chart Tools](#chart-tools) to see the most commonly used features.
|
||||
>
|
||||
> 🔐 **Need authentication?** See the [Authentication Guide](./authentication) for JWT setup.
|
||||
>
|
||||
> 🔧 **Want to add tools?** Check the [Development Guide](./development#adding-new-tools) for step-by-step instructions.
|
||||
|
||||
## Dashboard Tools
|
||||
|
||||
### list_dashboards
|
||||
|
||||
List dashboards with search, filtering, and pagination support.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"search": "sales", // Optional: Search term
|
||||
"filters": [ // Optional: Advanced filters
|
||||
{
|
||||
"col": "published",
|
||||
"opr": "eq",
|
||||
"value": true
|
||||
}
|
||||
],
|
||||
"page": 1, // Optional: Page number (default: 1)
|
||||
"page_size": 20, // Optional: Items per page (default: 20)
|
||||
"select_columns": [ // Optional: Specific columns
|
||||
"id", "dashboard_title", "uuid"
|
||||
],
|
||||
"use_cache": true // Optional: Use cached data (default: true)
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"dashboards": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"dashboard_title": "Sales Performance",
|
||||
"url": "/superset/dashboard/1/",
|
||||
"published": true,
|
||||
"owners": ["admin"],
|
||||
"created_on": "2024-01-15T10:30:00Z",
|
||||
"changed_on": "2024-01-20T14:15:00Z"
|
||||
}
|
||||
],
|
||||
"total_count": 45,
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"cache_status": {
|
||||
"cache_hit": true,
|
||||
"cache_age_seconds": 300
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### get_dashboard_info
|
||||
|
||||
Get detailed information about a specific dashboard.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"identifier": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", // ID, UUID, or slug
|
||||
"use_cache": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"dashboard_id": 1,
|
||||
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"dashboard_title": "Sales Performance Dashboard",
|
||||
"slug": "sales-performance",
|
||||
"url": "/superset/dashboard/1/",
|
||||
"published": true,
|
||||
"owners": ["admin", "analyst"],
|
||||
"roles": ["Sales Team"],
|
||||
"charts": [
|
||||
{
|
||||
"id": 10,
|
||||
"slice_name": "Monthly Revenue",
|
||||
"viz_type": "line"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"slice_name": "Regional Sales",
|
||||
"viz_type": "bar"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"column": "region",
|
||||
"type": "select"
|
||||
}
|
||||
],
|
||||
"created_on": "2024-01-15T10:30:00Z",
|
||||
"changed_on": "2024-01-20T14:15:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### generate_dashboard
|
||||
|
||||
Create a new dashboard with multiple charts.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"chart_ids": [10, 11, 12, 13],
|
||||
"dashboard_title": "Q4 Performance Dashboard",
|
||||
"description": "Quarterly performance metrics and KPIs",
|
||||
"published": true,
|
||||
"layout_type": "grid" // Optional: "grid" or "tabs"
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"dashboard_id": 25,
|
||||
"uuid": "new-dash-uuid-here",
|
||||
"dashboard_title": "Q4 Performance Dashboard",
|
||||
"url": "/superset/dashboard/25/",
|
||||
"charts_added": 4,
|
||||
"layout": {
|
||||
"type": "grid",
|
||||
"columns": 2,
|
||||
"rows": 2
|
||||
},
|
||||
"created_on": "2024-01-25T16:45:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Chart Tools
|
||||
|
||||
### list_charts
|
||||
|
||||
List charts with advanced filtering and search capabilities.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"search": "revenue",
|
||||
"filters": [
|
||||
{
|
||||
"col": "viz_type",
|
||||
"opr": "in",
|
||||
"value": ["line", "bar", "area"]
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 25,
|
||||
"select_columns": ["id", "slice_name", "viz_type", "uuid"],
|
||||
"use_cache": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"charts": [
|
||||
{
|
||||
"id": 10,
|
||||
"uuid": "chart-uuid-1",
|
||||
"slice_name": "Monthly Revenue Trend",
|
||||
"viz_type": "line",
|
||||
"datasource_name": "sales_data",
|
||||
"owners": ["admin"],
|
||||
"created_on": "2024-01-10T09:15:00Z"
|
||||
}
|
||||
],
|
||||
"total_count": 125,
|
||||
"page": 1,
|
||||
"page_size": 25
|
||||
}
|
||||
```
|
||||
|
||||
### get_chart_info
|
||||
|
||||
Get comprehensive chart information including configuration.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"identifier": 10, // ID or UUID
|
||||
"include_form_data": true, // Include chart configuration
|
||||
"use_cache": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"chart_id": 10,
|
||||
"uuid": "chart-uuid-1",
|
||||
"slice_name": "Monthly Revenue Trend",
|
||||
"viz_type": "line",
|
||||
"datasource_id": 5,
|
||||
"datasource_name": "sales_data",
|
||||
"datasource_type": "table",
|
||||
"form_data": {
|
||||
"viz_type": "line",
|
||||
"x_axis": "month",
|
||||
"metrics": ["sum__revenue"],
|
||||
"time_range": "Last 12 months"
|
||||
},
|
||||
"query_context": {
|
||||
"datasource": {"id": 5, "type": "table"},
|
||||
"queries": [{"columns": [], "metrics": ["sum__revenue"]}]
|
||||
},
|
||||
"explore_url": "/superset/explore/?form_data=%7B%22slice_id%22%3A10%7D",
|
||||
"owners": ["admin"],
|
||||
"created_on": "2024-01-10T09:15:00Z",
|
||||
"changed_on": "2024-01-15T11:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### generate_chart
|
||||
|
||||
Create a new chart with specified configuration.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"dataset_id": "5",
|
||||
"config": {
|
||||
"chart_type": "xy",
|
||||
"x": {"name": "month", "label": "Month"},
|
||||
"y": [
|
||||
{
|
||||
"name": "revenue",
|
||||
"aggregate": "SUM",
|
||||
"label": "Total Revenue"
|
||||
},
|
||||
{
|
||||
"name": "orders",
|
||||
"aggregate": "COUNT",
|
||||
"label": "Order Count"
|
||||
}
|
||||
],
|
||||
"kind": "line",
|
||||
"x_axis": {
|
||||
"title": "Month",
|
||||
"format": "smart_date"
|
||||
},
|
||||
"y_axis": {
|
||||
"title": "Revenue ($)",
|
||||
"format": "$,.0f"
|
||||
},
|
||||
"legend": {
|
||||
"show": true,
|
||||
"position": "top"
|
||||
}
|
||||
},
|
||||
"slice_name": "Revenue and Orders Trend",
|
||||
"description": "Monthly revenue and order count comparison",
|
||||
"save_chart": true,
|
||||
"generate_preview": true,
|
||||
"preview_formats": ["url", "ascii"]
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"chart_id": 45,
|
||||
"uuid": "new-chart-uuid",
|
||||
"slice_name": "Revenue and Orders Trend",
|
||||
"viz_type": "echarts_timeseries_line",
|
||||
"datasource_id": 5,
|
||||
"explore_url": "/superset/explore/?form_data=%7B%22slice_id%22%3A45%7D",
|
||||
"query_executed": true,
|
||||
"query_result": {
|
||||
"status": "success",
|
||||
"row_count": 12,
|
||||
"execution_time": 0.145
|
||||
},
|
||||
"preview": {
|
||||
"url": {
|
||||
"preview_url": "http://localhost:5008/screenshot/chart/45.png",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
},
|
||||
"ascii": {
|
||||
"ascii_content": "Revenue Trend\n==============\nJan |████████████████ $125K\nFeb |██████████████████ $140K\n...",
|
||||
"width": 80,
|
||||
"height": 20
|
||||
}
|
||||
},
|
||||
"created_on": "2024-01-25T14:20:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### get_chart_data
|
||||
|
||||
Export chart data in multiple formats.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"identifier": 10,
|
||||
"format": "json", // "json", "csv", "excel"
|
||||
"limit": 1000, // Optional: Row limit
|
||||
"offset": 0, // Optional: Row offset
|
||||
"filters": [ // Optional: Additional filters
|
||||
{
|
||||
"column": "region",
|
||||
"op": "=",
|
||||
"value": "US"
|
||||
}
|
||||
],
|
||||
"use_cache": true,
|
||||
"force_refresh": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"month": "2024-01",
|
||||
"revenue": 125000,
|
||||
"orders": 450
|
||||
},
|
||||
{
|
||||
"month": "2024-02",
|
||||
"revenue": 140000,
|
||||
"orders": 520
|
||||
}
|
||||
],
|
||||
"total_rows": 12,
|
||||
"columns": [
|
||||
{"name": "month", "type": "DATE"},
|
||||
{"name": "revenue", "type": "BIGINT"},
|
||||
{"name": "orders", "type": "BIGINT"}
|
||||
],
|
||||
"query": {
|
||||
"sql": "SELECT month, SUM(revenue) as revenue, COUNT(*) as orders FROM sales_data GROUP BY month ORDER BY month",
|
||||
"execution_time": 0.089
|
||||
},
|
||||
"cache_status": {
|
||||
"cache_hit": false,
|
||||
"cache_type": "query",
|
||||
"refreshed": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### get_chart_preview
|
||||
|
||||
Generate chart previews in multiple formats.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"identifier": 10,
|
||||
"format": "url", // "url", "base64", "ascii", "table"
|
||||
"width": 800, // For image formats
|
||||
"height": 600, // For image formats
|
||||
"ascii_width": 80, // For ASCII format
|
||||
"ascii_height": 20, // For ASCII format
|
||||
"use_cache": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response Examples:**
|
||||
|
||||
**URL Format:**
|
||||
```json
|
||||
{
|
||||
"format": "url",
|
||||
"preview_url": "http://localhost:5008/screenshot/chart/10.png",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"supports_interaction": false,
|
||||
"expires_at": "2024-01-26T14:20:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**ASCII Format:**
|
||||
```json
|
||||
{
|
||||
"format": "ascii",
|
||||
"ascii_content": "Monthly Revenue Trend\n=====================\n\nJan |████████████████████ $125K\nFeb |██████████████████████ $140K\nMar |███████████████████ $135K\nApr |█████████████████████████ $155K\n\nRange: $125K to $155K\n▁▃▂▅▇▆▄▃▂▄▅▆▇▅▃▂",
|
||||
"width": 80,
|
||||
"height": 20,
|
||||
"supports_color": false
|
||||
}
|
||||
```
|
||||
|
||||
**Table Format:**
|
||||
```json
|
||||
{
|
||||
"format": "table",
|
||||
"table_data": "Monthly Revenue Data\n====================\n\nMonth | Revenue | Orders\n---------|----------|--------\nJan 2024 | $125,000 | 450\nFeb 2024 | $140,000 | 520\nMar 2024 | $135,000 | 495\n\nTotal: 12 rows × 3 columns",
|
||||
"row_count": 12,
|
||||
"supports_sorting": true
|
||||
}
|
||||
```
|
||||
|
||||
## Dataset Tools
|
||||
|
||||
### list_datasets
|
||||
|
||||
List available datasets with columns and metrics.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"search": "sales",
|
||||
"filters": [
|
||||
{
|
||||
"col": "is_active",
|
||||
"opr": "eq",
|
||||
"value": true
|
||||
}
|
||||
],
|
||||
"include_columns": true, // Include column metadata
|
||||
"include_metrics": true, // Include metric metadata
|
||||
"page": 1,
|
||||
"page_size": 15
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"datasets": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "dataset-uuid-1",
|
||||
"table_name": "sales_data",
|
||||
"database_name": "main_warehouse",
|
||||
"schema": "public",
|
||||
"owners": ["admin"],
|
||||
"columns": [
|
||||
{
|
||||
"column_name": "region",
|
||||
"type": "VARCHAR",
|
||||
"is_active": true,
|
||||
"is_dttm": false
|
||||
},
|
||||
{
|
||||
"column_name": "revenue",
|
||||
"type": "DECIMAL",
|
||||
"is_active": true,
|
||||
"is_dttm": false
|
||||
}
|
||||
],
|
||||
"metrics": [
|
||||
{
|
||||
"metric_name": "sum__revenue",
|
||||
"expression": "SUM(revenue)",
|
||||
"metric_type": "sum"
|
||||
}
|
||||
],
|
||||
"created_on": "2024-01-05T08:00:00Z"
|
||||
}
|
||||
],
|
||||
"total_count": 23,
|
||||
"page": 1,
|
||||
"page_size": 15
|
||||
}
|
||||
```
|
||||
|
||||
### get_dataset_info
|
||||
|
||||
Get detailed dataset information with full column/metric metadata.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"identifier": "dataset-uuid-1", // ID or UUID
|
||||
"include_columns": true,
|
||||
"include_metrics": true,
|
||||
"use_cache": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"dataset_id": 1,
|
||||
"uuid": "dataset-uuid-1",
|
||||
"table_name": "sales_data",
|
||||
"database_name": "main_warehouse",
|
||||
"database_id": 1,
|
||||
"schema": "public",
|
||||
"sql": null,
|
||||
"is_active": true,
|
||||
"owners": ["admin", "data_team"],
|
||||
"columns": [
|
||||
{
|
||||
"id": 101,
|
||||
"column_name": "region",
|
||||
"type": "VARCHAR",
|
||||
"is_active": true,
|
||||
"is_dttm": false,
|
||||
"groupby": true,
|
||||
"filterable": true,
|
||||
"description": "Geographic region"
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"column_name": "order_date",
|
||||
"type": "DATE",
|
||||
"is_active": true,
|
||||
"is_dttm": true,
|
||||
"groupby": true,
|
||||
"filterable": true
|
||||
}
|
||||
],
|
||||
"metrics": [
|
||||
{
|
||||
"id": 201,
|
||||
"metric_name": "sum__revenue",
|
||||
"expression": "SUM(revenue)",
|
||||
"metric_type": "sum",
|
||||
"is_active": true,
|
||||
"description": "Total revenue"
|
||||
}
|
||||
],
|
||||
"created_on": "2024-01-05T08:00:00Z",
|
||||
"changed_on": "2024-01-18T12:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## System Tools
|
||||
|
||||
### get_superset_instance_info
|
||||
|
||||
Get Superset instance information and statistics.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"include_statistics": true, // Include usage statistics
|
||||
"include_tools": true, // Include available MCP tools
|
||||
"use_cache": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"version": "4.1.0",
|
||||
"build": "apache-superset-4.1.0",
|
||||
"mcp_service_version": "1.0.0",
|
||||
"authentication": {
|
||||
"enabled": true,
|
||||
"type": "jwt_bearer",
|
||||
"required_scopes": ["dashboard:read", "chart:read"]
|
||||
},
|
||||
"statistics": {
|
||||
"dashboards": {
|
||||
"total": 45,
|
||||
"published": 32
|
||||
},
|
||||
"charts": {
|
||||
"total": 125,
|
||||
"by_viz_type": {
|
||||
"line": 35,
|
||||
"bar": 28,
|
||||
"table": 42,
|
||||
"pie": 20
|
||||
}
|
||||
},
|
||||
"datasets": {
|
||||
"total": 23,
|
||||
"active": 18
|
||||
},
|
||||
"users": {
|
||||
"total": 15,
|
||||
"active": 12
|
||||
}
|
||||
},
|
||||
"mcp_tools": [
|
||||
{
|
||||
"name": "list_dashboards",
|
||||
"description": "List dashboards with search and filtering",
|
||||
"category": "dashboard"
|
||||
},
|
||||
{
|
||||
"name": "generate_chart",
|
||||
"description": "Create new charts programmatically",
|
||||
"category": "chart"
|
||||
}
|
||||
],
|
||||
"database_connections": [
|
||||
{
|
||||
"id": 1,
|
||||
"database_name": "main_warehouse",
|
||||
"backend": "postgresql",
|
||||
"status": "healthy"
|
||||
}
|
||||
],
|
||||
"cache_status": {
|
||||
"enabled": true,
|
||||
"backend": "redis",
|
||||
"hit_rate": 0.85
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### generate_explore_link
|
||||
|
||||
Generate Superset explore URLs with pre-configured chart settings.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"dataset_id": "1",
|
||||
"chart_config": {
|
||||
"viz_type": "line",
|
||||
"x_axis": "month",
|
||||
"metrics": ["sum__revenue"],
|
||||
"time_range": "Last 6 months"
|
||||
},
|
||||
"title": "Revenue Analysis",
|
||||
"cache_form_data": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"explore_url": "/superset/explore/?form_data_key=abc123def456",
|
||||
"full_url": "http://localhost:8088/superset/explore/?form_data_key=abc123def456",
|
||||
"form_data_key": "abc123def456",
|
||||
"expires_at": "2024-01-26T16:45:00Z",
|
||||
"chart_config": {
|
||||
"viz_type": "line",
|
||||
"datasource": "1__table",
|
||||
"x_axis": "month",
|
||||
"metrics": ["sum__revenue"],
|
||||
"time_range": "Last 6 months"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## SQL Lab Tools
|
||||
|
||||
### open_sql_lab_with_context
|
||||
|
||||
Open SQL Lab with pre-configured database, schema, and SQL.
|
||||
|
||||
**Request Schema:**
|
||||
```json
|
||||
{
|
||||
"database_connection_id": 1,
|
||||
"schema": "public",
|
||||
"dataset_in_context": "sales_data",
|
||||
"sql": "SELECT region, SUM(revenue) as total_revenue\nFROM sales_data \nWHERE order_date >= '2024-01-01'\nGROUP BY region\nORDER BY total_revenue DESC",
|
||||
"title": "Regional Sales Analysis"
|
||||
}
|
||||
```
|
||||
|
||||
**Response Example:**
|
||||
```json
|
||||
{
|
||||
"sql_lab_url": "/superset/sqllab/?dbid=1&schema=public&sql_template=encoded_sql_here",
|
||||
"full_url": "http://localhost:8088/superset/sqllab/?dbid=1&schema=public&sql_template=encoded_sql_here",
|
||||
"database_connection": {
|
||||
"id": 1,
|
||||
"database_name": "main_warehouse",
|
||||
"backend": "postgresql"
|
||||
},
|
||||
"schema": "public",
|
||||
"sql_template": "SELECT region, SUM(revenue) as total_revenue...",
|
||||
"context": {
|
||||
"dataset": "sales_data",
|
||||
"title": "Regional Sales Analysis"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
All tools can return error responses with this structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Chart not found with identifier: 999",
|
||||
"error_type": "NotFound",
|
||||
"suggestions": [
|
||||
"Verify the chart ID exists",
|
||||
"Check if you have permission to access this chart",
|
||||
"Try using the chart UUID instead of ID"
|
||||
],
|
||||
"details": {
|
||||
"identifier": 999,
|
||||
"identifier_type": "id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cache Status
|
||||
|
||||
Many responses include cache status information:
|
||||
|
||||
```json
|
||||
{
|
||||
"cache_status": {
|
||||
"cache_hit": true, // Data served from cache
|
||||
"cache_type": "query", // Type: query, metadata, form_data
|
||||
"cache_age_seconds": 300, // Age of cached data
|
||||
"refreshed": false // Whether cache was refreshed
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This API reference provides complete documentation for integrating with the Superset MCP service, including all request schemas, response formats, and error handling patterns.
|
||||
|
||||
## What's Next?
|
||||
|
||||
### 🔐 **Ready for Production?**
|
||||
Set up authentication and security with the [Authentication Guide](./authentication).
|
||||
|
||||
### 🔧 **Want to Add More Tools?**
|
||||
Learn how to extend the MCP service in the [Development Guide](./development).
|
||||
|
||||
### 🏗️ **Need Architecture Details?**
|
||||
Understand the system design in the [Architecture Overview](./architecture).
|
||||
|
||||
### 🏢 **Enterprise Features?**
|
||||
Explore advanced capabilities in the [Preset Integration Guide](./preset-integration).
|
||||
|
||||
> 📖 **Back to Documentation Index**: [MCP Service](./intro)
|
||||
@@ -1,191 +0,0 @@
|
||||
---
|
||||
title: Architecture Overview
|
||||
sidebar_position: 5
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Architecture Overview
|
||||
|
||||
The Superset Model Context Protocol (MCP) service provides a modular, schema-driven interface for programmatic access to Superset dashboards, charts, datasets, and instance metadata. Built on FastMCP for LLM agents and automation tools.
|
||||
|
||||
**Status:** Phase 1 Complete. Core functionality stable, authentication production-ready. See [SIP-171](https://github.com/apache/superset/issues/33870) for roadmap.
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### Tool Structure
|
||||
- **16 MCP tools** organized by domain: `dashboard/`, `chart/`, `dataset/`, `system/`
|
||||
- All tools decorated with `@mcp.tool` and `@mcp_auth_hook`
|
||||
- **Import inside functions**: All Superset DAOs/commands imported in function body to ensure proper app context
|
||||
- Pydantic v2 schemas with LLM/OpenAPI-compatible field descriptions
|
||||
|
||||
### Request Schema Pattern
|
||||
Eliminates LLM parameter validation issues using structured request objects:
|
||||
```python
|
||||
# New approach - single request object
|
||||
get_dataset_info(request={"identifier": 123}) # ID
|
||||
get_dataset_info(request={"identifier": "uuid-string"}) # UUID
|
||||
|
||||
# Old approach - replaced
|
||||
get_dataset_info(dataset_id=123)
|
||||
```
|
||||
|
||||
### Multi-Identifier Support
|
||||
- **Charts/Datasets**: ID (numeric) or UUID (string)
|
||||
- **Dashboards**: ID (numeric), UUID (string), or slug (string)
|
||||
- Validation prevents conflicting parameters (search + filters)
|
||||
|
||||
## Available Tools
|
||||
|
||||
### Dashboard Tools (5)
|
||||
- `list_dashboards` - List with search/filters/pagination
|
||||
- `get_dashboard_info` - Get by ID/UUID/slug
|
||||
- `get_dashboard_available_filters` - Discover filterable columns
|
||||
- `generate_dashboard` - Create dashboards with multiple charts
|
||||
- `add_chart_to_existing_dashboard` - Add charts to existing dashboards
|
||||
|
||||
### Chart Tools (8)
|
||||
- `list_charts` - List with search/filters/pagination
|
||||
- `get_chart_info` - Get by ID/UUID
|
||||
- `get_chart_available_filters` - Discover filterable columns
|
||||
- `generate_chart` - Create charts (table, line, bar, area, scatter)
|
||||
- `update_chart` - Update saved charts
|
||||
- `update_chart_preview` - Update cached previews
|
||||
- `get_chart_data` - Export data (JSON/CSV/Excel)
|
||||
- `get_chart_preview` - Screenshots, ASCII art, table previews
|
||||
|
||||
### Dataset Tools (3)
|
||||
- `list_datasets` - List with columns/metrics
|
||||
- `get_dataset_info` - Get by ID/UUID with metadata
|
||||
- `get_dataset_available_filters` - Discover filterable columns
|
||||
|
||||
### System Tools (2)
|
||||
- `get_superset_instance_info` - Instance statistics and version
|
||||
- `generate_explore_link` - Generate chart exploration URLs
|
||||
|
||||
### SQL Lab Tools (1)
|
||||
- `open_sql_lab_with_context` - Pre-configured SQL Lab sessions
|
||||
|
||||
## Authentication & Security
|
||||
|
||||
### JWT Bearer Authentication
|
||||
Production-ready authentication with configurable factory pattern:
|
||||
```python
|
||||
# In superset_config.py
|
||||
MCP_AUTH_ENABLED = True
|
||||
MCP_JWKS_URI = "https://auth.company.com/.well-known/jwks.json"
|
||||
MCP_JWT_ISSUER = "https://auth.company.com/"
|
||||
MCP_JWT_AUDIENCE = "superset-mcp-api"
|
||||
```
|
||||
|
||||
### Scope-Based Authorization
|
||||
| Tool Category | Required Scope |
|
||||
|---------------|----------------|
|
||||
| Dashboard ops | `dashboard:read` |
|
||||
| Chart ops | `chart:read` / `chart:write` |
|
||||
| Dataset ops | `dataset:read` |
|
||||
| System ops | `instance:read` |
|
||||
|
||||
### Audit Logging
|
||||
All operations logged with MCP context:
|
||||
- User impersonation tracking
|
||||
- Tool execution details
|
||||
- Sanitized payloads (sensitive data redacted)
|
||||
|
||||
## Cache Control
|
||||
|
||||
Leverages Superset's existing cache layers with comprehensive control:
|
||||
|
||||
### Cache Types
|
||||
1. **Query Result Cache** - Database query results
|
||||
2. **Metadata Cache** - Table schemas, columns, metrics
|
||||
3. **Form Data Cache** - Chart configurations
|
||||
4. **Dashboard Cache** - Rendered components
|
||||
|
||||
### Cache Parameters
|
||||
Tools support cache control through request schemas:
|
||||
- `use_cache`: Enable/disable caching (default: true)
|
||||
- `force_refresh`: Force cache refresh (default: false)
|
||||
- `cache_timeout`: Override timeout in seconds
|
||||
- `refresh_metadata`: Force metadata refresh
|
||||
|
||||
### Cache Status Reporting
|
||||
```json
|
||||
{
|
||||
"cache_status": {
|
||||
"cache_hit": true,
|
||||
"cache_type": "query",
|
||||
"cache_age_seconds": 300,
|
||||
"refreshed": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tool Abstractions
|
||||
|
||||
### Generic Base Classes
|
||||
- **ModelListTool**: Handles list/search/filter operations with pagination
|
||||
- **ModelGetInfoTool**: Single object retrieval by multiple identifier types
|
||||
- **ModelGetAvailableFiltersTool**: Returns filterable columns/operators
|
||||
|
||||
### Implementation Pattern
|
||||
```python
|
||||
@mcp.tool
|
||||
@mcp_auth_hook
|
||||
def my_tool(request: MyRequest) -> MyResponse:
|
||||
# Import Superset modules inside function
|
||||
from superset.daos.dashboard import DashboardDAO
|
||||
from superset.commands.chart.create import CreateChartCommand
|
||||
|
||||
# Tool implementation
|
||||
return response
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### URL Configuration
|
||||
Centralized URL management for consistent link generation:
|
||||
```python
|
||||
# In superset_config.py
|
||||
SUPERSET_WEBSERVER_ADDRESS = "http://localhost:8088" # Development
|
||||
SUPERSET_WEBSERVER_ADDRESS = "https://superset.company.com" # Production
|
||||
```
|
||||
|
||||
### Schema Design Principles
|
||||
- **Minimal columns** in list responses
|
||||
- **Optional fields** in info schemas for missing data handling
|
||||
- **Null exclusion** for cleaner JSON responses
|
||||
- **Type safety** with clear Pydantic validation
|
||||
|
||||
## Adding New Tools
|
||||
|
||||
1. **Choose domain folder**: `dashboard/`, `chart/`, `dataset/`, or `system/`
|
||||
2. **Define schemas**: Use Pydantic with field descriptions
|
||||
3. **Implement tool**:
|
||||
- Decorate with `@mcp.tool` and `@mcp_auth_hook`
|
||||
- Import Superset modules inside function body
|
||||
- Use generic abstractions where applicable
|
||||
4. **Register**: Add to appropriate `__init__.py`
|
||||
5. **Test**: Add unit tests in `tests/unit_tests/mcp_service/`
|
||||
|
||||
## Current Status
|
||||
|
||||
### ✅ Phase 1 Complete
|
||||
- FastMCP server with CLI
|
||||
- JWT authentication with RBAC
|
||||
- All 16 core tools implemented
|
||||
- Request schema pattern
|
||||
- Cache control system
|
||||
- Audit logging
|
||||
- 194+ unit tests
|
||||
|
||||
### 🎯 Future Enhancements
|
||||
- Demo notebooks and video examples
|
||||
- OAuth integration for user impersonation
|
||||
- Enhanced chart rendering formats
|
||||
- Advanced security features
|
||||
|
||||
**Production Ready**: Core functionality stable with comprehensive testing and authentication.
|
||||
|
||||
---
|
||||
|
||||
For setup and usage, see the [MCP Service overview](./intro).
|
||||
@@ -1,434 +0,0 @@
|
||||
---
|
||||
title: Authentication & Security
|
||||
sidebar_position: 4
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Authentication & Security
|
||||
|
||||
The MCP service provides enterprise-grade JWT Bearer authentication with flexible configuration options and comprehensive security controls.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Development Mode (Default)
|
||||
|
||||
:::tip
|
||||
Authentication is **disabled by default** for local development - no configuration needed.
|
||||
:::
|
||||
|
||||
```bash
|
||||
# No configuration needed - service runs without authentication
|
||||
superset mcp run --port 5008 --debug
|
||||
```
|
||||
|
||||
### Production Mode
|
||||
|
||||
:::warning
|
||||
Always enable authentication for production deployments to secure your Superset instance.
|
||||
:::
|
||||
|
||||
Enable JWT authentication in your Superset configuration:
|
||||
|
||||
```python
|
||||
# In superset_config.py
|
||||
MCP_AUTH_ENABLED = True
|
||||
MCP_JWKS_URI = "https://auth.company.com/.well-known/jwks.json"
|
||||
MCP_JWT_ISSUER = "https://auth.company.com/"
|
||||
MCP_JWT_AUDIENCE = "superset-mcp-api"
|
||||
MCP_REQUIRED_SCOPES = ["dashboard:read", "chart:read", "dataset:read"]
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Option 1: Simple Configuration
|
||||
|
||||
Add to your `superset_config.py`:
|
||||
|
||||
```python
|
||||
# Enable authentication
|
||||
MCP_AUTH_ENABLED = True
|
||||
|
||||
# JWT settings
|
||||
MCP_JWKS_URI = "https://auth.company.com/.well-known/jwks.json"
|
||||
MCP_JWT_ISSUER = "https://auth.company.com/"
|
||||
MCP_JWT_AUDIENCE = "superset-mcp-api"
|
||||
MCP_REQUIRED_SCOPES = ["dashboard:read", "chart:read"]
|
||||
|
||||
# Optional: User resolution
|
||||
MCP_JWT_USER_CLAIM = "sub" # JWT claim for username (default: "sub")
|
||||
MCP_JWT_EMAIL_CLAIM = "email" # JWT claim for email (default: "email")
|
||||
MCP_FALLBACK_USER = "admin" # Fallback user if JWT user not found
|
||||
```
|
||||
|
||||
### Option 2: Custom Factory
|
||||
|
||||
For advanced authentication requirements:
|
||||
|
||||
```python
|
||||
def create_custom_mcp_auth(app):
|
||||
"""Custom auth factory for enterprise environments."""
|
||||
from fastmcp.server.auth.providers.bearer import BearerAuthProvider
|
||||
|
||||
return BearerAuthProvider(
|
||||
jwks_uri=app.config["MCP_JWKS_URI"],
|
||||
issuer=app.config["MCP_JWT_ISSUER"],
|
||||
audience=app.config["MCP_JWT_AUDIENCE"],
|
||||
required_scopes=app.config.get("MCP_REQUIRED_SCOPES", []),
|
||||
user_resolver=custom_user_resolver,
|
||||
cache_ttl=300 # Cache JWKS for 5 minutes
|
||||
)
|
||||
|
||||
MCP_AUTH_FACTORY = create_custom_mcp_auth
|
||||
```
|
||||
|
||||
### Option 3: Environment Variables
|
||||
|
||||
For containerized deployments:
|
||||
|
||||
```bash
|
||||
# Environment variables
|
||||
export MCP_AUTH_ENABLED=true
|
||||
export MCP_JWKS_URI=https://auth.company.com/.well-known/jwks.json
|
||||
export MCP_JWT_ISSUER=https://auth.company.com/
|
||||
export MCP_JWT_AUDIENCE=superset-mcp-api
|
||||
export MCP_REQUIRED_SCOPES=dashboard:read,chart:read,dataset:read
|
||||
```
|
||||
|
||||
## Identity Provider Integration
|
||||
|
||||
### Auth0
|
||||
|
||||
```python
|
||||
# Auth0 configuration
|
||||
MCP_JWKS_URI = "https://your-tenant.auth0.com/.well-known/jwks.json"
|
||||
MCP_JWT_ISSUER = "https://your-tenant.auth0.com/"
|
||||
MCP_JWT_AUDIENCE = "superset-mcp-api"
|
||||
```
|
||||
|
||||
### Okta
|
||||
|
||||
```python
|
||||
# Okta configuration
|
||||
MCP_JWKS_URI = "https://your-org.okta.com/oauth2/default/v1/keys"
|
||||
MCP_JWT_ISSUER = "https://your-org.okta.com/oauth2/default"
|
||||
MCP_JWT_AUDIENCE = "api://superset-mcp"
|
||||
```
|
||||
|
||||
### AWS Cognito
|
||||
|
||||
```python
|
||||
# Cognito configuration
|
||||
MCP_JWKS_URI = "https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json"
|
||||
MCP_JWT_ISSUER = "https://cognito-idp.{region}.amazonaws.com/{userPoolId}"
|
||||
MCP_JWT_AUDIENCE = "your-app-client-id"
|
||||
```
|
||||
|
||||
### Azure AD
|
||||
|
||||
```python
|
||||
# Azure AD configuration
|
||||
MCP_JWKS_URI = "https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys"
|
||||
MCP_JWT_ISSUER = "https://login.microsoftonline.com/{tenant}/v2.0"
|
||||
MCP_JWT_AUDIENCE = "api://superset-mcp"
|
||||
```
|
||||
|
||||
## Scope-Based Authorization
|
||||
|
||||
### Standard Scopes
|
||||
|
||||
The MCP service defines these standard scopes:
|
||||
|
||||
| Scope | Description | Required For |
|
||||
|-------|-------------|--------------|
|
||||
| `dashboard:read` | Read dashboard information | `list_dashboards`, `get_dashboard_info` |
|
||||
| `dashboard:write` | Create/modify dashboards | `generate_dashboard`, `add_chart_to_existing_dashboard` |
|
||||
| `chart:read` | Read chart information | `list_charts`, `get_chart_info`, `get_chart_data` |
|
||||
| `chart:write` | Create/modify charts | `generate_chart`, `update_chart` |
|
||||
| `dataset:read` | Read dataset information | `list_datasets`, `get_dataset_info` |
|
||||
| `instance:read` | Read instance information | `get_superset_instance_info` |
|
||||
|
||||
### Custom Scopes
|
||||
|
||||
Define custom scopes for specific use cases:
|
||||
|
||||
```python
|
||||
# Custom scope definitions
|
||||
CUSTOM_MCP_SCOPES = {
|
||||
"analytics:export": "Export analytical data",
|
||||
"reports:generate": "Generate automated reports",
|
||||
"admin:config": "Access administrative configuration"
|
||||
}
|
||||
|
||||
# Map tools to custom scopes
|
||||
def get_custom_required_scopes(tool_name: str) -> List[str]:
|
||||
scope_map = {
|
||||
"get_chart_data": ["chart:read", "analytics:export"],
|
||||
"generate_dashboard": ["dashboard:write", "reports:generate"],
|
||||
"get_superset_instance_info": ["instance:read", "admin:config"]
|
||||
}
|
||||
return scope_map.get(tool_name, [])
|
||||
|
||||
MCP_SCOPE_RESOLVER = get_custom_required_scopes
|
||||
```
|
||||
|
||||
## JWT Token Format
|
||||
|
||||
### Required Claims
|
||||
|
||||
Your JWT tokens must include these standard claims:
|
||||
|
||||
```json
|
||||
{
|
||||
"iss": "https://auth.company.com/", // Issuer
|
||||
"aud": "superset-mcp-api", // Audience
|
||||
"sub": "user@company.com", // Subject (username)
|
||||
"exp": 1704118800, // Expiration timestamp
|
||||
"iat": 1704115200, // Issued at timestamp
|
||||
"scope": "dashboard:read chart:read" // Space-separated scopes
|
||||
}
|
||||
```
|
||||
|
||||
### Optional Claims
|
||||
|
||||
Additional claims for enhanced functionality:
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "user@company.com", // User email
|
||||
"name": "John Doe", // Full name
|
||||
"groups": ["analysts", "sales_team"], // User groups
|
||||
"tenant_id": "company_123", // Multi-tenant ID
|
||||
"role": "analyst" // User role
|
||||
}
|
||||
```
|
||||
|
||||
## Client Integration
|
||||
|
||||
### API Client Usage
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# Get JWT token from your identity provider
|
||||
token = get_jwt_token()
|
||||
|
||||
# Call MCP service with Bearer authentication
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
"http://localhost:5008/call_tool",
|
||||
headers=headers,
|
||||
json={
|
||||
"tool": "list_dashboards",
|
||||
"arguments": {"search": "sales"}
|
||||
}
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
```
|
||||
|
||||
### Claude Desktop with Authentication
|
||||
|
||||
For Claude Desktop, the proxy script handles authentication:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# run_proxy_with_auth.sh
|
||||
|
||||
# Get token from environment or file
|
||||
if [ -f ~/.superset_mcp_token ]; then
|
||||
TOKEN=$(cat ~/.superset_mcp_token)
|
||||
else
|
||||
TOKEN=${SUPERSET_MCP_TOKEN}
|
||||
fi
|
||||
|
||||
# Export token for proxy
|
||||
export MCP_AUTH_TOKEN="$TOKEN"
|
||||
|
||||
cd /path/to/superset
|
||||
source venv/bin/activate
|
||||
exec fastmcp proxy http://localhost:5008 --auth-header "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
## User Resolution
|
||||
|
||||
### Default User Resolution
|
||||
|
||||
The service maps JWT claims to Superset users:
|
||||
|
||||
```python
|
||||
def default_user_resolver(claims: Dict[str, Any]) -> User:
|
||||
"""Default user resolution from JWT claims."""
|
||||
|
||||
# Extract username from configurable claim
|
||||
username = claims.get(app.config.get("MCP_JWT_USER_CLAIM", "sub"))
|
||||
|
||||
# Find Superset user
|
||||
user = security_manager.find_user(username=username)
|
||||
|
||||
if not user:
|
||||
# Try email lookup
|
||||
email = claims.get(app.config.get("MCP_JWT_EMAIL_CLAIM", "email"))
|
||||
if email:
|
||||
user = security_manager.find_user(email=email)
|
||||
|
||||
if not user and app.config.get("MCP_FALLBACK_USER"):
|
||||
# Use fallback user for development
|
||||
user = security_manager.find_user(username=app.config["MCP_FALLBACK_USER"])
|
||||
|
||||
return user
|
||||
```
|
||||
|
||||
### Custom User Resolution
|
||||
|
||||
Implement custom user resolution logic:
|
||||
|
||||
```python
|
||||
def custom_user_resolver(claims: Dict[str, Any]) -> User:
|
||||
"""Custom user resolution for enterprise environments."""
|
||||
|
||||
# Extract custom claims
|
||||
employee_id = claims.get("employee_id")
|
||||
tenant_id = claims.get("tenant_id")
|
||||
|
||||
# Multi-tenant user lookup
|
||||
user = find_user_by_employee_id(employee_id, tenant_id)
|
||||
|
||||
if user:
|
||||
# Set additional context
|
||||
user.mcp_tenant_id = tenant_id
|
||||
user.mcp_groups = claims.get("groups", [])
|
||||
|
||||
return user
|
||||
|
||||
# Use custom resolver
|
||||
MCP_USER_RESOLVER = custom_user_resolver
|
||||
```
|
||||
|
||||
## Security Features
|
||||
|
||||
### Token Validation
|
||||
|
||||
Comprehensive JWT validation:
|
||||
|
||||
- **Signature verification**: RS256 with JWKS key rotation support
|
||||
- **Expiration checking**: Automatic token expiry validation
|
||||
- **Audience validation**: Prevents token reuse across services
|
||||
- **Issuer validation**: Ensures tokens from trusted sources only
|
||||
- **Scope validation**: Enforces tool-level permissions
|
||||
|
||||
### Request Security
|
||||
|
||||
- **HTTPS enforcement**: Production deployments should use HTTPS
|
||||
- **Rate limiting**: Configurable per-user rate limits
|
||||
- **Request logging**: All authenticated requests logged with user context
|
||||
- **Input validation**: Comprehensive request schema validation
|
||||
|
||||
### Audit Logging
|
||||
|
||||
Every tool call is logged with security context:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2024-01-25T14:30:00Z",
|
||||
"user_id": "user@company.com",
|
||||
"tool_name": "get_chart_data",
|
||||
"source": "mcp",
|
||||
"jwt_subject": "user@company.com",
|
||||
"jwt_scopes": ["chart:read", "analytics:export"],
|
||||
"tenant_id": "company_123",
|
||||
"request_id": "req_12345",
|
||||
"execution_time": 0.145,
|
||||
"status": "success"
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Authentication
|
||||
|
||||
### Generate Test Tokens
|
||||
|
||||
For development and testing:
|
||||
|
||||
```python
|
||||
from fastmcp.server.auth.providers.bearer import RSAKeyPair
|
||||
|
||||
# Generate test keypair
|
||||
keypair = RSAKeyPair.generate()
|
||||
print("Public key:", keypair.public_key)
|
||||
|
||||
# Create test token
|
||||
token = keypair.create_token(
|
||||
subject="test@example.com",
|
||||
issuer="https://test.example.com",
|
||||
audience="superset-mcp-api",
|
||||
scopes=["dashboard:read", "chart:read", "dataset:read"],
|
||||
expires_in=3600 # 1 hour
|
||||
)
|
||||
print("Test token:", token)
|
||||
```
|
||||
|
||||
### Test Configuration
|
||||
|
||||
```python
|
||||
# Test configuration with generated keypair
|
||||
MCP_AUTH_ENABLED = True
|
||||
MCP_JWT_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
||||
-----END PUBLIC KEY-----"""
|
||||
MCP_JWT_ISSUER = "https://test.example.com"
|
||||
MCP_JWT_AUDIENCE = "superset-mcp-api"
|
||||
MCP_FALLBACK_USER = "admin"
|
||||
```
|
||||
|
||||
### Manual Testing
|
||||
|
||||
```bash
|
||||
# Test with curl
|
||||
curl -X POST http://localhost:5008/call_tool \
|
||||
-H "Authorization: Bearer $TEST_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tool": "get_superset_instance_info",
|
||||
"arguments": {"include_statistics": true}
|
||||
}'
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Token Validation Errors:**
|
||||
```
|
||||
Error: Invalid JWT signature
|
||||
Solution: Verify JWKS_URI is accessible and contains correct keys
|
||||
```
|
||||
|
||||
**User Not Found:**
|
||||
```
|
||||
Error: User not found for JWT subject
|
||||
Solution: Check MCP_JWT_USER_CLAIM configuration and user exists in Superset
|
||||
```
|
||||
|
||||
**Insufficient Scopes:**
|
||||
```
|
||||
Error: Missing required scope 'chart:read'
|
||||
Solution: Update JWT token to include required scopes
|
||||
```
|
||||
|
||||
### Debug Configuration
|
||||
|
||||
Enable debug logging for authentication issues:
|
||||
|
||||
```python
|
||||
# Enhanced logging for auth debugging
|
||||
import logging
|
||||
logging.getLogger('superset.mcp_service.auth').setLevel(logging.DEBUG)
|
||||
|
||||
# Log all JWT validation steps
|
||||
MCP_AUTH_DEBUG = True
|
||||
```
|
||||
|
||||
This authentication guide provides comprehensive coverage for securing the MCP service in production environments while maintaining development flexibility.
|
||||
@@ -1,705 +0,0 @@
|
||||
---
|
||||
title: Development Guide
|
||||
sidebar_position: 2
|
||||
version: 1
|
||||
---
|
||||
|
||||
# MCP Service Development Guide
|
||||
|
||||
This guide covers the internal architecture, development workflows, and patterns for extending the Superset MCP service.
|
||||
|
||||
> 🚀 **New to MCP?** Start with the [Overview](./overview) to understand what the service does before diving into development.
|
||||
>
|
||||
> 📚 **Need API examples?** Check the [API Reference](./api-reference) to see how existing tools work.
|
||||
>
|
||||
> 🔐 **Planning production use?** Review [Authentication](./authentication) for security considerations.
|
||||
|
||||
## Internal Architecture
|
||||
|
||||
### Component Overview
|
||||
|
||||
The MCP service follows a layered architecture with clear separation of concerns:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Transport Layer"
|
||||
HTTP[HTTP Server :5008]
|
||||
FastMCP[FastMCP Protocol Handler]
|
||||
end
|
||||
|
||||
subgraph "Auth & Middleware Layer"
|
||||
AuthHook[Auth Hook Decorator]
|
||||
JWT[JWT Validator]
|
||||
RBAC[RBAC Engine]
|
||||
Audit[Audit Logger]
|
||||
end
|
||||
|
||||
subgraph "Tool Layer"
|
||||
Tools[16 MCP Tools<br/>Tool Decorated]
|
||||
Schemas[Pydantic Schemas]
|
||||
Validation[Request Validation]
|
||||
end
|
||||
|
||||
subgraph "Business Logic Layer"
|
||||
Generic[Generic Tool Abstractions]
|
||||
ModelList[ModelListTool]
|
||||
ModelGet[ModelGetInfoTool]
|
||||
ModelFilter[ModelGetAvailableFiltersTool]
|
||||
end
|
||||
|
||||
subgraph "Data Access Layer"
|
||||
DAOs[Superset DAOs]
|
||||
Commands[Superset Commands]
|
||||
Cache[Cache Manager]
|
||||
end
|
||||
|
||||
subgraph "Storage Layer"
|
||||
MetaDB[(Metadata DB)]
|
||||
DataWH[(Data Warehouse)]
|
||||
Redis[(Redis Cache)]
|
||||
end
|
||||
|
||||
HTTP --> FastMCP
|
||||
FastMCP --> AuthHook
|
||||
AuthHook --> JWT
|
||||
JWT --> RBAC
|
||||
RBAC --> Audit
|
||||
Audit --> Tools
|
||||
|
||||
Tools --> Schemas
|
||||
Schemas --> Validation
|
||||
Validation --> Generic
|
||||
|
||||
Generic --> ModelList
|
||||
Generic --> ModelGet
|
||||
Generic --> ModelFilter
|
||||
|
||||
ModelList --> DAOs
|
||||
ModelGet --> DAOs
|
||||
ModelFilter --> DAOs
|
||||
|
||||
Tools --> Commands
|
||||
Commands --> Cache
|
||||
|
||||
DAOs --> MetaDB
|
||||
Commands --> MetaDB
|
||||
Commands --> DataWH
|
||||
Cache --> Redis
|
||||
```
|
||||
|
||||
### Request Flow
|
||||
|
||||
Every MCP tool call follows this execution pattern:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as LLM Client
|
||||
participant MCP as FastMCP Server
|
||||
participant Auth as Auth Hook
|
||||
participant Tool as MCP Tool
|
||||
participant Generic as Generic Abstraction
|
||||
participant DAO as Superset DAO
|
||||
participant DB as Database
|
||||
|
||||
Client->>+MCP: tool_call(request)
|
||||
MCP->>+Auth: validate_and_authorize()
|
||||
Auth->>Auth: Validate JWT token
|
||||
Auth->>Auth: Check required scopes
|
||||
Auth->>Auth: Set Flask g.user context
|
||||
Auth->>Auth: Log audit event
|
||||
Auth->>+Tool: execute_tool(validated_request)
|
||||
|
||||
Tool->>Tool: Parse Pydantic request schema
|
||||
Tool->>+Generic: Use generic abstraction
|
||||
Generic->>+DAO: Query Superset data
|
||||
DAO->>+DB: Execute SQL
|
||||
DB-->>-DAO: Return results
|
||||
DAO-->>-Generic: Return objects
|
||||
Generic->>Generic: Apply pagination/filtering
|
||||
Generic-->>-Tool: Return formatted data
|
||||
|
||||
Tool->>Tool: Build Pydantic response schema
|
||||
Tool-->>-Auth: Return response
|
||||
Auth->>Auth: Log success audit event
|
||||
Auth-->>-MCP: Return validated response
|
||||
MCP-->>-Client: JSON response
|
||||
```
|
||||
|
||||
### Tool Registration System
|
||||
|
||||
Tools are automatically discovered and registered through the decorator pattern:
|
||||
|
||||
```python
|
||||
# superset/mcp_service/mcp_app.py
|
||||
from fastmcp import FastMCP
|
||||
|
||||
# Global MCP instance
|
||||
mcp = FastMCP("Superset MCP Service")
|
||||
|
||||
# Tools register themselves via decorators
|
||||
@mcp.tool
|
||||
@mcp_auth_hook(['chart:read'])
|
||||
def get_chart_info(request: GetChartInfoRequest) -> GetChartInfoResponse:
|
||||
# Tool implementation
|
||||
pass
|
||||
|
||||
# All tool modules imported to trigger registration
|
||||
from superset.mcp_service.chart.tool import *
|
||||
from superset.mcp_service.dashboard.tool import *
|
||||
from superset.mcp_service.dataset.tool import *
|
||||
from superset.mcp_service.system.tool import *
|
||||
```
|
||||
|
||||
## Development Patterns
|
||||
|
||||
### Tool Implementation Pattern
|
||||
|
||||
All tools follow this standardized pattern:
|
||||
|
||||
```python
|
||||
# Example: superset/mcp_service/chart/tool/get_chart_info.py
|
||||
from superset.mcp_service.auth import mcp_auth_hook
|
||||
from superset.mcp_service.mcp_app import mcp
|
||||
from superset.mcp_service.schemas.chart_schemas import (
|
||||
GetChartInfoRequest,
|
||||
GetChartInfoResponse,
|
||||
ChartError
|
||||
)
|
||||
|
||||
@mcp.tool
|
||||
@mcp_auth_hook(['chart:read'])
|
||||
def get_chart_info(request: GetChartInfoRequest) -> GetChartInfoResponse:
|
||||
"""
|
||||
Get detailed information about a specific chart.
|
||||
|
||||
Supports lookup by ID or UUID with comprehensive metadata.
|
||||
"""
|
||||
try:
|
||||
# CRITICAL: Import Superset modules inside function
|
||||
from superset.daos.chart import ChartDAO
|
||||
from superset.models.slice import Slice
|
||||
|
||||
# Use generic abstraction for common operations
|
||||
from superset.mcp_service.generic_tools import ModelGetInfoTool
|
||||
|
||||
tool = ModelGetInfoTool(
|
||||
dao=ChartDAO,
|
||||
model=Slice,
|
||||
response_schema=GetChartInfoResponse,
|
||||
identifier_field_map={
|
||||
'id': 'id',
|
||||
'uuid': 'uuid'
|
||||
}
|
||||
)
|
||||
|
||||
return tool.execute(request)
|
||||
|
||||
except Exception as e:
|
||||
return ChartError(
|
||||
error=f"Failed to get chart info: {str(e)}",
|
||||
error_type="ChartInfoError"
|
||||
)
|
||||
```
|
||||
|
||||
### Schema Design Patterns
|
||||
|
||||
Pydantic schemas follow these conventions:
|
||||
|
||||
```python
|
||||
# Request Schema Pattern
|
||||
class GetChartInfoRequest(BaseModel):
|
||||
"""Request to get detailed chart information."""
|
||||
|
||||
identifier: Union[int, str] = Field(
|
||||
...,
|
||||
description="Chart ID (numeric) or UUID (string)"
|
||||
)
|
||||
|
||||
include_form_data: bool = Field(
|
||||
default=True,
|
||||
description="Whether to include chart configuration"
|
||||
)
|
||||
|
||||
use_cache: bool = Field(
|
||||
default=True,
|
||||
description="Whether to use cached data"
|
||||
)
|
||||
|
||||
# Response Schema Pattern
|
||||
class GetChartInfoResponse(BaseModel):
|
||||
"""Detailed chart information response."""
|
||||
|
||||
chart_id: int = Field(description="Chart numeric ID")
|
||||
uuid: Optional[str] = Field(description="Chart UUID")
|
||||
slice_name: str = Field(description="Chart display name")
|
||||
viz_type: str = Field(description="Visualization type")
|
||||
datasource_id: Optional[int] = Field(description="Dataset ID")
|
||||
form_data: Optional[Dict[str, Any]] = Field(description="Chart configuration")
|
||||
explore_url: Optional[str] = Field(description="Explore URL for editing")
|
||||
|
||||
# Cache status for transparency
|
||||
cache_status: Optional[CacheStatus] = Field(description="Cache hit information")
|
||||
|
||||
# Error Schema Pattern
|
||||
class ChartError(BaseModel):
|
||||
"""Chart operation error response."""
|
||||
|
||||
error: str = Field(description="Error message")
|
||||
error_type: str = Field(description="Error type identifier")
|
||||
suggestions: Optional[List[str]] = Field(description="Suggested fixes")
|
||||
```
|
||||
|
||||
### Generic Tool Abstractions
|
||||
|
||||
Common operations are abstracted into reusable classes:
|
||||
|
||||
```python
|
||||
# superset/mcp_service/generic_tools.py
|
||||
from typing import Type, Dict, Any, List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
class ModelListTool:
|
||||
"""Generic tool for list operations with pagination and filtering."""
|
||||
|
||||
def __init__(self,
|
||||
dao: Type,
|
||||
model: Type,
|
||||
response_schema: Type[BaseModel],
|
||||
default_columns: List[str] = None,
|
||||
searchable_columns: List[str] = None):
|
||||
self.dao = dao
|
||||
self.model = model
|
||||
self.response_schema = response_schema
|
||||
self.default_columns = default_columns or []
|
||||
self.searchable_columns = searchable_columns or []
|
||||
|
||||
def execute(self, request: BaseModel) -> BaseModel:
|
||||
"""Execute list operation with pagination and filtering."""
|
||||
|
||||
# Build query with filters
|
||||
query = self.dao.find_all()
|
||||
|
||||
# Apply search if provided
|
||||
if hasattr(request, 'search') and request.search:
|
||||
query = self._apply_search(query, request.search)
|
||||
|
||||
# Apply filters if provided
|
||||
if hasattr(request, 'filters') and request.filters:
|
||||
query = self._apply_filters(query, request.filters)
|
||||
|
||||
# Apply pagination
|
||||
total = query.count()
|
||||
|
||||
if hasattr(request, 'page') and hasattr(request, 'page_size'):
|
||||
offset = (request.page - 1) * request.page_size
|
||||
query = query.offset(offset).limit(request.page_size)
|
||||
|
||||
# Execute query and serialize
|
||||
results = query.all()
|
||||
serialized = [self._serialize_model(obj) for obj in results]
|
||||
|
||||
return self.response_schema(
|
||||
results=serialized,
|
||||
total_count=total,
|
||||
page=getattr(request, 'page', 1),
|
||||
page_size=getattr(request, 'page_size', len(serialized))
|
||||
)
|
||||
|
||||
class ModelGetInfoTool:
|
||||
"""Generic tool for getting single object by multiple identifier types."""
|
||||
|
||||
def __init__(self,
|
||||
dao: Type,
|
||||
model: Type,
|
||||
response_schema: Type[BaseModel],
|
||||
identifier_field_map: Dict[str, str]):
|
||||
self.dao = dao
|
||||
self.model = model
|
||||
self.response_schema = response_schema
|
||||
self.identifier_field_map = identifier_field_map
|
||||
|
||||
def execute(self, request: BaseModel) -> BaseModel:
|
||||
"""Execute get operation with multi-identifier support."""
|
||||
|
||||
identifier = request.identifier
|
||||
|
||||
# Determine identifier type and field
|
||||
if isinstance(identifier, int):
|
||||
field = self.identifier_field_map.get('id', 'id')
|
||||
obj = self.dao.find_by_id(identifier)
|
||||
elif isinstance(identifier, str):
|
||||
if len(identifier) == 36 and '-' in identifier: # UUID format
|
||||
field = self.identifier_field_map.get('uuid', 'uuid')
|
||||
obj = self.dao.find_by_uuid(identifier)
|
||||
else: # Assume slug
|
||||
field = self.identifier_field_map.get('slug', 'slug')
|
||||
obj = getattr(self.dao, 'find_by_slug', lambda x: None)(identifier)
|
||||
|
||||
if not obj:
|
||||
raise ValueError(f"Object not found with identifier: {identifier}")
|
||||
|
||||
# Serialize and return
|
||||
serialized = self._serialize_model(obj)
|
||||
return self.response_schema(**serialized)
|
||||
```
|
||||
|
||||
## Adding New Tools
|
||||
|
||||
### Step-by-Step Process
|
||||
|
||||
1. **Define the Domain**
|
||||
|
||||
Choose the appropriate domain folder:
|
||||
- `dashboard/` - Dashboard operations
|
||||
- `chart/` - Chart operations
|
||||
- `dataset/` - Dataset operations
|
||||
- `system/` - System-level operations
|
||||
|
||||
2. **Create Schemas**
|
||||
|
||||
```bash
|
||||
# Create schema file
|
||||
touch superset/mcp_service/schemas/my_domain_schemas.py
|
||||
```
|
||||
|
||||
```python
|
||||
# Define request/response schemas
|
||||
class MyToolRequest(BaseModel):
|
||||
param1: str = Field(description="Parameter description")
|
||||
param2: Optional[int] = Field(default=None, description="Optional parameter")
|
||||
|
||||
class MyToolResponse(BaseModel):
|
||||
result: str = Field(description="Result description")
|
||||
metadata: Dict[str, Any] = Field(description="Additional metadata")
|
||||
```
|
||||
|
||||
3. **Implement the Tool**
|
||||
|
||||
```bash
|
||||
# Create tool file
|
||||
touch superset/mcp_service/my_domain/tool/my_tool.py
|
||||
```
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
@mcp_auth_hook(['required:scope'])
|
||||
def my_tool(request: MyToolRequest) -> MyToolResponse:
|
||||
"""Tool description for LLM."""
|
||||
|
||||
# Import Superset modules inside function
|
||||
from superset.daos.my_dao import MyDAO
|
||||
|
||||
# Implement business logic
|
||||
result = MyDAO.do_something(request.param1)
|
||||
|
||||
return MyToolResponse(
|
||||
result=result,
|
||||
metadata={"processed_at": datetime.utcnow()}
|
||||
)
|
||||
```
|
||||
|
||||
4. **Register the Tool**
|
||||
|
||||
```python
|
||||
# Add to superset/mcp_service/my_domain/tool/__init__.py
|
||||
from .my_tool import my_tool
|
||||
|
||||
__all__ = ['my_tool']
|
||||
```
|
||||
|
||||
```python
|
||||
# Import in superset/mcp_service/mcp_app.py
|
||||
from superset.mcp_service.my_domain.tool import *
|
||||
```
|
||||
|
||||
5. **Add Tests**
|
||||
|
||||
```bash
|
||||
# Create test file
|
||||
touch tests/unit_tests/mcp_service/test_my_tool.py
|
||||
```
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from superset.mcp_service.my_domain.tool.my_tool import my_tool
|
||||
from superset.mcp_service.schemas.my_domain_schemas import MyToolRequest
|
||||
|
||||
class TestMyTool:
|
||||
def test_my_tool_success(self):
|
||||
request = MyToolRequest(param1="test")
|
||||
response = my_tool(request)
|
||||
assert response.result == "expected_result"
|
||||
```
|
||||
|
||||
### Tool Best Practices
|
||||
|
||||
1. **Import Inside Functions**
|
||||
```python
|
||||
# ❌ DON'T: Import at module level
|
||||
from superset.daos.chart import ChartDAO
|
||||
|
||||
@mcp.tool
|
||||
def my_tool():
|
||||
# Tool implementation
|
||||
pass
|
||||
|
||||
# ✅ DO: Import inside function
|
||||
@mcp.tool
|
||||
def my_tool():
|
||||
from superset.daos.chart import ChartDAO
|
||||
# Tool implementation
|
||||
pass
|
||||
```
|
||||
|
||||
2. **Use Generic Abstractions**
|
||||
```python
|
||||
# ✅ Leverage existing patterns
|
||||
@mcp.tool
|
||||
def list_my_objects(request):
|
||||
from superset.mcp_service.generic_tools import ModelListTool
|
||||
|
||||
tool = ModelListTool(
|
||||
dao=MyDAO,
|
||||
model=MyModel,
|
||||
response_schema=ListMyObjectsResponse
|
||||
)
|
||||
return tool.execute(request)
|
||||
```
|
||||
|
||||
3. **Comprehensive Error Handling**
|
||||
```python
|
||||
@mcp.tool
|
||||
def my_tool(request):
|
||||
try:
|
||||
# Tool implementation
|
||||
return success_response
|
||||
except PermissionError as e:
|
||||
return MyToolError(
|
||||
error="Permission denied",
|
||||
error_type="PermissionError",
|
||||
suggestions=["Check user permissions"]
|
||||
)
|
||||
except Exception as e:
|
||||
return MyToolError(
|
||||
error=f"Unexpected error: {str(e)}",
|
||||
error_type="InternalError"
|
||||
)
|
||||
```
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Unit Test Structure
|
||||
|
||||
```python
|
||||
# tests/unit_tests/mcp_service/test_chart_tools.py
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
from superset.mcp_service.chart.tool.get_chart_info import get_chart_info
|
||||
from superset.mcp_service.schemas.chart_schemas import GetChartInfoRequest
|
||||
|
||||
class TestGetChartInfo:
|
||||
"""Test suite for get_chart_info tool."""
|
||||
|
||||
@patch('superset.mcp_service.chart.tool.get_chart_info.ChartDAO')
|
||||
def test_get_chart_info_by_id_success(self, mock_dao):
|
||||
"""Test successful chart lookup by ID."""
|
||||
|
||||
# Setup mock
|
||||
mock_chart = Mock()
|
||||
mock_chart.id = 1
|
||||
mock_chart.slice_name = "Test Chart"
|
||||
mock_chart.viz_type = "line"
|
||||
mock_dao.find_by_id.return_value = mock_chart
|
||||
|
||||
# Execute
|
||||
request = GetChartInfoRequest(identifier=1)
|
||||
response = get_chart_info(request)
|
||||
|
||||
# Verify
|
||||
assert response.chart_id == 1
|
||||
assert response.slice_name == "Test Chart"
|
||||
mock_dao.find_by_id.assert_called_once_with(1)
|
||||
|
||||
@patch('superset.mcp_service.chart.tool.get_chart_info.ChartDAO')
|
||||
def test_get_chart_info_not_found(self, mock_dao):
|
||||
"""Test chart not found scenario."""
|
||||
|
||||
# Setup mock
|
||||
mock_dao.find_by_id.return_value = None
|
||||
|
||||
# Execute
|
||||
request = GetChartInfoRequest(identifier=999)
|
||||
response = get_chart_info(request)
|
||||
|
||||
# Verify error response
|
||||
assert hasattr(response, 'error')
|
||||
assert "not found" in response.error.lower()
|
||||
```
|
||||
|
||||
### Integration Test Patterns
|
||||
|
||||
```python
|
||||
# tests/integration_tests/mcp_service/test_chart_integration.py
|
||||
import pytest
|
||||
from superset.app import create_app
|
||||
from superset.mcp_service.mcp_app import mcp
|
||||
from tests.integration_tests.base_tests import SupersetTestCase
|
||||
|
||||
class TestChartIntegration(SupersetTestCase):
|
||||
"""Integration tests for chart tools."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.app = create_app()
|
||||
self.app_context = self.app.app_context()
|
||||
self.app_context.push()
|
||||
|
||||
def tearDown(self):
|
||||
self.app_context.pop()
|
||||
super().tearDown()
|
||||
|
||||
def test_chart_workflow_integration(self):
|
||||
"""Test complete chart workflow."""
|
||||
|
||||
# Create chart
|
||||
create_request = {
|
||||
"dataset_id": "1",
|
||||
"config": {
|
||||
"chart_type": "table",
|
||||
"columns": [{"name": "region"}]
|
||||
}
|
||||
}
|
||||
|
||||
create_response = mcp.call_tool("generate_chart", create_request)
|
||||
chart_id = create_response["chart_id"]
|
||||
|
||||
# Get chart info
|
||||
info_request = {"identifier": chart_id}
|
||||
info_response = mcp.call_tool("get_chart_info", info_request)
|
||||
|
||||
assert info_response["chart_id"] == chart_id
|
||||
assert info_response["viz_type"] == "table"
|
||||
|
||||
# Get chart data
|
||||
data_request = {"identifier": chart_id, "limit": 10}
|
||||
data_response = mcp.call_tool("get_chart_data", data_request)
|
||||
|
||||
assert "data" in data_response
|
||||
assert len(data_response["data"]) <= 10
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
The MCP service leverages Superset's existing cache layers:
|
||||
|
||||
```python
|
||||
# Cache control in tools
|
||||
@mcp.tool
|
||||
def get_chart_data(request: GetChartDataRequest):
|
||||
"""Tool with cache control."""
|
||||
|
||||
cache_config = {
|
||||
'use_cache': request.use_cache,
|
||||
'force_refresh': request.force_refresh,
|
||||
'cache_timeout': request.cache_timeout
|
||||
}
|
||||
|
||||
# Use Superset's cache infrastructure
|
||||
result = execute_with_cache(query, cache_config)
|
||||
|
||||
return ChartDataResponse(
|
||||
data=result.data,
|
||||
cache_status=result.cache_status
|
||||
)
|
||||
```
|
||||
|
||||
### Query Optimization
|
||||
|
||||
```python
|
||||
# Efficient pagination
|
||||
def list_objects(query, page, page_size):
|
||||
"""Optimized pagination pattern."""
|
||||
|
||||
# Count query optimization
|
||||
total = query.count()
|
||||
|
||||
# Limit columns for list operations
|
||||
query = query.options(load_only('id', 'name', 'created_on'))
|
||||
|
||||
# Apply pagination
|
||||
offset = (page - 1) * page_size
|
||||
results = query.offset(offset).limit(page_size).all()
|
||||
|
||||
return results, total
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```python
|
||||
# JWT validation and user context
|
||||
@mcp_auth_hook(['chart:read'])
|
||||
def secure_tool(request):
|
||||
"""Tool with proper security context."""
|
||||
|
||||
# g.user is set by auth hook
|
||||
user_id = g.user.id
|
||||
|
||||
# Apply user-specific filtering
|
||||
query = ChartDAO.find_all().filter(
|
||||
Chart.owners.contains(g.user)
|
||||
)
|
||||
|
||||
return execute_query(query)
|
||||
```
|
||||
|
||||
### Input Validation
|
||||
|
||||
```python
|
||||
# Comprehensive request validation
|
||||
class CreateChartRequest(BaseModel):
|
||||
"""Validated chart creation request."""
|
||||
|
||||
dataset_id: Union[int, str] = Field(
|
||||
...,
|
||||
description="Dataset ID or UUID"
|
||||
)
|
||||
|
||||
config: ChartConfig = Field(
|
||||
...,
|
||||
description="Chart configuration"
|
||||
)
|
||||
|
||||
@validator('dataset_id')
|
||||
def validate_dataset_id(cls, v):
|
||||
"""Validate dataset exists and user has access."""
|
||||
# Validation logic
|
||||
return v
|
||||
|
||||
@validator('config')
|
||||
def validate_chart_config(cls, v):
|
||||
"""Validate chart configuration."""
|
||||
# Configuration validation
|
||||
return v
|
||||
```
|
||||
|
||||
This development guide provides comprehensive coverage of the MCP service's internal architecture and development patterns, enabling team members to effectively extend and maintain the system.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
### 📚 **Ready to Use Your New Tools?**
|
||||
Test your implementations with examples from the [API Reference](./api-reference).
|
||||
|
||||
### 🔐 **Securing Your Extensions?**
|
||||
Add authentication to your tools using the [Authentication Guide](./authentication).
|
||||
|
||||
### 🏗️ **Understanding the Big Picture?**
|
||||
See the complete system design in the [Architecture Overview](./architecture).
|
||||
|
||||
### 🏢 **Building Enterprise Features?**
|
||||
Explore advanced patterns in the [Preset Integration Guide](./preset-integration).
|
||||
|
||||
> 📖 **Back to Documentation Index**: [MCP Service](./intro)
|
||||
@@ -1,124 +0,0 @@
|
||||
---
|
||||
title: MCP Service
|
||||
sidebar_position: 1
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Superset MCP Service
|
||||
|
||||
The Superset Model Context Protocol (MCP) service provides programmatic access to Superset dashboards, charts, datasets, and instance metadata. Built for LLM agents and automation tools.
|
||||
|
||||
## What is MCP?
|
||||
|
||||
The Model Context Protocol (MCP) is an open standard that allows AI assistants to securely connect to data sources and tools. Superset's MCP service exposes **16 production-ready tools** that enable:
|
||||
|
||||
- 📊 **Data Exploration**: List and query dashboards, charts, and datasets
|
||||
- 🔧 **Chart Creation**: Generate visualizations programmatically
|
||||
- 📈 **Data Export**: Extract data in multiple formats (JSON, CSV, Excel)
|
||||
- 🔗 **Navigation**: Generate explore links and SQL Lab sessions
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
:::note
|
||||
The MCP service is included with Superset development setup. FastMCP dependencies are installed automatically with `make install`.
|
||||
:::
|
||||
|
||||
```bash
|
||||
# MCP service is included with Superset development setup
|
||||
git clone https://github.com/apache/superset.git
|
||||
cd superset
|
||||
make venv && source venv/bin/activate
|
||||
make install
|
||||
|
||||
# Start Superset
|
||||
superset run -p 8088 --with-threads --reload --debugger
|
||||
|
||||
# Start MCP service (separate terminal)
|
||||
source venv/bin/activate
|
||||
superset mcp run --port 5008 --debug
|
||||
```
|
||||
|
||||
### Claude Desktop Integration
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"Superset MCP": {
|
||||
"command": "/path/to/superset/superset/mcp_service/run_proxy.sh",
|
||||
"args": [],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### 🔧 **16 Production Tools**
|
||||
| Category | Tools | Purpose |
|
||||
|----------|-------|---------|
|
||||
| **Dashboard** (5) | List, get info, create, add charts | Dashboard management |
|
||||
| **Chart** (8) | Full CRUD, data export, previews | Chart operations |
|
||||
| **Dataset** (3) | List, get info, discover filters | Dataset exploration |
|
||||
| **System** (2) | Instance info, explore links | System integration |
|
||||
| **SQL Lab** (1) | Pre-configured sessions | SQL development |
|
||||
|
||||
### 🔐 **Enterprise Security**
|
||||
- **JWT Bearer Authentication**: Production-ready with configurable factory pattern
|
||||
- **RBAC Integration**: Scope-based permissions with Superset's security model
|
||||
- **Audit Logging**: Comprehensive MCP context tracking
|
||||
|
||||
### 📊 **Advanced Capabilities**
|
||||
- **Multi-format Export**: JSON, CSV, Excel data export
|
||||
- **Chart Previews**: Screenshots, ASCII art, table representations
|
||||
- **Cache Control**: Leverage Superset's existing cache infrastructure
|
||||
- **Request Schemas**: Eliminates LLM parameter validation issues
|
||||
|
||||
## Example Usage
|
||||
|
||||
```python
|
||||
# List dashboards
|
||||
dashboards = client.call_tool("list_dashboards", {
|
||||
"search": "sales",
|
||||
"page_size": 10
|
||||
})
|
||||
|
||||
# Create a chart
|
||||
chart = client.call_tool("generate_chart", {
|
||||
"dataset_id": "1",
|
||||
"config": {
|
||||
"chart_type": "line",
|
||||
"x": {"name": "date"},
|
||||
"y": [{"name": "revenue", "aggregate": "SUM"}]
|
||||
}
|
||||
})
|
||||
|
||||
# Export chart data
|
||||
data = client.call_tool("get_chart_data", {
|
||||
"identifier": chart["chart_id"],
|
||||
"format": "json",
|
||||
"limit": 1000
|
||||
})
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
✅ **Phase 1 Complete** - Core functionality stable, authentication production-ready, comprehensive testing coverage.
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
### Getting Started
|
||||
- **[Overview](./overview)** - Features, use cases, and examples
|
||||
- **[API Reference](./api-reference)** - Complete tool documentation
|
||||
|
||||
### Development
|
||||
- **[Development Guide](./development)** - Internal architecture and adding tools
|
||||
- **[Architecture](./architecture)** - System design and patterns
|
||||
|
||||
### Production
|
||||
- **[Authentication](./authentication)** - JWT setup and security
|
||||
- **[Preset Integration](./preset-integration)** - Enterprise features
|
||||
|
||||
> 🚀 **Ready to start?** Continue with the [Overview](./overview) for detailed examples and use cases.
|
||||
@@ -1,196 +0,0 @@
|
||||
---
|
||||
title: MCP Service Overview
|
||||
sidebar_position: 1
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Superset MCP Service
|
||||
|
||||
The Superset Model Context Protocol (MCP) service provides a modular, schema-driven interface for programmatic access to Superset dashboards, charts, datasets, and instance metadata. Built on FastMCP, it's designed for LLM agents and automation tools.
|
||||
|
||||
**Status:** ✅ Phase 1 Complete. Core functionality stable, authentication production-ready, comprehensive testing coverage.
|
||||
|
||||
## What is MCP?
|
||||
|
||||
The Model Context Protocol (MCP) is an open standard for connecting AI assistants to data sources and tools. Superset's MCP service exposes 16 tools that allow LLM agents to:
|
||||
|
||||
- **Explore data**: List and query dashboards, charts, and datasets
|
||||
- **Create visualizations**: Generate charts and dashboards programmatically
|
||||
- **Export data**: Extract chart data in multiple formats
|
||||
- **Navigate interfaces**: Generate explore links and SQL Lab sessions
|
||||
|
||||
## Key Features
|
||||
|
||||
### 🔧 **16 Production-Ready Tools**
|
||||
- **Dashboard Tools (5)**: List, get info, create dashboards, add charts
|
||||
- **Chart Tools (8)**: Full CRUD operations, data export, screenshot previews
|
||||
- **Dataset Tools (3)**: List, get info, discover filterable columns
|
||||
- **System Tools (2)**: Instance info, explore link generation
|
||||
- **SQL Lab Tools (1)**: Pre-configured SQL sessions
|
||||
|
||||
### 🔐 **Enterprise Authentication**
|
||||
- **JWT Bearer Authentication**: Production-ready with configurable factory pattern
|
||||
- **RBAC Integration**: Scope-based permissions with Superset's security model
|
||||
- **Audit Logging**: Comprehensive MCP context tracking with impersonation support
|
||||
|
||||
### 📊 **Advanced Capabilities**
|
||||
- **Multi-format Export**: JSON, CSV, Excel data export
|
||||
- **Chart Previews**: Screenshots, ASCII art, and table representations
|
||||
- **Cache Control**: Comprehensive control over Superset's cache layers
|
||||
- **Request Schema Pattern**: Eliminates LLM parameter validation issues
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Client Layer"
|
||||
LLM[LLM/Agent Client]
|
||||
Claude[Claude Desktop]
|
||||
SDK[Custom SDK]
|
||||
end
|
||||
|
||||
subgraph "MCP Service Layer"
|
||||
FastMCP[FastMCP Server<br/>Port 5008]
|
||||
Auth[JWT Auth Hook]
|
||||
Tools[16 MCP Tools]
|
||||
end
|
||||
|
||||
subgraph "Superset Integration"
|
||||
DAOs[Superset DAOs]
|
||||
Commands[Superset Commands]
|
||||
Cache[Cache Layer]
|
||||
end
|
||||
|
||||
subgraph "Data Layer"
|
||||
DB[(Superset Database)]
|
||||
DataWarehouse[(Data Warehouse)]
|
||||
end
|
||||
|
||||
LLM --> FastMCP
|
||||
Claude --> FastMCP
|
||||
SDK --> FastMCP
|
||||
|
||||
FastMCP --> Auth
|
||||
Auth --> Tools
|
||||
|
||||
Tools --> DAOs
|
||||
Tools --> Commands
|
||||
Tools --> Cache
|
||||
|
||||
DAOs --> DB
|
||||
Commands --> DB
|
||||
Commands --> DataWarehouse
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Quick Setup
|
||||
|
||||
```bash
|
||||
# Clone and install Superset
|
||||
git clone https://github.com/apache/superset.git
|
||||
cd superset
|
||||
make venv && source venv/bin/activate
|
||||
make install
|
||||
|
||||
# Start Superset
|
||||
superset run -p 8088 --with-threads --reload --debugger
|
||||
|
||||
# Start MCP service (in separate terminal)
|
||||
source venv/bin/activate
|
||||
superset mcp run --port 5008 --debug
|
||||
```
|
||||
|
||||
### Connect to Claude Desktop
|
||||
|
||||
:::note
|
||||
The MCP service runs on HTTP and requires a proxy for Claude Desktop integration.
|
||||
:::
|
||||
|
||||
```bash
|
||||
# Install FastMCP proxy
|
||||
pip install fastmcp
|
||||
```
|
||||
|
||||
Configure Claude Desktop (`~/.config/Claude/claude_desktop_config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"Superset MCP": {
|
||||
"command": "/path/to/superset/superset/mcp_service/run_proxy.sh",
|
||||
"args": [],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Data Exploration
|
||||
- "List all dashboards related to sales"
|
||||
- "Show me the charts in the Q4 Performance dashboard"
|
||||
- "What datasets are available for customer analysis?"
|
||||
|
||||
### Chart Creation
|
||||
- "Create a line chart showing revenue trends by month"
|
||||
- "Generate a table showing top 10 products by sales"
|
||||
- "Build a bar chart comparing regional performance"
|
||||
|
||||
### Data Export
|
||||
- "Export the sales data from this chart as CSV"
|
||||
- "Get the underlying data for this dashboard as JSON"
|
||||
- "Show me a preview of this chart as ASCII art"
|
||||
|
||||
### Dashboard Management
|
||||
- "Create a new dashboard with these 4 charts"
|
||||
- "Add this revenue chart to the executive dashboard"
|
||||
- "Generate an explore link for this chart configuration"
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```python
|
||||
# List available dashboards
|
||||
dashboards = client.call_tool("list_dashboards", {
|
||||
"search": "sales",
|
||||
"page_size": 10
|
||||
})
|
||||
|
||||
# Get detailed dashboard info
|
||||
dashboard = client.call_tool("get_dashboard_info", {
|
||||
"identifier": dashboards["dashboards"][0]["id"]
|
||||
})
|
||||
|
||||
# Create a new chart
|
||||
chart = client.call_tool("generate_chart", {
|
||||
"dataset_id": "1",
|
||||
"config": {
|
||||
"chart_type": "line",
|
||||
"x": {"name": "date"},
|
||||
"y": [{"name": "revenue", "aggregate": "SUM"}]
|
||||
}
|
||||
})
|
||||
|
||||
# Export chart data
|
||||
data = client.call_tool("get_chart_data", {
|
||||
"identifier": chart["chart_id"],
|
||||
"format": "json",
|
||||
"limit": 1000
|
||||
})
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Ready to Use MCP?
|
||||
- **[📚 API Reference](./api-reference)** - Try all 16 tools with request/response examples
|
||||
- **[🔐 Authentication](./authentication)** - Set up JWT security for production use
|
||||
|
||||
### Want to Extend MCP?
|
||||
- **[🔧 Development Guide](./development)** - Learn internal architecture and add new tools
|
||||
- **[🏗️ Architecture](./architecture)** - Understand system design and deployment patterns
|
||||
|
||||
### Enterprise Deployment?
|
||||
- **[🏢 Preset Integration](./preset-integration)** - RBAC extensions and OIDC integration for enterprise
|
||||
|
||||
> 💡 **Getting started?** Return to the [MCP Service intro](./intro) for a complete overview.
|
||||
@@ -1,483 +0,0 @@
|
||||
---
|
||||
title: Preset.io Integration
|
||||
sidebar_position: 6
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Preset.io Integration Guide
|
||||
|
||||
This document outlines integration points for the Preset.io team to extend the Superset MCP service with enterprise features, RBAC customizations, and OIDC integration.
|
||||
|
||||
## RBAC Extension Points
|
||||
|
||||
### Custom Authorization Factory
|
||||
|
||||
The MCP service supports custom authorization logic through the factory pattern:
|
||||
|
||||
```python
|
||||
# In preset_config.py or superset_config.py
|
||||
def create_preset_mcp_auth(app):
|
||||
"""Custom auth factory for Preset.io environments."""
|
||||
from superset.mcp_service.auth import create_auth_provider
|
||||
from preset.auth.mcp import PresetMCPAuthProvider
|
||||
|
||||
return PresetMCPAuthProvider(
|
||||
jwks_uri=app.config["PRESET_JWKS_URI"],
|
||||
issuer=app.config["PRESET_JWT_ISSUER"],
|
||||
audience=app.config["PRESET_JWT_AUDIENCE"],
|
||||
tenant_resolver=preset_tenant_resolver,
|
||||
rbac_manager=app.security_manager,
|
||||
)
|
||||
|
||||
MCP_AUTH_FACTORY = create_preset_mcp_auth
|
||||
```
|
||||
|
||||
### Multi-Tenant RBAC
|
||||
|
||||
Extend the base auth hook for tenant-aware permissions:
|
||||
|
||||
```python
|
||||
# preset/mcp/auth.py
|
||||
from superset.mcp_service.auth import mcp_auth_hook
|
||||
from functools import wraps
|
||||
|
||||
def preset_tenant_auth_hook(required_permissions=None):
|
||||
"""Preset-specific auth hook with tenant isolation."""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
@mcp_auth_hook(required_permissions)
|
||||
def wrapper(*args, **kwargs):
|
||||
# Extract tenant from JWT claims
|
||||
tenant_id = g.user.tenant_id if hasattr(g.user, 'tenant_id') else None
|
||||
|
||||
# Inject tenant context
|
||||
g.mcp_tenant_id = tenant_id
|
||||
g.mcp_tenant_context = get_tenant_context(tenant_id)
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
```
|
||||
|
||||
### Custom Permission Scopes
|
||||
|
||||
Define Preset-specific permission scopes:
|
||||
|
||||
```python
|
||||
# preset/mcp/permissions.py
|
||||
PRESET_MCP_SCOPES = {
|
||||
# Tenant-level permissions
|
||||
"tenant:admin": "Full tenant administration",
|
||||
"tenant:read": "Read tenant resources",
|
||||
|
||||
# Workspace-level permissions
|
||||
"workspace:admin": "Full workspace administration",
|
||||
"workspace:read": "Read workspace resources",
|
||||
|
||||
# Enhanced dashboard permissions
|
||||
"dashboard:publish": "Publish dashboards to marketplace",
|
||||
"dashboard:embed": "Generate embed tokens",
|
||||
|
||||
# Enhanced chart permissions
|
||||
"chart:export": "Export chart data and configs",
|
||||
"chart:alerts": "Manage chart alerts and notifications",
|
||||
|
||||
# Dataset permissions with row-level security
|
||||
"dataset:rls": "Apply row-level security filters",
|
||||
"dataset:pii": "Access PII-flagged columns",
|
||||
}
|
||||
|
||||
def get_preset_required_scopes(tool_name: str, context: dict = None) -> List[str]:
|
||||
"""Map tool calls to Preset-specific permission requirements."""
|
||||
base_scopes = get_base_required_scopes(tool_name)
|
||||
|
||||
# Add tenant-aware scopes
|
||||
if context and context.get('tenant_id'):
|
||||
base_scopes.append(f"tenant:{context['tenant_id']}")
|
||||
|
||||
# Add workspace-aware scopes
|
||||
if context and context.get('workspace_id'):
|
||||
base_scopes.append(f"workspace:{context['workspace_id']}")
|
||||
|
||||
return base_scopes
|
||||
```
|
||||
|
||||
### Row-Level Security Integration
|
||||
|
||||
Extend data access tools with RLS:
|
||||
|
||||
```python
|
||||
# preset/mcp/rls.py
|
||||
def apply_preset_rls_filters(query_context: dict, user_context: dict) -> dict:
|
||||
"""Apply Preset row-level security filters to query context."""
|
||||
|
||||
# Get user's RLS rules from Preset metadata
|
||||
rls_rules = get_user_rls_rules(
|
||||
user_id=user_context['user_id'],
|
||||
tenant_id=user_context['tenant_id'],
|
||||
workspace_id=user_context.get('workspace_id')
|
||||
)
|
||||
|
||||
# Apply RLS filters to query
|
||||
for rule in rls_rules:
|
||||
if rule.applies_to_dataset(query_context['datasource']['id']):
|
||||
query_context = rule.apply_filter(query_context)
|
||||
|
||||
return query_context
|
||||
|
||||
# Usage in custom tools
|
||||
@mcp.tool
|
||||
@preset_tenant_auth_hook(['dataset:read', 'dataset:rls'])
|
||||
def preset_get_chart_data(request: GetChartDataRequest) -> ChartDataResponse:
|
||||
"""Get chart data with Preset RLS applied."""
|
||||
|
||||
# Apply RLS before executing query
|
||||
query_context = build_query_context(request)
|
||||
query_context = apply_preset_rls_filters(
|
||||
query_context,
|
||||
{'user_id': g.user.id, 'tenant_id': g.mcp_tenant_id}
|
||||
)
|
||||
|
||||
return execute_chart_data_query(query_context)
|
||||
```
|
||||
|
||||
## OIDC Integration Points
|
||||
|
||||
### Preset OIDC Provider
|
||||
|
||||
Custom OIDC integration for Preset environments:
|
||||
|
||||
```python
|
||||
# preset/mcp/oidc.py
|
||||
from superset.mcp_service.auth.providers.bearer import BearerAuthProvider
|
||||
import requests
|
||||
from typing import Dict, Any
|
||||
|
||||
class PresetOIDCAuthProvider(BearerAuthProvider):
|
||||
"""OIDC-specific auth provider for Preset.io."""
|
||||
|
||||
def __init__(self,
|
||||
oidc_discovery_url: str,
|
||||
client_id: str,
|
||||
client_secret: str = None,
|
||||
**kwargs):
|
||||
|
||||
# Discover OIDC endpoints
|
||||
self.discovery_doc = self._fetch_discovery_document(oidc_discovery_url)
|
||||
|
||||
super().__init__(
|
||||
jwks_uri=self.discovery_doc['jwks_uri'],
|
||||
issuer=self.discovery_doc['issuer'],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
|
||||
def _fetch_discovery_document(self, discovery_url: str) -> Dict[str, Any]:
|
||||
"""Fetch OIDC discovery document."""
|
||||
response = requests.get(discovery_url)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def validate_token(self, token: str) -> Dict[str, Any]:
|
||||
"""Validate JWT token with OIDC-specific claims."""
|
||||
claims = super().validate_token(token)
|
||||
|
||||
# Validate OIDC-specific claims
|
||||
if claims.get('aud') != self.client_id:
|
||||
raise ValueError("Invalid audience claim")
|
||||
|
||||
# Extract Preset-specific claims
|
||||
claims['preset_tenant_id'] = claims.get('tenant_id')
|
||||
claims['preset_workspace_id'] = claims.get('workspace_id')
|
||||
claims['preset_roles'] = claims.get('roles', [])
|
||||
|
||||
return claims
|
||||
|
||||
def resolve_user(self, claims: Dict[str, Any]) -> Any:
|
||||
"""Resolve Superset user from OIDC claims."""
|
||||
from preset.auth.user_resolver import resolve_preset_user
|
||||
|
||||
return resolve_preset_user(
|
||||
subject=claims['sub'],
|
||||
email=claims.get('email'),
|
||||
tenant_id=claims.get('preset_tenant_id'),
|
||||
roles=claims.get('preset_roles', [])
|
||||
)
|
||||
```
|
||||
|
||||
### Configuration for OIDC
|
||||
|
||||
```python
|
||||
# In preset_config.py
|
||||
def create_preset_oidc_auth(app):
|
||||
"""Factory for Preset OIDC authentication."""
|
||||
from preset.mcp.oidc import PresetOIDCAuthProvider
|
||||
|
||||
return PresetOIDCAuthProvider(
|
||||
oidc_discovery_url=app.config["PRESET_OIDC_DISCOVERY_URL"],
|
||||
client_id=app.config["PRESET_OIDC_CLIENT_ID"],
|
||||
client_secret=app.config["PRESET_OIDC_CLIENT_SECRET"],
|
||||
audience=app.config["PRESET_MCP_AUDIENCE"],
|
||||
required_scopes=app.config.get("PRESET_MCP_REQUIRED_SCOPES", [])
|
||||
)
|
||||
|
||||
# MCP Configuration
|
||||
MCP_AUTH_ENABLED = True
|
||||
MCP_AUTH_FACTORY = create_preset_oidc_auth
|
||||
|
||||
# OIDC Configuration
|
||||
PRESET_OIDC_DISCOVERY_URL = "https://auth.preset.io/.well-known/openid_configuration"
|
||||
PRESET_OIDC_CLIENT_ID = "preset-mcp-service"
|
||||
PRESET_OIDC_CLIENT_SECRET = os.environ.get("PRESET_OIDC_CLIENT_SECRET")
|
||||
PRESET_MCP_AUDIENCE = "preset-superset-mcp"
|
||||
PRESET_MCP_REQUIRED_SCOPES = [
|
||||
"openid", "profile", "email",
|
||||
"superset:read", "superset:write"
|
||||
]
|
||||
```
|
||||
|
||||
## Preset-Specific Tools
|
||||
|
||||
### Tenant Management Tools
|
||||
|
||||
```python
|
||||
# preset/mcp/tools/tenant.py
|
||||
@mcp.tool
|
||||
@preset_tenant_auth_hook(['tenant:read'])
|
||||
def get_tenant_info(request: GetTenantInfoRequest) -> TenantInfoResponse:
|
||||
"""Get Preset tenant information and quotas."""
|
||||
|
||||
tenant_id = g.mcp_tenant_id
|
||||
tenant = get_tenant_by_id(tenant_id)
|
||||
|
||||
return TenantInfoResponse(
|
||||
tenant_id=tenant.id,
|
||||
name=tenant.name,
|
||||
plan=tenant.plan,
|
||||
quotas=tenant.quotas,
|
||||
usage=get_tenant_usage(tenant_id),
|
||||
workspaces=list_tenant_workspaces(tenant_id)
|
||||
)
|
||||
|
||||
@mcp.tool
|
||||
@preset_tenant_auth_hook(['workspace:read'])
|
||||
def list_workspace_assets(request: ListWorkspaceAssetsRequest) -> ListWorkspaceAssetsResponse:
|
||||
"""List all assets in a Preset workspace."""
|
||||
|
||||
workspace_id = request.workspace_id
|
||||
tenant_id = g.mcp_tenant_id
|
||||
|
||||
# Validate workspace belongs to tenant
|
||||
validate_workspace_access(workspace_id, tenant_id)
|
||||
|
||||
assets = {
|
||||
'dashboards': list_workspace_dashboards(workspace_id),
|
||||
'charts': list_workspace_charts(workspace_id),
|
||||
'datasets': list_workspace_datasets(workspace_id)
|
||||
}
|
||||
|
||||
return ListWorkspaceAssetsResponse(
|
||||
workspace_id=workspace_id,
|
||||
assets=assets,
|
||||
total_count=sum(len(v) for v in assets.values())
|
||||
)
|
||||
```
|
||||
|
||||
### Embed Token Generation
|
||||
|
||||
```python
|
||||
# preset/mcp/tools/embed.py
|
||||
@mcp.tool
|
||||
@preset_tenant_auth_hook(['dashboard:embed'])
|
||||
def generate_embed_token(request: GenerateEmbedTokenRequest) -> EmbedTokenResponse:
|
||||
"""Generate secure embed token for dashboard/chart."""
|
||||
|
||||
# Validate resource access
|
||||
resource = validate_embed_resource_access(
|
||||
resource_type=request.resource_type,
|
||||
resource_id=request.resource_id,
|
||||
tenant_id=g.mcp_tenant_id
|
||||
)
|
||||
|
||||
# Generate signed embed token
|
||||
embed_token = create_embed_token(
|
||||
resource=resource,
|
||||
user_id=g.user.id,
|
||||
tenant_id=g.mcp_tenant_id,
|
||||
permissions=request.permissions,
|
||||
expiry=request.expiry_hours
|
||||
)
|
||||
|
||||
return EmbedTokenResponse(
|
||||
embed_token=embed_token,
|
||||
embed_url=f"{get_preset_base_url()}/embed/{embed_token}",
|
||||
expires_at=embed_token.expires_at
|
||||
)
|
||||
```
|
||||
|
||||
## Audit and Compliance Extensions
|
||||
|
||||
### Enhanced Audit Logging
|
||||
|
||||
```python
|
||||
# preset/mcp/audit.py
|
||||
from superset.mcp_service.auth import get_audit_context
|
||||
|
||||
def create_preset_audit_context(user_context: dict, tool_name: str,
|
||||
request_data: dict) -> dict:
|
||||
"""Create Preset-specific audit context."""
|
||||
|
||||
base_context = get_audit_context(user_context, tool_name, request_data)
|
||||
|
||||
# Add Preset-specific fields
|
||||
preset_context = {
|
||||
**base_context,
|
||||
'tenant_id': user_context.get('tenant_id'),
|
||||
'workspace_id': user_context.get('workspace_id'),
|
||||
'preset_user_role': user_context.get('preset_role'),
|
||||
'data_classification': classify_request_data(request_data),
|
||||
'compliance_flags': get_compliance_flags(tool_name, request_data)
|
||||
}
|
||||
|
||||
return preset_context
|
||||
|
||||
def log_preset_mcp_access(audit_context: dict):
|
||||
"""Log MCP access to Preset audit systems."""
|
||||
|
||||
# Log to Superset's audit system
|
||||
log_superset_audit_event(audit_context)
|
||||
|
||||
# Log to Preset's compliance system
|
||||
log_preset_compliance_event(audit_context)
|
||||
|
||||
# Log to external SIEM if configured
|
||||
if app.config.get('PRESET_SIEM_ENABLED'):
|
||||
log_to_siem(audit_context)
|
||||
```
|
||||
|
||||
### Data Classification
|
||||
|
||||
```python
|
||||
# preset/mcp/classification.py
|
||||
def classify_request_data(request_data: dict) -> dict:
|
||||
"""Classify data sensitivity in MCP requests."""
|
||||
|
||||
classification = {
|
||||
'contains_pii': False,
|
||||
'data_level': 'public',
|
||||
'retention_policy': 'standard'
|
||||
}
|
||||
|
||||
# Check for PII in request
|
||||
if contains_pii_fields(request_data):
|
||||
classification['contains_pii'] = True
|
||||
classification['data_level'] = 'restricted'
|
||||
classification['retention_policy'] = 'pii_compliant'
|
||||
|
||||
# Check for sensitive datasets
|
||||
if references_sensitive_datasets(request_data):
|
||||
classification['data_level'] = 'confidential'
|
||||
|
||||
return classification
|
||||
```
|
||||
|
||||
## Deployment Considerations
|
||||
|
||||
### Multi-Region Deployment
|
||||
|
||||
```python
|
||||
# preset/mcp/deployment.py
|
||||
def get_region_specific_config():
|
||||
"""Get region-specific MCP configuration."""
|
||||
|
||||
region = os.environ.get('PRESET_REGION', 'us-east-1')
|
||||
|
||||
config_map = {
|
||||
'us-east-1': {
|
||||
'jwks_uri': 'https://auth-us.preset.io/.well-known/jwks.json',
|
||||
'base_url': 'https://app.preset.io',
|
||||
'data_residency': 'US'
|
||||
},
|
||||
'eu-west-1': {
|
||||
'jwks_uri': 'https://auth-eu.preset.io/.well-known/jwks.json',
|
||||
'base_url': 'https://eu.preset.io',
|
||||
'data_residency': 'EU'
|
||||
}
|
||||
}
|
||||
|
||||
return config_map.get(region, config_map['us-east-1'])
|
||||
|
||||
# Usage in config
|
||||
region_config = get_region_specific_config()
|
||||
PRESET_JWKS_URI = region_config['jwks_uri']
|
||||
SUPERSET_WEBSERVER_ADDRESS = region_config['base_url']
|
||||
```
|
||||
|
||||
### Health Check Extensions
|
||||
|
||||
```python
|
||||
# preset/mcp/health.py
|
||||
@mcp.tool
|
||||
def preset_health_check() -> HealthCheckResponse:
|
||||
"""Preset-specific health check for MCP service."""
|
||||
|
||||
checks = {
|
||||
'mcp_service': check_mcp_service_health(),
|
||||
'database': check_database_health(),
|
||||
'auth_provider': check_auth_provider_health(),
|
||||
'tenant_isolation': check_tenant_isolation(),
|
||||
'rls_engine': check_rls_engine_health()
|
||||
}
|
||||
|
||||
overall_status = 'healthy' if all(
|
||||
check['status'] == 'healthy' for check in checks.values()
|
||||
) else 'degraded'
|
||||
|
||||
return HealthCheckResponse(
|
||||
status=overall_status,
|
||||
checks=checks,
|
||||
region=os.environ.get('PRESET_REGION'),
|
||||
version=get_preset_mcp_version()
|
||||
)
|
||||
```
|
||||
|
||||
## Configuration Templates
|
||||
|
||||
### Production Configuration
|
||||
|
||||
```python
|
||||
# preset_production_config.py
|
||||
from preset.mcp.auth import create_preset_oidc_auth
|
||||
from preset.mcp.audit import create_preset_audit_context
|
||||
|
||||
# MCP Service Configuration
|
||||
MCP_AUTH_ENABLED = True
|
||||
MCP_AUTH_FACTORY = create_preset_oidc_auth
|
||||
MCP_AUDIT_CONTEXT_FACTORY = create_preset_audit_context
|
||||
|
||||
# Preset OIDC Configuration
|
||||
PRESET_OIDC_DISCOVERY_URL = "https://auth.preset.io/.well-known/openid_configuration"
|
||||
PRESET_OIDC_CLIENT_ID = "preset-mcp-production"
|
||||
PRESET_MCP_AUDIENCE = "preset-superset-mcp"
|
||||
|
||||
# Security Configuration
|
||||
PRESET_MCP_REQUIRED_SCOPES = [
|
||||
"openid", "profile", "email",
|
||||
"tenant:read", "workspace:read",
|
||||
"dashboard:read", "chart:read", "dataset:read"
|
||||
]
|
||||
|
||||
# Audit Configuration
|
||||
PRESET_AUDIT_ENABLED = True
|
||||
PRESET_SIEM_ENABLED = True
|
||||
PRESET_COMPLIANCE_MODE = "SOC2"
|
||||
|
||||
# Performance Configuration
|
||||
PRESET_MCP_CACHE_ENABLED = True
|
||||
PRESET_MCP_RATE_LIMIT = "1000/hour"
|
||||
PRESET_MCP_TIMEOUT = 30
|
||||
```
|
||||
|
||||
This integration guide provides the Preset.io team with concrete extension points for implementing enterprise features while maintaining compatibility with the base MCP service architecture.
|
||||
@@ -87,16 +87,6 @@ const sidebars = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'MCP Service',
|
||||
items: [
|
||||
{
|
||||
type: 'autogenerated',
|
||||
dirName: 'mcp-service',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'FAQ',
|
||||
|
||||
@@ -133,7 +133,6 @@ solr = ["sqlalchemy-solr >= 0.2.0"]
|
||||
elasticsearch = ["elasticsearch-dbapi>=0.2.9, <0.3.0"]
|
||||
exasol = ["sqlalchemy-exasol >= 2.4.0, <3.0"]
|
||||
excel = ["xlrd>=1.2.0, <1.3"]
|
||||
fastmcp = ["fastmcp>=2.8.1"]
|
||||
firebird = ["sqlalchemy-firebird>=0.7.0, <0.8"]
|
||||
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
|
||||
gevent = ["gevent>=23.9.1"]
|
||||
@@ -203,7 +202,6 @@ development = [
|
||||
"pyinstrument>=4.0.2,<5",
|
||||
"pylint",
|
||||
"pytest<8.0.0", # hairy issue with pytest >=8 where current_app proxies are not set in time
|
||||
"pytest-asyncio", # need this due to not using latest pytest
|
||||
"pytest-cov",
|
||||
"pytest-mock",
|
||||
"python-ldap>=3.4.4",
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
-e .[development,bigquery,druid,fastmcp,gevent,gsheets,mysql,postgres,presto,prophet,trino,thumbnails]
|
||||
-e .[development,bigquery,druid,gevent,gsheets,mysql,postgres,presto,prophet,trino,thumbnails]
|
||||
|
||||
@@ -10,14 +10,6 @@ amqp==5.3.1
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# kombu
|
||||
annotated-types==0.7.0
|
||||
# via pydantic
|
||||
anyio==4.9.0
|
||||
# via
|
||||
# httpx
|
||||
# mcp
|
||||
# sse-starlette
|
||||
# starlette
|
||||
apispec==6.6.1
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -32,14 +24,11 @@ attrs==25.3.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# cattrs
|
||||
# cyclopts
|
||||
# jsonschema
|
||||
# outcome
|
||||
# referencing
|
||||
# requests-cache
|
||||
# trio
|
||||
authlib==1.6.1
|
||||
# via fastmcp
|
||||
babel==2.17.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -88,8 +77,6 @@ celery==5.5.2
|
||||
certifi==2025.6.15
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# httpcore
|
||||
# httpx
|
||||
# requests
|
||||
# selenium
|
||||
cffi==1.17.1
|
||||
@@ -114,7 +101,6 @@ click==8.2.1
|
||||
# click-repl
|
||||
# flask
|
||||
# flask-appbuilder
|
||||
# uvicorn
|
||||
click-didyoumean==0.3.1
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -154,13 +140,10 @@ cryptography==44.0.3
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# apache-superset
|
||||
# authlib
|
||||
# paramiko
|
||||
# pyopenssl
|
||||
cycler==0.12.1
|
||||
# via matplotlib
|
||||
cyclopts==3.22.2
|
||||
# via fastmcp
|
||||
db-dtypes==1.3.1
|
||||
# via pandas-gbq
|
||||
defusedxml==0.7.1
|
||||
@@ -185,23 +168,14 @@ dnspython==2.7.0
|
||||
# email-validator
|
||||
docker==7.0.0
|
||||
# via apache-superset
|
||||
docstring-parser==0.17.0
|
||||
# via cyclopts
|
||||
docutils==0.21.2
|
||||
# via rich-rst
|
||||
email-validator==2.2.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# flask-appbuilder
|
||||
# pydantic
|
||||
et-xmlfile==2.0.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# openpyxl
|
||||
exceptiongroup==1.3.0
|
||||
# via fastmcp
|
||||
fastmcp==2.10.6
|
||||
# via apache-superset
|
||||
filelock==3.12.2
|
||||
# via virtualenv
|
||||
flask==2.3.3
|
||||
@@ -353,8 +327,6 @@ gunicorn==23.0.0
|
||||
h11==0.16.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# httpcore
|
||||
# uvicorn
|
||||
# wsproto
|
||||
hashids==1.3.1
|
||||
# via
|
||||
@@ -365,14 +337,6 @@ holidays==0.25
|
||||
# -c requirements/base.txt
|
||||
# apache-superset
|
||||
# prophet
|
||||
httpcore==1.0.9
|
||||
# via httpx
|
||||
httpx==0.28.1
|
||||
# via
|
||||
# fastmcp
|
||||
# mcp
|
||||
httpx-sse==0.4.1
|
||||
# via mcp
|
||||
humanize==4.12.3
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -382,9 +346,7 @@ identify==2.5.36
|
||||
idna==3.10
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# anyio
|
||||
# email-validator
|
||||
# httpx
|
||||
# requests
|
||||
# trio
|
||||
# url-normalize
|
||||
@@ -416,7 +378,6 @@ jsonschema==4.23.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# flask-appbuilder
|
||||
# mcp
|
||||
# openapi-schema-validator
|
||||
# openapi-spec-validator
|
||||
jsonschema-path==0.3.4
|
||||
@@ -476,8 +437,6 @@ matplotlib==3.9.0
|
||||
# via prophet
|
||||
mccabe==0.7.0
|
||||
# via pylint
|
||||
mcp==1.12.0
|
||||
# via fastmcp
|
||||
mdurl==0.1.2
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -516,8 +475,6 @@ odfpy==1.4.1
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# pandas
|
||||
openapi-pydantic==0.5.1
|
||||
# via fastmcp
|
||||
openapi-schema-validator==0.6.3
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -650,16 +607,6 @@ pycparser==2.22
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# cffi
|
||||
pydantic==2.11.7
|
||||
# via
|
||||
# fastmcp
|
||||
# mcp
|
||||
# openapi-pydantic
|
||||
# pydantic-settings
|
||||
pydantic-core==2.33.2
|
||||
# via pydantic
|
||||
pydantic-settings==2.10.1
|
||||
# via mcp
|
||||
pydata-google-auth==1.9.0
|
||||
# via pandas-gbq
|
||||
pydruid==0.6.9
|
||||
@@ -695,8 +642,6 @@ pyparsing==3.2.3
|
||||
# -c requirements/base.txt
|
||||
# apache-superset
|
||||
# matplotlib
|
||||
pyperclip==1.9.0
|
||||
# via fastmcp
|
||||
pysocks==1.7.1
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -704,11 +649,8 @@ pysocks==1.7.1
|
||||
pytest==7.4.4
|
||||
# via
|
||||
# apache-superset
|
||||
# pytest-asyncio
|
||||
# pytest-cov
|
||||
# pytest-mock
|
||||
pytest-asyncio==0.23.8
|
||||
# via apache-superset
|
||||
pytest-cov==6.0.0
|
||||
# via apache-superset
|
||||
pytest-mock==3.10.0
|
||||
@@ -732,16 +674,12 @@ python-dotenv==1.1.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# apache-superset
|
||||
# fastmcp
|
||||
# pydantic-settings
|
||||
python-geohash==0.8.5
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# apache-superset
|
||||
python-ldap==3.4.4
|
||||
# via apache-superset
|
||||
python-multipart==0.0.20
|
||||
# via mcp
|
||||
pytz==2025.2
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -796,12 +734,7 @@ rfc3339-validator==0.1.4
|
||||
rich==13.9.4
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# cyclopts
|
||||
# fastmcp
|
||||
# flask-limiter
|
||||
# rich-rst
|
||||
rich-rst==1.3.1
|
||||
# via cyclopts
|
||||
rpds-py==0.25.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -846,7 +779,6 @@ slack-sdk==3.35.0
|
||||
sniffio==1.3.1
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# anyio
|
||||
# trio
|
||||
sortedcontainers==2.4.0
|
||||
# via
|
||||
@@ -876,14 +808,10 @@ sqlglot==27.3.0
|
||||
# apache-superset
|
||||
sqloxide==0.1.51
|
||||
# via apache-superset
|
||||
sse-starlette==2.4.1
|
||||
# via mcp
|
||||
sshtunnel==0.4.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# apache-superset
|
||||
starlette==0.47.2
|
||||
# via mcp
|
||||
statsd==4.0.1
|
||||
# via apache-superset
|
||||
tabulate==0.9.0
|
||||
@@ -911,23 +839,13 @@ typing-extensions==4.14.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
# alembic
|
||||
# anyio
|
||||
# apache-superset
|
||||
# cattrs
|
||||
# exceptiongroup
|
||||
# limits
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
# pyopenssl
|
||||
# referencing
|
||||
# selenium
|
||||
# shillelagh
|
||||
# starlette
|
||||
# typing-inspection
|
||||
typing-inspection==0.4.1
|
||||
# via
|
||||
# pydantic
|
||||
# pydantic-settings
|
||||
tzdata==2025.2
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
@@ -946,8 +864,6 @@ urllib3==2.5.0
|
||||
# requests
|
||||
# requests-cache
|
||||
# selenium
|
||||
uvicorn==0.35.0
|
||||
# via mcp
|
||||
vine==5.1.0
|
||||
# via
|
||||
# -c requirements/base.txt
|
||||
|
||||
@@ -33,4 +33,4 @@ superset load-test-users
|
||||
|
||||
echo "Running tests"
|
||||
|
||||
pytest --durations-min=2 --maxfail=1 --cov-report= --cov=superset ./tests/integration_tests "$@"
|
||||
pytest --durations-min=2 --cov-report= --cov=superset ./tests/integration_tests "$@"
|
||||
|
||||
43
superset-frontend/package-lock.json
generated
43
superset-frontend/package-lock.json
generated
@@ -10877,12 +10877,6 @@
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit/node_modules/reselect": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rjsf/core": {
|
||||
"version": "5.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.24.1.tgz",
|
||||
@@ -24611,12 +24605,6 @@
|
||||
"d3-time": "1 - 2"
|
||||
}
|
||||
},
|
||||
"node_modules/encodable/node_modules/reselect": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
@@ -50641,9 +50629,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resize-observer-polyfill": {
|
||||
@@ -59097,7 +59085,7 @@
|
||||
"csstype": "^3.1.3",
|
||||
"d3-format": "^1.3.2",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-scale": "^3.0.0",
|
||||
"d3-time": "^3.1.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"dayjs": "^1.11.13",
|
||||
@@ -59120,7 +59108,7 @@
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"reselect": "^5.1.1",
|
||||
"reselect": "^4.0.0",
|
||||
"rison": "^0.1.1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"xss": "^1.0.14"
|
||||
@@ -59199,19 +59187,16 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"packages/superset-ui-core/node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||
"license": "ISC",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz",
|
||||
"integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"d3-array": "2.10.0 - 3",
|
||||
"d3-format": "1 - 3",
|
||||
"d3-interpolate": "1.2.0 - 3",
|
||||
"d3-time": "2.1.1 - 3",
|
||||
"d3-time-format": "2 - 4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"d3-array": "^2.3.0",
|
||||
"d3-format": "1 - 2",
|
||||
"d3-interpolate": "1.2.0 - 2",
|
||||
"d3-time": "^2.1.1",
|
||||
"d3-time-format": "2 - 3"
|
||||
}
|
||||
},
|
||||
"packages/superset-ui-core/node_modules/d3-scale/node_modules/d3-interpolate": {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"d3-format": "^1.3.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"d3-interpolate": "^3.0.1",
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-scale": "^3.0.0",
|
||||
"d3-time": "^3.1.0",
|
||||
"d3-time-format": "^4.1.0",
|
||||
"dompurify": "^3.2.4",
|
||||
@@ -59,7 +59,7 @@
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"reselect": "^5.1.1",
|
||||
"reselect": "^4.0.0",
|
||||
"rison": "^0.1.1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"@visx/responsive": "^3.12.0",
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/** Type checking is disabled for this file due to reselect only supporting
|
||||
* TS declarations for selectors with up to 12 arguments. */
|
||||
// @ts-nocheck
|
||||
import { RefObject } from 'react';
|
||||
import { createSelector, lruMemoize } from 'reselect';
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
AppSection,
|
||||
Behavior,
|
||||
@@ -34,7 +37,7 @@ import {
|
||||
SetDataMaskHook,
|
||||
} from '../types/Base';
|
||||
import { QueryData, DataRecordFilters } from '..';
|
||||
import { supersetTheme, SupersetTheme } from '../../theme';
|
||||
import { SupersetTheme } from '../../theme';
|
||||
|
||||
// TODO: more specific typing for these fields of ChartProps
|
||||
type AnnotationData = PlainObject;
|
||||
@@ -106,8 +109,6 @@ export interface ChartPropsConfig {
|
||||
theme: SupersetTheme;
|
||||
/* legend index */
|
||||
legendIndex?: number;
|
||||
inContextMenu?: boolean;
|
||||
emitCrossFilters?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_WIDTH = 800;
|
||||
@@ -160,11 +161,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
|
||||
|
||||
theme: SupersetTheme;
|
||||
|
||||
constructor(
|
||||
config: ChartPropsConfig & { formData?: FormData } = {
|
||||
theme: supersetTheme,
|
||||
},
|
||||
) {
|
||||
constructor(config: ChartPropsConfig & { formData?: FormData } = {}) {
|
||||
const {
|
||||
annotationData = {},
|
||||
datasource = {},
|
||||
@@ -279,16 +276,5 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
|
||||
emitCrossFilters,
|
||||
theme,
|
||||
}),
|
||||
// Below config is to retain usage of 1-sized `lruMemoize` object in Reselect v4
|
||||
// Reselect v5 introduces `weakMapMemoize` which is more performant but potentially memory-leaky
|
||||
// due to infinite cache size.
|
||||
// Source: https://github.com/reduxjs/reselect/releases/tag/v5.0.1
|
||||
{
|
||||
memoize: lruMemoize,
|
||||
argsMemoize: lruMemoize,
|
||||
memoizeOptions: {
|
||||
maxSize: 10,
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -119,7 +119,7 @@ describe('ChartProps', () => {
|
||||
});
|
||||
expect(props1).not.toBe(props2);
|
||||
});
|
||||
it('selector returns a new chartProps if some input fields change and returns memoized chart props', () => {
|
||||
it('selector returns a new chartProps if some input fields change', () => {
|
||||
const props1 = selector({
|
||||
width: 800,
|
||||
height: 600,
|
||||
@@ -145,7 +145,7 @@ describe('ChartProps', () => {
|
||||
theme: supersetTheme,
|
||||
});
|
||||
expect(props1).not.toBe(props2);
|
||||
expect(props1).toBe(props3);
|
||||
expect(props1).not.toBe(props3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ def load_examples_run(
|
||||
load_big_data: bool = False,
|
||||
only_metadata: bool = False,
|
||||
force: bool = False,
|
||||
cleanup: bool = False,
|
||||
) -> None:
|
||||
if only_metadata:
|
||||
logger.info("Loading examples metadata")
|
||||
@@ -40,51 +41,41 @@ def load_examples_run(
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import superset.examples.data_loading as examples
|
||||
|
||||
# Clear old examples if requested
|
||||
if cleanup:
|
||||
clear_old_examples()
|
||||
|
||||
examples.load_css_templates()
|
||||
|
||||
if load_test_data:
|
||||
# Import test fixtures from tests directory
|
||||
from tests.fixtures.examples.energy import load_energy
|
||||
from tests.fixtures.examples.supported_charts_dashboard import (
|
||||
load_supported_charts_dashboard,
|
||||
)
|
||||
from tests.fixtures.examples.tabbed_dashboard import load_tabbed_dashboard
|
||||
|
||||
logger.info("Loading energy related dataset")
|
||||
examples.load_energy(only_metadata, force)
|
||||
load_energy(only_metadata, force)
|
||||
|
||||
logger.info("Loading [World Bank's Health Nutrition and Population Stats]")
|
||||
examples.load_world_bank_health_n_pop(only_metadata, force)
|
||||
|
||||
logger.info("Loading [Birth names]")
|
||||
examples.load_birth_names(only_metadata, force)
|
||||
|
||||
if load_test_data:
|
||||
logger.info("Loading [Tabbed dashboard]")
|
||||
examples.load_tabbed_dashboard(only_metadata)
|
||||
load_tabbed_dashboard(only_metadata)
|
||||
|
||||
logger.info("Loading [Supported Charts Dashboard]")
|
||||
examples.load_supported_charts_dashboard()
|
||||
load_supported_charts_dashboard()
|
||||
else:
|
||||
logger.info("Loading [Random long/lat data]")
|
||||
examples.load_long_lat_data(only_metadata, force)
|
||||
|
||||
logger.info("Loading [Country Map data]")
|
||||
examples.load_country_map_data(only_metadata, force)
|
||||
|
||||
logger.info("Loading [San Francisco population polygons]")
|
||||
examples.load_sf_population_polygons(only_metadata, force)
|
||||
|
||||
logger.info("Loading [Flights data]")
|
||||
examples.load_flights(only_metadata, force)
|
||||
|
||||
logger.info("Loading [BART lines]")
|
||||
examples.load_bart_lines(only_metadata, force)
|
||||
|
||||
logger.info("Loading [Misc Charts] dashboard")
|
||||
examples.load_misc_dashboard()
|
||||
|
||||
logger.info("Loading DECK.gl demo")
|
||||
examples.load_deck_dash()
|
||||
|
||||
if load_big_data:
|
||||
# Import test fixture from tests directory
|
||||
from tests.fixtures.examples.big_data import load_big_data as load_big_data_func
|
||||
|
||||
logger.info("Loading big synthetic data for tests")
|
||||
examples.load_big_data()
|
||||
load_big_data_func()
|
||||
|
||||
# load examples that are stored as YAML config files
|
||||
logger.info("Loading examples from YAML configuration files")
|
||||
examples.load_examples_from_configs(force, load_test_data)
|
||||
|
||||
|
||||
@@ -112,4 +103,222 @@ def load_examples(
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Loads a set of Slices and Dashboards and a supporting dataset"""
|
||||
# Show deprecation warning
|
||||
click.echo(
|
||||
click.style(
|
||||
"\nWARNING: 'superset load-examples' is deprecated. "
|
||||
"Please use 'superset examples load' instead.\n",
|
||||
fg="yellow",
|
||||
),
|
||||
err=True,
|
||||
)
|
||||
|
||||
load_examples_run(load_test_data, load_big_data, only_metadata, force)
|
||||
|
||||
|
||||
# New CLI structure
|
||||
@click.group(name="examples", help="Manage example data")
|
||||
def examples_cli() -> None:
|
||||
"""Group for example-related commands."""
|
||||
pass
|
||||
|
||||
|
||||
@examples_cli.command(name="load", help="Load example data into the database")
|
||||
@with_appcontext
|
||||
@transaction()
|
||||
@click.option("--load-test-data", "-t", is_flag=True, help="Load additional test data")
|
||||
@click.option("--load-big-data", "-b", is_flag=True, help="Load additional big data")
|
||||
@click.option(
|
||||
"--only-metadata",
|
||||
"-m",
|
||||
is_flag=True,
|
||||
help="Only load metadata, skip actual data",
|
||||
)
|
||||
@click.option(
|
||||
"--force",
|
||||
"-f",
|
||||
is_flag=True,
|
||||
help="Force load data even if table already exists",
|
||||
)
|
||||
def load(
|
||||
load_test_data: bool = False,
|
||||
load_big_data: bool = False,
|
||||
only_metadata: bool = False,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Load example datasets, charts, and dashboards."""
|
||||
load_examples_run(
|
||||
load_test_data, load_big_data, only_metadata, force, cleanup=False
|
||||
)
|
||||
|
||||
|
||||
def clear_old_examples() -> bool:
|
||||
"""
|
||||
Clear old Python-generated examples.
|
||||
Returns True if clear was performed, False otherwise.
|
||||
"""
|
||||
from superset import db
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.examples.utils import _has_old_examples
|
||||
from superset.models.core import Database
|
||||
from superset.models.dashboard import Dashboard, dashboard_slices
|
||||
from superset.models.slice import Slice
|
||||
|
||||
# Check if old examples exist
|
||||
if not _has_old_examples():
|
||||
logger.info("No old examples found to clear")
|
||||
return False
|
||||
|
||||
# Find the examples database
|
||||
examples_db = db.session.query(Database).filter_by(database_name="examples").first()
|
||||
|
||||
if not examples_db:
|
||||
return False
|
||||
|
||||
logger.info("Found examples database (id=%s)", examples_db.id)
|
||||
logger.info("Clearing old examples...")
|
||||
|
||||
# 1. Get all datasets from examples database
|
||||
example_datasets = (
|
||||
db.session.query(SqlaTable).filter_by(database_id=examples_db.id).all()
|
||||
)
|
||||
dataset_ids = [ds.id for ds in example_datasets]
|
||||
logger.info("Found %d example datasets", len(example_datasets))
|
||||
|
||||
# 2. Find all charts using these datasets
|
||||
example_charts = []
|
||||
if dataset_ids:
|
||||
example_charts = (
|
||||
db.session.query(Slice)
|
||||
.filter(
|
||||
Slice.datasource_id.in_(dataset_ids),
|
||||
Slice.datasource_type == "table",
|
||||
)
|
||||
.all()
|
||||
)
|
||||
logger.info("Found %d example charts", len(example_charts))
|
||||
|
||||
chart_ids = [chart.id for chart in example_charts]
|
||||
|
||||
# 3. Find dashboards that contain these charts
|
||||
example_dashboards = []
|
||||
if chart_ids:
|
||||
# Get dashboards that have relationships with our example charts
|
||||
example_dashboards = (
|
||||
db.session.query(Dashboard)
|
||||
.join(dashboard_slices)
|
||||
.filter(dashboard_slices.c.slice_id.in_(chart_ids))
|
||||
.distinct()
|
||||
.all()
|
||||
)
|
||||
logger.info("Found %d example dashboards", len(example_dashboards))
|
||||
|
||||
# Remove dashboard-slice relationships first
|
||||
db.session.execute(
|
||||
dashboard_slices.delete().where(dashboard_slices.c.slice_id.in_(chart_ids))
|
||||
)
|
||||
logger.info(
|
||||
"Removed dashboard-slice relationships for %d charts",
|
||||
len(chart_ids),
|
||||
)
|
||||
|
||||
# 4. Delete dashboards that are now empty (contain only example charts)
|
||||
for dashboard in example_dashboards:
|
||||
# Since we already deleted the relationships, check if dashboard is empty
|
||||
remaining_charts = (
|
||||
db.session.query(dashboard_slices.c.slice_id)
|
||||
.filter(dashboard_slices.c.dashboard_id == dashboard.id)
|
||||
.count()
|
||||
)
|
||||
|
||||
if remaining_charts == 0:
|
||||
db.session.delete(dashboard)
|
||||
logger.info(
|
||||
"Deleted dashboard: %s (slug: %s)",
|
||||
dashboard.dashboard_title,
|
||||
dashboard.slug,
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
"Keeping dashboard %s as it contains non-example charts",
|
||||
dashboard.dashboard_title,
|
||||
)
|
||||
|
||||
# 5. Delete charts
|
||||
for chart in example_charts:
|
||||
db.session.delete(chart)
|
||||
logger.info("Deleted %d example charts", len(example_charts))
|
||||
|
||||
# 6. Delete the database - this will cascade delete all datasets,
|
||||
# columns, and metrics thanks to the cascade="all, delete-orphan"
|
||||
db.session.delete(examples_db)
|
||||
|
||||
logger.info("Examples database and all related objects removed successfully")
|
||||
return True
|
||||
|
||||
|
||||
@examples_cli.command(name="clear-old", help="Clear old Python-based example data")
|
||||
@with_appcontext
|
||||
@transaction()
|
||||
@click.option(
|
||||
"--confirm",
|
||||
is_flag=True,
|
||||
help="Skip confirmation prompt",
|
||||
)
|
||||
def clear_old(confirm: bool = False) -> None:
|
||||
"""Clear old Python-generated example datasets, charts, and dashboards."""
|
||||
if not confirm:
|
||||
click.confirm(
|
||||
"This will delete old Python-based example data. Are you sure?",
|
||||
abort=True,
|
||||
)
|
||||
|
||||
try:
|
||||
if clear_old_examples():
|
||||
logger.info("Old examples cleared successfully")
|
||||
else:
|
||||
logger.info("No old examples found to clear")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to clear old examples: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@examples_cli.command(name="clear", help="Clear all example data (NOT YET IMPLEMENTED)")
|
||||
@with_appcontext
|
||||
def clear() -> None:
|
||||
"""Clear all example data including YAML-based examples."""
|
||||
click.echo(
|
||||
click.style(
|
||||
"Clearing YAML-based examples is NOT YET IMPLEMENTED.\n"
|
||||
"Use 'superset examples clear-old' to remove old Python-based examples.",
|
||||
fg="yellow",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@examples_cli.command(name="reload", help="Clear and reload example data")
|
||||
@with_appcontext
|
||||
@transaction()
|
||||
@click.option("--load-test-data", "-t", is_flag=True, help="Load additional test data")
|
||||
@click.option("--load-big-data", "-b", is_flag=True, help="Load additional big data")
|
||||
@click.option(
|
||||
"--only-metadata",
|
||||
"-m",
|
||||
is_flag=True,
|
||||
help="Only load metadata, skip actual data",
|
||||
)
|
||||
@click.option(
|
||||
"--force",
|
||||
"-f",
|
||||
is_flag=True,
|
||||
help="Force load data even if table already exists",
|
||||
)
|
||||
def reload(
|
||||
load_test_data: bool = False,
|
||||
load_big_data: bool = False,
|
||||
only_metadata: bool = False,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Clear existing examples and load fresh ones."""
|
||||
# This is essentially the old --cleanup behavior
|
||||
load_examples_run(load_test_data, load_big_data, only_metadata, force, cleanup=True)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# 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.
|
||||
"""CLI module for MCP service"""
|
||||
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
from superset.mcp_service.server import run_server
|
||||
|
||||
|
||||
@click.group()
|
||||
def mcp() -> None:
|
||||
"""Model Context Protocol service commands"""
|
||||
pass
|
||||
|
||||
|
||||
@mcp.command()
|
||||
@click.option("--host", default="127.0.0.1", help="Host to bind to")
|
||||
@click.option("--port", default=5008, help="Port to bind to")
|
||||
@click.option("--debug", is_flag=True, help="Enable debug mode")
|
||||
@click.option("--sql-debug", is_flag=True, help="Enable SQL query logging")
|
||||
def run(host: str, port: int, debug: bool, sql_debug: bool) -> None:
|
||||
"""Run the MCP service"""
|
||||
if sql_debug:
|
||||
os.environ["SQLALCHEMY_DEBUG"] = "1"
|
||||
click.echo("🔍 SQL Debug mode enabled")
|
||||
|
||||
run_server(host=host, port=port, debug=debug)
|
||||
@@ -16,6 +16,7 @@
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import logging
|
||||
from inspect import isclass
|
||||
from typing import Any
|
||||
|
||||
@@ -27,6 +28,8 @@ from superset.models.slice import Slice
|
||||
from superset.utils import json
|
||||
from superset.utils.core import AnnotationType, get_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def filter_chart_annotations(chart_config: dict[str, Any]) -> None:
|
||||
"""
|
||||
@@ -63,10 +66,13 @@ def import_chart(
|
||||
if not overwrite or not can_write:
|
||||
return existing
|
||||
config["id"] = existing.id
|
||||
logger.info(f"Updating existing chart: {config.get('slice_name')}")
|
||||
elif not can_write:
|
||||
raise ImportFailedError(
|
||||
"Chart doesn't exist and user doesn't have permission to create charts"
|
||||
)
|
||||
else:
|
||||
logger.info(f"Creating new chart: {config.get('slice_name')}")
|
||||
|
||||
filter_chart_annotations(config)
|
||||
|
||||
|
||||
@@ -123,6 +123,9 @@ class ExportDashboardsCommand(ExportModelsCommand):
|
||||
include_defaults=True,
|
||||
export_uuids=True,
|
||||
)
|
||||
# Remove theme_id from export to make dashboards theme-free
|
||||
payload.pop("theme_id", None)
|
||||
|
||||
# TODO (betodealmeida): move this logic to export_to_dict once this
|
||||
# becomes the default export endpoint
|
||||
for key, new_name in JSON_KEYS.items():
|
||||
|
||||
@@ -166,11 +166,14 @@ def import_dashboard( # noqa: C901
|
||||
elif not overwrite or not can_write:
|
||||
return existing
|
||||
config["id"] = existing.id
|
||||
logger.info(f"Updating existing dashboard: {config.get('dashboard_title')}")
|
||||
elif not can_write:
|
||||
raise ImportFailedError(
|
||||
"Dashboard doesn't exist and user doesn't "
|
||||
"have permission to create dashboards"
|
||||
)
|
||||
else:
|
||||
logger.info(f"Creating new dashboard: {config.get('dashboard_title')}")
|
||||
|
||||
# TODO (betodealmeida): move this logic to import_from_dict
|
||||
config = config.copy()
|
||||
|
||||
@@ -46,10 +46,13 @@ def import_database(
|
||||
if not overwrite or not can_write:
|
||||
return existing
|
||||
config["id"] = existing.id
|
||||
logger.info(f"Updating existing database: {config.get('database_name')}")
|
||||
elif not can_write:
|
||||
raise ImportFailedError(
|
||||
"Database doesn't exist and user doesn't have permission to create databases" # noqa: E501
|
||||
)
|
||||
else:
|
||||
logger.info(f"Creating new database: {config.get('database_name')}")
|
||||
# Check if this URI is allowed
|
||||
if app.config["PREVENT_UNSAFE_DB_CONNECTIONS"]:
|
||||
try:
|
||||
|
||||
@@ -124,11 +124,13 @@ def import_dataset( # noqa: C901
|
||||
if not overwrite or not can_write:
|
||||
return existing
|
||||
config["id"] = existing.id
|
||||
|
||||
logger.info(f"Updating existing dataset: {config.get('table_name')}")
|
||||
elif not can_write:
|
||||
raise ImportFailedError(
|
||||
"Dataset doesn't exist and user doesn't have permission to create datasets"
|
||||
)
|
||||
else:
|
||||
logger.info(f"Creating new dataset: {config.get('table_name')}")
|
||||
|
||||
# TODO (betodealmeida): move this logic to import_from_dict
|
||||
config = config.copy()
|
||||
@@ -209,7 +211,12 @@ def load_data(data_uri: str, dataset: SqlaTable, database: Database) -> None:
|
||||
data = request.urlopen(data_uri) # pylint: disable=consider-using-with # noqa: S310
|
||||
if data_uri.endswith(".gz"):
|
||||
data = gzip.open(data)
|
||||
df = pd.read_csv(data, encoding="utf-8")
|
||||
|
||||
# Determine file format based on URI
|
||||
if ".json" in data_uri:
|
||||
df = pd.read_json(data, encoding="utf-8")
|
||||
else:
|
||||
df = pd.read_csv(data, encoding="utf-8")
|
||||
dtype = get_dtype(df, dataset)
|
||||
|
||||
# convert temporal columns
|
||||
|
||||
@@ -37,10 +37,6 @@ class CreateFormDataCommand(BaseCommand):
|
||||
def __init__(self, cmd_params: CommandParameters):
|
||||
self._cmd_params = cmd_params
|
||||
|
||||
def _get_session_id(self) -> str:
|
||||
"""Get session ID. Can be overridden in subclasses."""
|
||||
return session.get("_id")
|
||||
|
||||
def run(self) -> str:
|
||||
self.validate()
|
||||
try:
|
||||
@@ -51,7 +47,7 @@ class CreateFormDataCommand(BaseCommand):
|
||||
form_data = self._cmd_params.form_data
|
||||
check_access(datasource_id, chart_id, datasource_type)
|
||||
contextual_key = cache_key(
|
||||
self._get_session_id(), tab_id, datasource_id, chart_id, datasource_type
|
||||
session.get("_id"), tab_id, datasource_id, chart_id, datasource_type
|
||||
)
|
||||
key = cache_manager.explore_form_data_cache.get(contextual_key)
|
||||
if not key or not tab_id:
|
||||
|
||||
@@ -195,4 +195,5 @@ class ImportExamplesCommand(ImportModelsCommand):
|
||||
{"dashboard_id": dashboard_id, "slice_id": chart_id}
|
||||
for (dashboard_id, chart_id) in dashboard_chart_ids
|
||||
]
|
||||
db.session.execute(dashboard_slices.insert(), values)
|
||||
if values:
|
||||
db.session.execute(dashboard_slices.insert(), values)
|
||||
|
||||
@@ -1155,7 +1155,7 @@ class CeleryConfig: # pylint: disable=too-few-public-methods
|
||||
}
|
||||
|
||||
|
||||
CELERY_CONFIG: type[CeleryConfig] | None = CeleryConfig
|
||||
CELERY_CONFIG: type[CeleryConfig] = CeleryConfig
|
||||
|
||||
# Set celery config to None to disable all the above configuration
|
||||
# CELERY_CONFIG = None
|
||||
|
||||
@@ -16,87 +16,18 @@
|
||||
# under the License.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid as uuid_lib
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
Generic,
|
||||
get_args,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
)
|
||||
from typing import Any, Generic, get_args, TypeVar
|
||||
|
||||
from flask_appbuilder.models.filters import BaseFilter
|
||||
from flask_appbuilder.models.sqla import Model
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import asc, cast, desc, or_, Text
|
||||
from sqlalchemy.exc import StatementError
|
||||
from sqlalchemy.inspection import inspect
|
||||
from sqlalchemy.orm import ColumnProperty, joinedload, RelationshipProperty
|
||||
|
||||
from superset.extensions import db
|
||||
|
||||
T = TypeVar("T", bound=Model)
|
||||
|
||||
|
||||
class ColumnOperatorEnum(str, Enum):
|
||||
eq = "eq"
|
||||
ne = "ne"
|
||||
sw = "sw"
|
||||
ew = "ew"
|
||||
in_ = "in"
|
||||
nin = "nin"
|
||||
gt = "gt"
|
||||
gte = "gte"
|
||||
lt = "lt"
|
||||
lte = "lte"
|
||||
like = "like"
|
||||
ilike = "ilike"
|
||||
is_null = "is_null"
|
||||
is_not_null = "is_not_null"
|
||||
|
||||
@classmethod
|
||||
def operator_map(cls) -> Dict[ColumnOperatorEnum, Any]:
|
||||
return {
|
||||
cls.eq: lambda col, val: col == val,
|
||||
cls.ne: lambda col, val: col != val,
|
||||
cls.sw: lambda col, val: col.like(f"{val}%"),
|
||||
cls.ew: lambda col, val: col.like(f"%{val}"),
|
||||
cls.in_: lambda col, val: col.in_(
|
||||
val if isinstance(val, (list, tuple)) else [val]
|
||||
),
|
||||
cls.nin: lambda col, val: ~col.in_(
|
||||
val if isinstance(val, (list, tuple)) else [val]
|
||||
),
|
||||
cls.gt: lambda col, val: col > val,
|
||||
cls.gte: lambda col, val: col >= val,
|
||||
cls.lt: lambda col, val: col < val,
|
||||
cls.lte: lambda col, val: col <= val,
|
||||
cls.like: lambda col, val: col.like(f"%{val}%"),
|
||||
cls.ilike: lambda col, val: col.ilike(f"%{val}%"),
|
||||
cls.is_null: lambda col, _: col.is_(None),
|
||||
cls.is_not_null: lambda col, _: col.isnot(None),
|
||||
}
|
||||
|
||||
def apply(self, column: Any, value: Any) -> Any:
|
||||
op_func = self.operator_map().get(self)
|
||||
if not op_func:
|
||||
raise ValueError(f"Unsupported operator: {self}")
|
||||
return op_func(column, value)
|
||||
|
||||
|
||||
class ColumnOperator(BaseModel):
|
||||
col: str = Field(..., description="Column name to filter on")
|
||||
opr: ColumnOperatorEnum = Field(..., description="Operator")
|
||||
value: Any = Field(None, description="Value for the filter")
|
||||
|
||||
|
||||
class BaseDAO(Generic[T]):
|
||||
"""
|
||||
Base DAO, implement base CRUD sqlalchemy operations
|
||||
@@ -119,128 +50,45 @@ class BaseDAO(Generic[T]):
|
||||
)[0]
|
||||
|
||||
@classmethod
|
||||
def _apply_base_filter(
|
||||
cls, query: Any, skip_base_filter: bool = False, data_model: Any = None
|
||||
) -> Any:
|
||||
"""
|
||||
Apply the base_filter to the query if it exists and skip_base_filter is False.
|
||||
"""
|
||||
if cls.base_filter and not skip_base_filter:
|
||||
if data_model is None:
|
||||
data_model = SQLAInterface(cls.model_cls, db.session)
|
||||
query = cls.base_filter( # pylint: disable=not-callable
|
||||
cls.id_column_name, data_model
|
||||
).apply(query, None)
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
def _convert_value_for_column(cls, column: Any, value: Any) -> Any:
|
||||
"""
|
||||
Convert a value to the appropriate type for a given SQLAlchemy column.
|
||||
|
||||
Args:
|
||||
column: SQLAlchemy column object
|
||||
value: Value to convert
|
||||
|
||||
Returns:
|
||||
Converted value or None if conversion fails
|
||||
"""
|
||||
if (
|
||||
hasattr(column.type, "python_type")
|
||||
and column.type.python_type == uuid_lib.UUID
|
||||
):
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return uuid_lib.UUID(value)
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def _find_by_column(
|
||||
def find_by_id(
|
||||
cls,
|
||||
column_name: str,
|
||||
value: str | int,
|
||||
model_id: str | int,
|
||||
skip_base_filter: bool = False,
|
||||
) -> T | None:
|
||||
"""
|
||||
Private method to find a model by any column value.
|
||||
|
||||
Args:
|
||||
column_name: Name of the column to search by
|
||||
value: Value to search for
|
||||
skip_base_filter: Whether to skip base filtering
|
||||
|
||||
Returns:
|
||||
Model instance or None if not found
|
||||
Find a model by id, if defined applies `base_filter`
|
||||
"""
|
||||
query = db.session.query(cls.model_cls)
|
||||
query = cls._apply_base_filter(query, skip_base_filter)
|
||||
|
||||
if not hasattr(cls.model_cls, column_name):
|
||||
return None
|
||||
|
||||
column = getattr(cls.model_cls, column_name)
|
||||
converted_value = cls._convert_value_for_column(column, value)
|
||||
if converted_value is None:
|
||||
return None
|
||||
|
||||
if cls.base_filter and not skip_base_filter:
|
||||
data_model = SQLAInterface(cls.model_cls, db.session)
|
||||
query = cls.base_filter( # pylint: disable=not-callable
|
||||
cls.id_column_name, data_model
|
||||
).apply(query, None)
|
||||
id_column = getattr(cls.model_cls, cls.id_column_name)
|
||||
try:
|
||||
return query.filter(column == converted_value).one_or_none()
|
||||
return query.filter(id_column == model_id).one_or_none()
|
||||
except StatementError:
|
||||
# can happen if int is passed instead of a string or similar
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_by_id(
|
||||
cls,
|
||||
model_id: str | int,
|
||||
skip_base_filter: bool = False,
|
||||
id_column: str | None = None,
|
||||
) -> T | None:
|
||||
"""
|
||||
Find a model by ID using specified or default ID column.
|
||||
|
||||
Args:
|
||||
model_id: ID value to search for
|
||||
skip_base_filter: Whether to skip base filtering
|
||||
id_column: Column name to use (defaults to cls.id_column_name)
|
||||
|
||||
Returns:
|
||||
Model instance or None if not found
|
||||
"""
|
||||
column = id_column or cls.id_column_name
|
||||
return cls._find_by_column(column, model_id, skip_base_filter)
|
||||
|
||||
@classmethod
|
||||
def find_by_ids(
|
||||
cls,
|
||||
model_ids: Sequence[str | int],
|
||||
model_ids: list[str] | list[int],
|
||||
skip_base_filter: bool = False,
|
||||
id_column: str | None = None,
|
||||
) -> list[T]:
|
||||
"""
|
||||
Find a List of models by a list of ids, if defined applies `base_filter`
|
||||
|
||||
:param model_ids: List of IDs to find
|
||||
:param skip_base_filter: If true, skip applying the base filter
|
||||
:param id_column: Optional column name to use for ID lookup
|
||||
(defaults to id_column_name)
|
||||
"""
|
||||
column = id_column or cls.id_column_name
|
||||
id_col = getattr(cls.model_cls, column, None)
|
||||
id_col = getattr(cls.model_cls, cls.id_column_name, None)
|
||||
if id_col is None:
|
||||
return []
|
||||
|
||||
# Convert IDs to appropriate types based on column type
|
||||
converted_ids: list[str | int | uuid_lib.UUID] = []
|
||||
for id_val in model_ids:
|
||||
converted_value = cls._convert_value_for_column(id_col, id_val)
|
||||
if converted_value is not None:
|
||||
converted_ids.append(converted_value)
|
||||
|
||||
query = db.session.query(cls.model_cls).filter(id_col.in_(converted_ids))
|
||||
query = cls._apply_base_filter(query, skip_base_filter)
|
||||
query = db.session.query(cls.model_cls).filter(id_col.in_(model_ids))
|
||||
if cls.base_filter and not skip_base_filter:
|
||||
data_model = SQLAInterface(cls.model_cls, db.session)
|
||||
query = cls.base_filter( # pylint: disable=not-callable
|
||||
cls.id_column_name, data_model
|
||||
).apply(query, None)
|
||||
return query.all()
|
||||
|
||||
@classmethod
|
||||
@@ -249,7 +97,11 @@ class BaseDAO(Generic[T]):
|
||||
Get all that fit the `base_filter`
|
||||
"""
|
||||
query = db.session.query(cls.model_cls)
|
||||
query = cls._apply_base_filter(query)
|
||||
if cls.base_filter:
|
||||
data_model = SQLAInterface(cls.model_cls, db.session)
|
||||
query = cls.base_filter( # pylint: disable=not-callable
|
||||
cls.id_column_name, data_model
|
||||
).apply(query, None)
|
||||
return query.all()
|
||||
|
||||
@classmethod
|
||||
@@ -258,7 +110,11 @@ class BaseDAO(Generic[T]):
|
||||
Get the first that fit the `base_filter`
|
||||
"""
|
||||
query = db.session.query(cls.model_cls)
|
||||
query = cls._apply_base_filter(query)
|
||||
if cls.base_filter:
|
||||
data_model = SQLAInterface(cls.model_cls, db.session)
|
||||
query = cls.base_filter( # pylint: disable=not-callable
|
||||
cls.id_column_name, data_model
|
||||
).apply(query, None)
|
||||
return query.filter_by(**filter_by).one_or_none()
|
||||
|
||||
@classmethod
|
||||
@@ -328,247 +184,3 @@ class BaseDAO(Generic[T]):
|
||||
|
||||
for item in items:
|
||||
db.session.delete(item)
|
||||
|
||||
@classmethod
|
||||
def apply_column_operators(
|
||||
cls, query: Any, column_operators: Optional[List[ColumnOperator]] = None
|
||||
) -> Any:
|
||||
"""
|
||||
Apply column operators (list of ColumnOperator) to the query using
|
||||
ColumnOperatorEnum logic. Raises ValueError if a filter references a
|
||||
non-existent column.
|
||||
"""
|
||||
if not column_operators:
|
||||
return query
|
||||
for c in column_operators:
|
||||
if not isinstance(c, ColumnOperator):
|
||||
continue
|
||||
col = c.col
|
||||
opr = c.opr
|
||||
value = c.value
|
||||
if not col or not hasattr(cls.model_cls, col):
|
||||
model_name = cls.model_cls.__name__ if cls.model_cls else "Unknown"
|
||||
logging.error(
|
||||
f"Invalid filter: column '{col}' does not exist on {model_name}"
|
||||
)
|
||||
raise ValueError(
|
||||
f"Invalid filter: column '{col}' does not exist on {model_name}"
|
||||
)
|
||||
column = getattr(cls.model_cls, col)
|
||||
try:
|
||||
# Always use ColumnOperatorEnum's apply method
|
||||
operator_enum = ColumnOperatorEnum(opr)
|
||||
query = query.filter(operator_enum.apply(column, value))
|
||||
except Exception as e:
|
||||
logging.error(f"Error applying filter on column '{col}': {e}")
|
||||
raise
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
def get_filterable_columns_and_operators(cls) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Returns a dict mapping filterable columns (including hybrid/computed fields if
|
||||
present) to their supported operators. Used by MCP tools to dynamically expose
|
||||
filter options. Custom fields supported by the DAO but not present on the model
|
||||
should be documented here.
|
||||
"""
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
mapper = inspect(cls.model_cls)
|
||||
columns = {c.key: c for c in mapper.columns}
|
||||
# Add hybrid properties
|
||||
hybrids = {
|
||||
name: attr
|
||||
for name, attr in vars(cls.model_cls).items()
|
||||
if isinstance(attr, hybrid_property)
|
||||
}
|
||||
# You may add custom fields here, e.g.:
|
||||
# custom_fields = {"tags": ["eq", "in_", "like"], ...}
|
||||
custom_fields: Dict[str, List[str]] = {}
|
||||
# Map SQLAlchemy types to supported operators
|
||||
type_operator_map = {
|
||||
"string": [
|
||||
"eq",
|
||||
"ne",
|
||||
"sw",
|
||||
"ew",
|
||||
"in_",
|
||||
"nin",
|
||||
"like",
|
||||
"ilike",
|
||||
"is_null",
|
||||
"is_not_null",
|
||||
],
|
||||
"boolean": ["eq", "ne", "is_null", "is_not_null"],
|
||||
"number": [
|
||||
"eq",
|
||||
"ne",
|
||||
"gt",
|
||||
"gte",
|
||||
"lt",
|
||||
"lte",
|
||||
"in_",
|
||||
"nin",
|
||||
"is_null",
|
||||
"is_not_null",
|
||||
],
|
||||
"datetime": [
|
||||
"eq",
|
||||
"ne",
|
||||
"gt",
|
||||
"gte",
|
||||
"lt",
|
||||
"lte",
|
||||
"in_",
|
||||
"nin",
|
||||
"is_null",
|
||||
"is_not_null",
|
||||
],
|
||||
}
|
||||
import sqlalchemy as sa
|
||||
|
||||
filterable = {}
|
||||
for name, col in columns.items():
|
||||
if isinstance(col.type, (sa.String, sa.Text)):
|
||||
filterable[name] = type_operator_map["string"]
|
||||
elif isinstance(col.type, (sa.Boolean,)):
|
||||
filterable[name] = type_operator_map["boolean"]
|
||||
elif isinstance(col.type, (sa.Integer, sa.Float, sa.Numeric)):
|
||||
filterable[name] = type_operator_map["number"]
|
||||
elif isinstance(col.type, (sa.DateTime, sa.Date, sa.Time)):
|
||||
filterable[name] = type_operator_map["datetime"]
|
||||
else:
|
||||
# Fallback to eq/ne/null
|
||||
filterable[name] = ["eq", "ne", "is_null", "is_not_null"]
|
||||
# Add hybrid properties as string fields by default
|
||||
for name in hybrids:
|
||||
filterable[name] = type_operator_map["string"]
|
||||
# Add custom fields
|
||||
filterable.update(custom_fields)
|
||||
return filterable
|
||||
|
||||
@classmethod
|
||||
def _build_query(
|
||||
cls,
|
||||
column_operators: Optional[List[ColumnOperator]] = None,
|
||||
search: Optional[str] = None,
|
||||
search_columns: Optional[List[str]] = None,
|
||||
custom_filters: Optional[Dict[str, BaseFilter]] = None,
|
||||
skip_base_filter: bool = False,
|
||||
data_model: Optional[SQLAInterface] = None,
|
||||
) -> Any:
|
||||
"""
|
||||
Build a SQLAlchemy query with base filter, column operators, search, and
|
||||
custom filters.
|
||||
"""
|
||||
if data_model is None:
|
||||
data_model = SQLAInterface(cls.model_cls, db.session)
|
||||
query = data_model.session.query(cls.model_cls)
|
||||
query = cls._apply_base_filter(
|
||||
query, skip_base_filter=skip_base_filter, data_model=data_model
|
||||
)
|
||||
if search and search_columns:
|
||||
search_filters = []
|
||||
for column_name in search_columns:
|
||||
if hasattr(cls.model_cls, column_name):
|
||||
column = getattr(cls.model_cls, column_name)
|
||||
search_filters.append(cast(column, Text).ilike(f"%{search}%"))
|
||||
if search_filters:
|
||||
query = query.filter(or_(*search_filters))
|
||||
if custom_filters:
|
||||
for filter_class in custom_filters.values():
|
||||
query = filter_class.apply(query, None)
|
||||
if column_operators:
|
||||
query = cls.apply_column_operators(query, column_operators)
|
||||
return query
|
||||
|
||||
@classmethod
|
||||
def list( # noqa: C901
|
||||
cls,
|
||||
column_operators: Optional[List[ColumnOperator]] = None,
|
||||
order_column: str = "changed_on",
|
||||
order_direction: str = "desc",
|
||||
page: int = 0,
|
||||
page_size: int = 100,
|
||||
search: Optional[str] = None,
|
||||
search_columns: Optional[List[str]] = None,
|
||||
custom_filters: Optional[Dict[str, BaseFilter]] = None,
|
||||
columns: Optional[List[str]] = None,
|
||||
) -> Tuple[List[Any], int]:
|
||||
"""
|
||||
Generic list method for filtered, sorted, and paginated results.
|
||||
If columns is specified, returns a list of tuples (one per row),
|
||||
otherwise returns model instances.
|
||||
"""
|
||||
data_model = SQLAInterface(cls.model_cls, db.session)
|
||||
|
||||
column_attrs = []
|
||||
relationship_loads = []
|
||||
if columns is None:
|
||||
columns = []
|
||||
for name in columns:
|
||||
attr = getattr(cls.model_cls, name, None)
|
||||
if attr is None:
|
||||
continue
|
||||
prop = getattr(attr, "property", None)
|
||||
if isinstance(prop, ColumnProperty):
|
||||
column_attrs.append(attr)
|
||||
elif isinstance(prop, RelationshipProperty):
|
||||
relationship_loads.append(joinedload(attr))
|
||||
# Ignore properties and other non-queryable attributes
|
||||
|
||||
if relationship_loads:
|
||||
# If any relationships are requested, query the full model and joinedload
|
||||
# relationships
|
||||
query = data_model.session.query(cls.model_cls)
|
||||
for loader in relationship_loads:
|
||||
query = query.options(loader)
|
||||
elif column_attrs:
|
||||
# Only columns requested
|
||||
query = data_model.session.query(*column_attrs)
|
||||
else:
|
||||
# Fallback: query the full model
|
||||
query = data_model.session.query(cls.model_cls)
|
||||
query = cls._apply_base_filter(query, data_model=data_model)
|
||||
if search and search_columns:
|
||||
search_filters = []
|
||||
for column_name in search_columns:
|
||||
if hasattr(cls.model_cls, column_name):
|
||||
column = getattr(cls.model_cls, column_name)
|
||||
search_filters.append(cast(column, Text).ilike(f"%{search}%"))
|
||||
if search_filters:
|
||||
query = query.filter(or_(*search_filters))
|
||||
if custom_filters:
|
||||
for filter_class in custom_filters.values():
|
||||
query = filter_class.apply(query, None)
|
||||
if column_operators:
|
||||
query = cls.apply_column_operators(query, column_operators)
|
||||
total_count = query.count()
|
||||
if hasattr(cls.model_cls, order_column):
|
||||
column = getattr(cls.model_cls, order_column)
|
||||
if order_direction.lower() == "desc":
|
||||
query = query.order_by(desc(column))
|
||||
else:
|
||||
query = query.order_by(asc(column))
|
||||
page = page
|
||||
page_size = max(page_size, 1)
|
||||
query = query.offset(page * page_size).limit(page_size)
|
||||
items = query.all()
|
||||
# If columns are specified, SQLAlchemy returns Row objects (not tuples or
|
||||
# model instances)
|
||||
return items, total_count
|
||||
|
||||
@classmethod
|
||||
def count(
|
||||
cls,
|
||||
column_operators: Optional[List[ColumnOperator]] = None,
|
||||
skip_base_filter: bool = False,
|
||||
) -> int:
|
||||
"""
|
||||
Count the number of records for the model, optionally filtered by column
|
||||
operators.
|
||||
"""
|
||||
query = cls._build_query(
|
||||
column_operators=column_operators, skip_base_filter=skip_base_filter
|
||||
)
|
||||
return query.count()
|
||||
|
||||
@@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from superset.charts.filters import ChartFilter
|
||||
from superset.daos.base import BaseDAO
|
||||
@@ -36,20 +36,6 @@ logger = logging.getLogger(__name__)
|
||||
class ChartDAO(BaseDAO[Slice]):
|
||||
base_filter = ChartFilter
|
||||
|
||||
@classmethod
|
||||
def get_filterable_columns_and_operators(cls) -> Dict[str, List[str]]:
|
||||
filterable = super().get_filterable_columns_and_operators()
|
||||
# Add custom fields for charts
|
||||
filterable.update(
|
||||
{
|
||||
"tags": ["eq", "in_", "like"],
|
||||
"owner": ["eq", "in_"],
|
||||
"viz_type": ["eq", "in_", "like"],
|
||||
"datasource_name": ["eq", "in_", "like"],
|
||||
}
|
||||
)
|
||||
return filterable
|
||||
|
||||
@staticmethod
|
||||
def favorited_ids(charts: list[Slice]) -> list[FavStar]:
|
||||
ids = [chart.id for chart in charts]
|
||||
|
||||
@@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any
|
||||
|
||||
from flask import g
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
@@ -48,20 +48,6 @@ logger = logging.getLogger(__name__)
|
||||
class DashboardDAO(BaseDAO[Dashboard]):
|
||||
base_filter = DashboardAccessFilter
|
||||
|
||||
@classmethod
|
||||
def get_filterable_columns_and_operators(cls) -> Dict[str, List[str]]:
|
||||
filterable = super().get_filterable_columns_and_operators()
|
||||
# Add custom fields for dashboards
|
||||
filterable.update(
|
||||
{
|
||||
"tags": ["eq", "in_", "like"],
|
||||
"owner": ["eq", "in_"],
|
||||
"published": ["eq"],
|
||||
"favorite": ["eq"],
|
||||
}
|
||||
)
|
||||
return filterable
|
||||
|
||||
@classmethod
|
||||
def get_by_id_or_slug(cls, id_or_slug: int | str) -> Dashboard:
|
||||
if is_uuid(id_or_slug):
|
||||
|
||||
@@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any
|
||||
|
||||
import dateutil.parser
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
@@ -37,13 +37,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DatasetDAO(BaseDAO[SqlaTable]):
|
||||
"""
|
||||
DAO for datasets. Supports filtering on model fields, hybrid properties, and custom
|
||||
fields:
|
||||
- tags: list of tags (eq, in_, like)
|
||||
- owner: user id (eq, in_)
|
||||
"""
|
||||
|
||||
base_filter = DatasourceFilter
|
||||
|
||||
@staticmethod
|
||||
@@ -358,18 +351,6 @@ class DatasetDAO(BaseDAO[SqlaTable]):
|
||||
.one_or_none()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_filterable_columns_and_operators(cls) -> Dict[str, List[str]]:
|
||||
filterable = super().get_filterable_columns_and_operators()
|
||||
# Add custom fields
|
||||
filterable.update(
|
||||
{
|
||||
"tags": ["eq", "in_", "like"],
|
||||
"owner": ["eq", "in_"],
|
||||
}
|
||||
)
|
||||
return filterable
|
||||
|
||||
|
||||
class DatasetColumnDAO(BaseDAO[TableColumn]):
|
||||
pass
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# 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 logging
|
||||
|
||||
import polyline
|
||||
from sqlalchemy import inspect, String, Text
|
||||
|
||||
from superset import db
|
||||
from superset.sql.parse import Table
|
||||
from superset.utils import json
|
||||
|
||||
from ..utils.database import get_example_database # noqa: TID252
|
||||
from .helpers import get_table_connector_registry, read_example_data
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_bart_lines(only_metadata: bool = False, force: bool = False) -> None:
|
||||
tbl_name = "bart_lines"
|
||||
database = get_example_database()
|
||||
with database.get_sqla_engine() as engine:
|
||||
schema = inspect(engine).default_schema_name
|
||||
table_exists = database.has_table(Table(tbl_name, schema))
|
||||
|
||||
if not only_metadata and (not table_exists or force):
|
||||
df = read_example_data(
|
||||
"examples://bart-lines.json.gz", encoding="latin-1", compression="gzip"
|
||||
)
|
||||
df["path_json"] = df.path.map(json.dumps)
|
||||
df["polyline"] = df.path.map(polyline.encode)
|
||||
del df["path"]
|
||||
|
||||
df.to_sql(
|
||||
tbl_name,
|
||||
engine,
|
||||
schema=schema,
|
||||
if_exists="replace",
|
||||
chunksize=500,
|
||||
dtype={
|
||||
"color": String(255),
|
||||
"name": String(255),
|
||||
"polyline": Text,
|
||||
"path_json": Text,
|
||||
},
|
||||
index=False,
|
||||
)
|
||||
|
||||
logger.debug(f"Creating table {tbl_name} reference")
|
||||
table = get_table_connector_registry()
|
||||
tbl = db.session.query(table).filter_by(table_name=tbl_name).first()
|
||||
if not tbl:
|
||||
tbl = table(table_name=tbl_name, schema=schema)
|
||||
db.session.add(tbl)
|
||||
tbl.description = "BART lines"
|
||||
tbl.database = database
|
||||
tbl.filter_select_enabled = True
|
||||
tbl.fetch_metadata()
|
||||
@@ -1,869 +0,0 @@
|
||||
# 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 logging
|
||||
import textwrap
|
||||
from typing import Union
|
||||
|
||||
import pandas as pd
|
||||
from sqlalchemy import DateTime, inspect, String
|
||||
from sqlalchemy.sql import column
|
||||
|
||||
from superset import app, db, security_manager
|
||||
from superset.connectors.sqla.models import SqlaTable, SqlMetric, TableColumn
|
||||
from superset.models.core import Database
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.sql.parse import Table
|
||||
from superset.utils import json
|
||||
from superset.utils.core import DatasourceType
|
||||
|
||||
from ..utils.database import get_example_database # noqa: TID252
|
||||
from .helpers import (
|
||||
get_slice_json,
|
||||
get_table_connector_registry,
|
||||
merge_slice,
|
||||
misc_dash_slices,
|
||||
read_example_data,
|
||||
update_slice_ids,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def gen_filter(
|
||||
subject: str, comparator: str, operator: str = "=="
|
||||
) -> dict[str, Union[bool, str]]:
|
||||
return {
|
||||
"clause": "WHERE",
|
||||
"comparator": comparator,
|
||||
"expressionType": "SIMPLE",
|
||||
"operator": operator,
|
||||
"subject": subject,
|
||||
}
|
||||
|
||||
|
||||
def load_data(tbl_name: str, database: Database, sample: bool = False) -> None:
|
||||
pdf = read_example_data("examples://birth_names2.json.gz", compression="gzip")
|
||||
|
||||
# TODO(bkyryliuk): move load examples data into the pytest fixture
|
||||
if database.backend == "presto":
|
||||
pdf.ds = pd.to_datetime(pdf.ds, unit="ms")
|
||||
pdf.ds = pdf.ds.dt.strftime("%Y-%m-%d %H:%M%:%S")
|
||||
else:
|
||||
pdf.ds = pd.to_datetime(pdf.ds, unit="ms")
|
||||
pdf = pdf.head(100) if sample else pdf
|
||||
|
||||
with database.get_sqla_engine() as engine:
|
||||
schema = inspect(engine).default_schema_name
|
||||
|
||||
pdf.to_sql(
|
||||
tbl_name,
|
||||
engine,
|
||||
schema=schema,
|
||||
if_exists="replace",
|
||||
chunksize=500,
|
||||
dtype={
|
||||
# TODO(bkyryliuk): use TIMESTAMP type for presto
|
||||
"ds": DateTime if database.backend != "presto" else String(255),
|
||||
"gender": String(16),
|
||||
"state": String(10),
|
||||
"name": String(255),
|
||||
},
|
||||
method="multi",
|
||||
index=False,
|
||||
)
|
||||
logger.debug("Done loading table!")
|
||||
logger.debug("-" * 80)
|
||||
|
||||
|
||||
def load_birth_names(
|
||||
only_metadata: bool = False, force: bool = False, sample: bool = False
|
||||
) -> None:
|
||||
"""Loading birth name dataset from a zip file in the repo"""
|
||||
database = get_example_database()
|
||||
with database.get_sqla_engine() as engine:
|
||||
schema = inspect(engine).default_schema_name
|
||||
|
||||
tbl_name = "birth_names"
|
||||
table_exists = database.has_table(Table(tbl_name, schema))
|
||||
|
||||
if not only_metadata and (not table_exists or force):
|
||||
load_data(tbl_name, database, sample=sample)
|
||||
|
||||
table = get_table_connector_registry()
|
||||
obj = db.session.query(table).filter_by(table_name=tbl_name, schema=schema).first()
|
||||
if not obj:
|
||||
logger.debug(f"Creating table [{tbl_name}] reference")
|
||||
obj = table(table_name=tbl_name, schema=schema)
|
||||
db.session.add(obj)
|
||||
|
||||
_set_table_metadata(obj, database)
|
||||
_add_table_metrics(obj)
|
||||
|
||||
slices, _ = create_slices(obj)
|
||||
create_dashboard(slices)
|
||||
|
||||
|
||||
def _set_table_metadata(datasource: SqlaTable, database: "Database") -> None:
|
||||
datasource.main_dttm_col = "ds"
|
||||
datasource.database = database
|
||||
datasource.filter_select_enabled = True
|
||||
datasource.fetch_metadata()
|
||||
|
||||
|
||||
def _add_table_metrics(datasource: SqlaTable) -> None:
|
||||
# By accessing the attribute first, we make sure `datasource.columns` and
|
||||
# `datasource.metrics` are already loaded. Otherwise accessing them later
|
||||
# may trigger an unnecessary and unexpected `after_update` event.
|
||||
columns, metrics = datasource.columns, datasource.metrics
|
||||
|
||||
if not any(col.column_name == "num_california" for col in columns):
|
||||
col_state = str(column("state").compile(db.engine))
|
||||
col_num = str(column("num").compile(db.engine))
|
||||
columns.append(
|
||||
TableColumn(
|
||||
column_name="num_california",
|
||||
expression=f"CASE WHEN {col_state} = 'CA' THEN {col_num} ELSE 0 END",
|
||||
)
|
||||
)
|
||||
|
||||
if not any(col.metric_name == "sum__num" for col in metrics):
|
||||
col = str(column("num").compile(db.engine))
|
||||
metrics.append(SqlMetric(metric_name="sum__num", expression=f"SUM({col})"))
|
||||
|
||||
for col in columns:
|
||||
if col.column_name == "ds": # type: ignore
|
||||
col.is_dttm = True # type: ignore
|
||||
break
|
||||
|
||||
datasource.columns = columns
|
||||
datasource.metrics = metrics
|
||||
|
||||
|
||||
def create_slices(tbl: SqlaTable) -> tuple[list[Slice], list[Slice]]:
|
||||
owner = security_manager.get_user_by_id(1)
|
||||
metrics = [
|
||||
{
|
||||
"expressionType": "SIMPLE",
|
||||
"column": {"column_name": "num", "type": "BIGINT"},
|
||||
"aggregate": "SUM",
|
||||
"label": "Births",
|
||||
"optionName": "metric_11",
|
||||
}
|
||||
]
|
||||
metric = "sum__num"
|
||||
|
||||
defaults = {
|
||||
"compare_lag": "10",
|
||||
"compare_suffix": "o10Y",
|
||||
"limit": "25",
|
||||
"granularity_sqla": "ds",
|
||||
"groupby": [],
|
||||
"row_limit": app.config["ROW_LIMIT"],
|
||||
"time_range": "100 years ago : now",
|
||||
"viz_type": "table",
|
||||
"markup_type": "markdown",
|
||||
}
|
||||
|
||||
default_query_context = {
|
||||
"result_format": "json",
|
||||
"result_type": "full",
|
||||
"datasource": {
|
||||
"id": tbl.id,
|
||||
"type": "table",
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"columns": [],
|
||||
"metrics": [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
slice_kwargs = {
|
||||
"datasource_id": tbl.id,
|
||||
"datasource_type": DatasourceType.TABLE,
|
||||
}
|
||||
|
||||
logger.debug("Creating some slices")
|
||||
slices = [
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Participants",
|
||||
viz_type="big_number",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="big_number",
|
||||
granularity_sqla="ds",
|
||||
compare_lag="5",
|
||||
compare_suffix="over 5Y",
|
||||
metric=metric,
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Genders",
|
||||
viz_type="pie",
|
||||
params=get_slice_json(
|
||||
defaults, viz_type="pie", groupby=["gender"], metric=metric
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Trends",
|
||||
viz_type="echarts_timeseries_line",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="echarts_timeseries_line",
|
||||
groupby=["name"],
|
||||
granularity_sqla="ds",
|
||||
rich_tooltip=True,
|
||||
show_legend=True,
|
||||
metrics=metrics,
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Genders by State",
|
||||
viz_type="echarts_timeseries_bar",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
adhoc_filters=[
|
||||
{
|
||||
"clause": "WHERE",
|
||||
"expressionType": "SIMPLE",
|
||||
"filterOptionName": "2745eae5",
|
||||
"comparator": ["other"],
|
||||
"operator": "NOT IN",
|
||||
"subject": "state",
|
||||
}
|
||||
],
|
||||
viz_type="echarts_timeseries_bar",
|
||||
metrics=[
|
||||
{
|
||||
"expressionType": "SIMPLE",
|
||||
"column": {"column_name": "num_boys", "type": "BIGINT(20)"},
|
||||
"aggregate": "SUM",
|
||||
"label": "Boys",
|
||||
"optionName": "metric_11",
|
||||
},
|
||||
{
|
||||
"expressionType": "SIMPLE",
|
||||
"column": {"column_name": "num_girls", "type": "BIGINT(20)"},
|
||||
"aggregate": "SUM",
|
||||
"label": "Girls",
|
||||
"optionName": "metric_12",
|
||||
},
|
||||
],
|
||||
groupby=["state"],
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Girls",
|
||||
viz_type="table",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
groupby=["name"],
|
||||
adhoc_filters=[gen_filter("gender", "girl")],
|
||||
row_limit=50,
|
||||
timeseries_limit_metric=metric,
|
||||
metrics=[metric],
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Girl Name Cloud",
|
||||
viz_type="word_cloud",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="word_cloud",
|
||||
size_from="10",
|
||||
series="name",
|
||||
size_to="70",
|
||||
rotation="square",
|
||||
limit="100",
|
||||
adhoc_filters=[gen_filter("gender", "girl")],
|
||||
metric=metric,
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Boys",
|
||||
viz_type="table",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
groupby=["name"],
|
||||
adhoc_filters=[gen_filter("gender", "boy")],
|
||||
row_limit=50,
|
||||
timeseries_limit_metric=metric,
|
||||
metrics=[metric],
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Boy Name Cloud",
|
||||
viz_type="word_cloud",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="word_cloud",
|
||||
size_from="10",
|
||||
series="name",
|
||||
size_to="70",
|
||||
rotation="square",
|
||||
limit="100",
|
||||
adhoc_filters=[gen_filter("gender", "boy")],
|
||||
metric=metric,
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Top 10 Girl Name Share",
|
||||
viz_type="echarts_area",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
adhoc_filters=[gen_filter("gender", "girl")],
|
||||
comparison_type="values",
|
||||
groupby=["name"],
|
||||
limit=10,
|
||||
stacked_style="expand",
|
||||
time_grain_sqla="P1D",
|
||||
viz_type="echarts_area",
|
||||
x_axis_forma="smart_date",
|
||||
metrics=metrics,
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Top 10 Boy Name Share",
|
||||
viz_type="echarts_area",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
adhoc_filters=[gen_filter("gender", "boy")],
|
||||
comparison_type="values",
|
||||
groupby=["name"],
|
||||
limit=10,
|
||||
stacked_style="expand",
|
||||
time_grain_sqla="P1D",
|
||||
viz_type="echarts_area",
|
||||
x_axis_forma="smart_date",
|
||||
metrics=metrics,
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Pivot Table v2",
|
||||
viz_type="pivot_table_v2",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="pivot_table_v2",
|
||||
groupbyRows=["name"],
|
||||
groupbyColumns=["state"],
|
||||
metrics=[metric],
|
||||
),
|
||||
query_context=get_slice_json(
|
||||
default_query_context,
|
||||
queries=[
|
||||
{
|
||||
"columns": ["name", "state"],
|
||||
"metrics": [metric],
|
||||
}
|
||||
],
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
]
|
||||
misc_slices = [
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Average and Sum Trends",
|
||||
viz_type="mixed_timeseries",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="mixed_timeseries",
|
||||
metrics=[
|
||||
{
|
||||
"expressionType": "SIMPLE",
|
||||
"column": {"column_name": "num", "type": "BIGINT(20)"},
|
||||
"aggregate": "AVG",
|
||||
"label": "AVG(num)",
|
||||
"optionName": "metric_vgops097wej_g8uff99zhk7",
|
||||
}
|
||||
],
|
||||
metrics_b=["sum__num"],
|
||||
granularity_sqla="ds",
|
||||
yAxisIndex=0,
|
||||
yAxisIndexB=1,
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Num Births Trend",
|
||||
viz_type="echarts_timeseries_line",
|
||||
params=get_slice_json(
|
||||
defaults, viz_type="echarts_timeseries_line", metrics=metrics
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Daily Totals",
|
||||
viz_type="table",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
groupby=["ds"],
|
||||
time_range="1983 : 2023",
|
||||
viz_type="table",
|
||||
metrics=metrics,
|
||||
),
|
||||
query_context=get_slice_json(
|
||||
default_query_context,
|
||||
queries=[
|
||||
{
|
||||
"columns": ["ds"],
|
||||
"metrics": metrics,
|
||||
"time_range": "1983 : 2023",
|
||||
}
|
||||
],
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Number of California Births",
|
||||
viz_type="big_number_total",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
metric={
|
||||
"expressionType": "SIMPLE",
|
||||
"column": {
|
||||
"column_name": "num_california",
|
||||
"expression": "CASE WHEN state = 'CA' THEN num ELSE 0 END",
|
||||
},
|
||||
"aggregate": "SUM",
|
||||
"label": "SUM(num_california)",
|
||||
},
|
||||
viz_type="big_number_total",
|
||||
granularity_sqla="ds",
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Top 10 California Names Timeseries",
|
||||
viz_type="echarts_timeseries_line",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
metrics=[
|
||||
{
|
||||
"expressionType": "SIMPLE",
|
||||
"column": {
|
||||
"column_name": "num_california",
|
||||
"expression": "CASE WHEN state = 'CA' THEN num ELSE 0 END",
|
||||
},
|
||||
"aggregate": "SUM",
|
||||
"label": "SUM(num_california)",
|
||||
}
|
||||
],
|
||||
viz_type="echarts_timeseries_line",
|
||||
granularity_sqla="ds",
|
||||
groupby=["name"],
|
||||
timeseries_limit_metric={
|
||||
"expressionType": "SIMPLE",
|
||||
"column": {
|
||||
"column_name": "num_california",
|
||||
"expression": "CASE WHEN state = 'CA' THEN num ELSE 0 END",
|
||||
},
|
||||
"aggregate": "SUM",
|
||||
"label": "SUM(num_california)",
|
||||
},
|
||||
limit="10",
|
||||
),
|
||||
owners=[owner] if owner else [],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Names Sorted by Num in California",
|
||||
viz_type="table",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
metrics=metrics,
|
||||
groupby=["name"],
|
||||
row_limit=50,
|
||||
timeseries_limit_metric={
|
||||
"expressionType": "SIMPLE",
|
||||
"column": {
|
||||
"column_name": "num_california",
|
||||
"expression": "CASE WHEN state = 'CA' THEN num ELSE 0 END",
|
||||
},
|
||||
"aggregate": "SUM",
|
||||
"label": "SUM(num_california)",
|
||||
},
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Number of Girls",
|
||||
viz_type="big_number_total",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
metric=metric,
|
||||
viz_type="big_number_total",
|
||||
granularity_sqla="ds",
|
||||
adhoc_filters=[gen_filter("gender", "girl")],
|
||||
subheader="total female participants",
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Pivot Table",
|
||||
viz_type="pivot_table_v2",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="pivot_table_v2",
|
||||
groupbyRows=["name"],
|
||||
groupbyColumns=["state"],
|
||||
metrics=metrics,
|
||||
),
|
||||
owners=[],
|
||||
),
|
||||
]
|
||||
for slc in slices:
|
||||
merge_slice(slc)
|
||||
|
||||
for slc in misc_slices:
|
||||
merge_slice(slc)
|
||||
misc_dash_slices.add(slc.slice_name)
|
||||
|
||||
return slices, misc_slices
|
||||
|
||||
|
||||
def create_dashboard(slices: list[Slice]) -> Dashboard:
|
||||
logger.debug("Creating a dashboard")
|
||||
dash = db.session.query(Dashboard).filter_by(slug="births").first()
|
||||
if not dash:
|
||||
dash = Dashboard()
|
||||
db.session.add(dash)
|
||||
|
||||
dash.published = True
|
||||
dash.json_metadata = textwrap.dedent(
|
||||
"""\
|
||||
{
|
||||
"label_colors": {
|
||||
"Girls": "#FF69B4",
|
||||
"Boys": "#ADD8E6",
|
||||
"girl": "#FF69B4",
|
||||
"boy": "#ADD8E6"
|
||||
}
|
||||
}"""
|
||||
)
|
||||
# pylint: disable=line-too-long
|
||||
pos = json.loads( # noqa: TID251
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
{
|
||||
"CHART-6GdlekVise": {
|
||||
"children": [],
|
||||
"id": "CHART-6GdlekVise",
|
||||
"meta": {
|
||||
"chartId": 5547,
|
||||
"height": 50,
|
||||
"sliceName": "Top 10 Girl Name Share",
|
||||
"width": 5
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-eh0w37bWbR"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-6n9jxb30JG": {
|
||||
"children": [],
|
||||
"id": "CHART-6n9jxb30JG",
|
||||
"meta": {
|
||||
"chartId": 5540,
|
||||
"height": 36,
|
||||
"sliceName": "Genders by State",
|
||||
"width": 5
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW--EyBZQlDi"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-Jj9qh1ol-N": {
|
||||
"children": [],
|
||||
"id": "CHART-Jj9qh1ol-N",
|
||||
"meta": {
|
||||
"chartId": 5545,
|
||||
"height": 50,
|
||||
"sliceName": "Boy Name Cloud",
|
||||
"width": 4
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-kzWtcvo8R1"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-ODvantb_bF": {
|
||||
"children": [],
|
||||
"id": "CHART-ODvantb_bF",
|
||||
"meta": {
|
||||
"chartId": 5548,
|
||||
"height": 50,
|
||||
"sliceName": "Top 10 Boy Name Share",
|
||||
"width": 5
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-kzWtcvo8R1"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-PAXUUqwmX9": {
|
||||
"children": [],
|
||||
"id": "CHART-PAXUUqwmX9",
|
||||
"meta": {
|
||||
"chartId": 5538,
|
||||
"height": 34,
|
||||
"sliceName": "Genders",
|
||||
"width": 3
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-2n0XgiHDgs"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-_T6n_K9iQN": {
|
||||
"children": [],
|
||||
"id": "CHART-_T6n_K9iQN",
|
||||
"meta": {
|
||||
"chartId": 5539,
|
||||
"height": 36,
|
||||
"sliceName": "Trends",
|
||||
"width": 7
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW--EyBZQlDi"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-eNY0tcE_ic": {
|
||||
"children": [],
|
||||
"id": "CHART-eNY0tcE_ic",
|
||||
"meta": {
|
||||
"chartId": 5537,
|
||||
"height": 34,
|
||||
"sliceName": "Participants",
|
||||
"width": 3
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-2n0XgiHDgs"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-g075mMgyYb": {
|
||||
"children": [],
|
||||
"id": "CHART-g075mMgyYb",
|
||||
"meta": {
|
||||
"chartId": 5541,
|
||||
"height": 50,
|
||||
"sliceName": "Girls",
|
||||
"width": 3
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-eh0w37bWbR"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-n-zGGE6S1y": {
|
||||
"children": [],
|
||||
"id": "CHART-n-zGGE6S1y",
|
||||
"meta": {
|
||||
"chartId": 5542,
|
||||
"height": 50,
|
||||
"sliceName": "Girl Name Cloud",
|
||||
"width": 4
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-eh0w37bWbR"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"CHART-vJIPjmcbD3": {
|
||||
"children": [],
|
||||
"id": "CHART-vJIPjmcbD3",
|
||||
"meta": {
|
||||
"chartId": 5543,
|
||||
"height": 50,
|
||||
"sliceName": "Boys",
|
||||
"width": 3
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-kzWtcvo8R1"
|
||||
],
|
||||
"type": "CHART"
|
||||
},
|
||||
"DASHBOARD_VERSION_KEY": "v2",
|
||||
"GRID_ID": {
|
||||
"children": [
|
||||
"ROW-2n0XgiHDgs",
|
||||
"ROW--EyBZQlDi",
|
||||
"ROW-eh0w37bWbR",
|
||||
"ROW-kzWtcvo8R1"
|
||||
],
|
||||
"id": "GRID_ID",
|
||||
"parents": [
|
||||
"ROOT_ID"
|
||||
],
|
||||
"type": "GRID"
|
||||
},
|
||||
"HEADER_ID": {
|
||||
"id": "HEADER_ID",
|
||||
"meta": {
|
||||
"text": "Births"
|
||||
},
|
||||
"type": "HEADER"
|
||||
},
|
||||
"MARKDOWN-zaflB60tbC": {
|
||||
"children": [],
|
||||
"id": "MARKDOWN-zaflB60tbC",
|
||||
"meta": {
|
||||
"code": "<div style=\\"text-align:center\\"> <h1>Birth Names Dashboard</h1> <img src=\\"/static/assets/images/babies.png\\" style=\\"width:50%;\\"></div>",
|
||||
"height": 34,
|
||||
"width": 6
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID",
|
||||
"ROW-2n0XgiHDgs"
|
||||
],
|
||||
"type": "MARKDOWN"
|
||||
},
|
||||
"ROOT_ID": {
|
||||
"children": [
|
||||
"GRID_ID"
|
||||
],
|
||||
"id": "ROOT_ID",
|
||||
"type": "ROOT"
|
||||
},
|
||||
"ROW--EyBZQlDi": {
|
||||
"children": [
|
||||
"CHART-_T6n_K9iQN",
|
||||
"CHART-6n9jxb30JG"
|
||||
],
|
||||
"id": "ROW--EyBZQlDi",
|
||||
"meta": {
|
||||
"background": "BACKGROUND_TRANSPARENT"
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID"
|
||||
],
|
||||
"type": "ROW"
|
||||
},
|
||||
"ROW-2n0XgiHDgs": {
|
||||
"children": [
|
||||
"CHART-eNY0tcE_ic",
|
||||
"MARKDOWN-zaflB60tbC",
|
||||
"CHART-PAXUUqwmX9"
|
||||
],
|
||||
"id": "ROW-2n0XgiHDgs",
|
||||
"meta": {
|
||||
"background": "BACKGROUND_TRANSPARENT"
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID"
|
||||
],
|
||||
"type": "ROW"
|
||||
},
|
||||
"ROW-eh0w37bWbR": {
|
||||
"children": [
|
||||
"CHART-g075mMgyYb",
|
||||
"CHART-n-zGGE6S1y",
|
||||
"CHART-6GdlekVise"
|
||||
],
|
||||
"id": "ROW-eh0w37bWbR",
|
||||
"meta": {
|
||||
"background": "BACKGROUND_TRANSPARENT"
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID"
|
||||
],
|
||||
"type": "ROW"
|
||||
},
|
||||
"ROW-kzWtcvo8R1": {
|
||||
"children": [
|
||||
"CHART-vJIPjmcbD3",
|
||||
"CHART-Jj9qh1ol-N",
|
||||
"CHART-ODvantb_bF"
|
||||
],
|
||||
"id": "ROW-kzWtcvo8R1",
|
||||
"meta": {
|
||||
"background": "BACKGROUND_TRANSPARENT"
|
||||
},
|
||||
"parents": [
|
||||
"ROOT_ID",
|
||||
"GRID_ID"
|
||||
],
|
||||
"type": "ROW"
|
||||
}
|
||||
}
|
||||
""" # noqa: E501
|
||||
)
|
||||
)
|
||||
# pylint: enable=line-too-long
|
||||
# dashboard v2 doesn't allow add markup slice
|
||||
dash.slices = [slc for slc in slices if slc.viz_type != "markup"]
|
||||
update_slice_ids(pos)
|
||||
dash.dashboard_title = "USA Births Names"
|
||||
dash.position_json = json.dumps(pos, indent=4) # noqa: TID251
|
||||
dash.slug = "births"
|
||||
return dash
|
||||
@@ -0,0 +1,26 @@
|
||||
slice_name: Birth in France by department in 2016
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: country_map
|
||||
params:
|
||||
entity: DEPT_ID
|
||||
granularity_sqla: ''
|
||||
metric:
|
||||
aggregate: AVG
|
||||
column:
|
||||
column_name: '2004'
|
||||
type: INT
|
||||
expressionType: SIMPLE
|
||||
label: Boys
|
||||
optionName: metric_112342
|
||||
row_limit: 500000
|
||||
select_country: france
|
||||
since: ''
|
||||
until: ''
|
||||
viz_type: country_map
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 6bd584f1-0ef5-44fc-8a05-61400f83bb62
|
||||
version: 1.0.0
|
||||
dataset_uuid: c21dd48d-9a4b-4a08-a926-47c3601c2a8d
|
||||
@@ -0,0 +1,21 @@
|
||||
slice_name: OSM Long/Lat
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: osm
|
||||
params:
|
||||
all_columns:
|
||||
- occupancy
|
||||
all_columns_x: LON
|
||||
all_columns_y: LAT
|
||||
granularity_sqla: day
|
||||
mapbox_style: https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
row_limit: 500000
|
||||
since: '2014-01-01'
|
||||
until: now
|
||||
viz_type: mapbox
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: a4e90860-c8f5-4c50-8c04-06b2e144809c
|
||||
version: 1.0.0
|
||||
dataset_uuid: 605eaec7-ebf1-4fea-ac4b-07652fcb46e7
|
||||
@@ -0,0 +1,31 @@
|
||||
slice_name: Parallel Coordinates
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: para
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby: []
|
||||
limit: 100
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- sum__SP_POP_TOTL
|
||||
- sum__SP_RUR_TOTL_ZS
|
||||
- sum__SH_DYN_AIDS
|
||||
row_limit: 50000
|
||||
secondary_metric: sum__SP_POP_TOTL
|
||||
series: country_name
|
||||
show_bubbles: true
|
||||
since: '2011-01-01'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: '2012-01-01'
|
||||
viz_type: para
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 041377c4-0ca9-4a40-8abd-befcd137c0dc
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,30 @@
|
||||
slice_name: Pivot Table v2
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: pivot_table_v2
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby: []
|
||||
groupbyColumns:
|
||||
- state
|
||||
groupbyRows:
|
||||
- name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- sum__num
|
||||
row_limit: 50000
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: pivot_table_v2
|
||||
query_context: "{\n \"datasource\": {\n \"id\": 2,\n \"type\": \"\
|
||||
table\"\n },\n \"queries\": [\n {\n \"columns\": [\n \
|
||||
\ \"name\",\n \"state\"\n ],\n \
|
||||
\ \"metrics\": [\n \"sum__num\"\n ]\n }\n ],\n\
|
||||
\ \"result_format\": \"json\",\n \"result_type\": \"full\"\n}"
|
||||
cache_timeout: null
|
||||
uuid: 86778b63-19d8-4278-a79f-c90a1b31e162
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,32 @@
|
||||
slice_name: Average and Sum Trends
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: mixed_timeseries
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: AVG
|
||||
column:
|
||||
column_name: num
|
||||
type: BIGINT(20)
|
||||
expressionType: SIMPLE
|
||||
label: AVG(num)
|
||||
optionName: metric_vgops097wej_g8uff99zhk7
|
||||
metrics_b:
|
||||
- sum__num
|
||||
row_limit: 50000
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: mixed_timeseries
|
||||
yAxisIndex: 0
|
||||
yAxisIndexB: 1
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 9c690f97-9196-5e01-bec9-8f4975ea5108
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,31 @@
|
||||
slice_name: Boy Name Cloud
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: word_cloud
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator: boy
|
||||
expressionType: SIMPLE
|
||||
operator: ==
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby: []
|
||||
limit: '100'
|
||||
markup_type: markdown
|
||||
metric: sum__num
|
||||
rotation: square
|
||||
row_limit: 50000
|
||||
series: name
|
||||
size_from: '10'
|
||||
size_to: '70'
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: word_cloud
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 6994ec83-0cf2-4a26-97e2-1e30b0002aa0
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
30
superset/examples/configs/charts/USA Births Names/Boys.yaml
Normal file
30
superset/examples/configs/charts/USA Births Names/Boys.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
slice_name: Boys
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: table
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator: boy
|
||||
expressionType: SIMPLE
|
||||
operator: ==
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- sum__num
|
||||
row_limit: 50
|
||||
time_range: '100 years ago : now'
|
||||
timeseries_limit_metric: sum__num
|
||||
viz_type: table
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 0af97164-82f0-42bb-a611-7093e5c56596
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,28 @@
|
||||
slice_name: Daily Totals
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: table
|
||||
params:
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- ds
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num
|
||||
type: BIGINT
|
||||
expressionType: SIMPLE
|
||||
label: Births
|
||||
optionName: metric_11
|
||||
row_limit: 50
|
||||
time_range: '1983 : 2023'
|
||||
timeseries_limit_metric: sum__num
|
||||
viz_type: table
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: a3d4f2e1-8c9b-4d2a-9e7f-1b6c8d5e2f4a
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,22 @@
|
||||
slice_name: Genders
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: pie
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- gender
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metric: sum__num
|
||||
row_limit: 50000
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: pie
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: fb05dca0-bd3e-4953-a0a5-94b51de3a653
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,44 @@
|
||||
slice_name: Genders by State
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_timeseries_bar
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator:
|
||||
- other
|
||||
expressionType: SIMPLE
|
||||
filterOptionName: 2745eae5
|
||||
operator: NOT IN
|
||||
subject: state
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- state
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num_boys
|
||||
type: BIGINT(20)
|
||||
expressionType: SIMPLE
|
||||
label: Boys
|
||||
optionName: metric_11
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num_girls
|
||||
type: BIGINT(20)
|
||||
expressionType: SIMPLE
|
||||
label: Girls
|
||||
optionName: metric_12
|
||||
row_limit: 50000
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: echarts_timeseries_bar
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 2cc25185-3d8c-494c-aa3c-14f081ac7e54
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,31 @@
|
||||
slice_name: Girl Name Cloud
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: word_cloud
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator: girl
|
||||
expressionType: SIMPLE
|
||||
operator: ==
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby: []
|
||||
limit: '100'
|
||||
markup_type: markdown
|
||||
metric: sum__num
|
||||
rotation: square
|
||||
row_limit: 50000
|
||||
series: name
|
||||
size_from: '10'
|
||||
size_to: '70'
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: word_cloud
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: ba6574fe-a6c0-41ef-9499-1ea6ff36bd2d
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
30
superset/examples/configs/charts/USA Births Names/Girls.yaml
Normal file
30
superset/examples/configs/charts/USA Births Names/Girls.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
slice_name: Girls
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: table
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator: girl
|
||||
expressionType: SIMPLE
|
||||
operator: ==
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- sum__num
|
||||
row_limit: 50
|
||||
time_range: '100 years ago : now'
|
||||
timeseries_limit_metric: sum__num
|
||||
viz_type: table
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 44cfa30e-af8e-4176-8612-4df0c0609516
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,36 @@
|
||||
slice_name: Names Sorted by Num in California
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: table
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num
|
||||
type: BIGINT
|
||||
expressionType: SIMPLE
|
||||
label: Births
|
||||
optionName: metric_11
|
||||
row_limit: 50
|
||||
time_range: '100 years ago : now'
|
||||
timeseries_limit_metric:
|
||||
aggregate: SUM
|
||||
column:
|
||||
column_name: num_california
|
||||
expression: CASE WHEN state = 'CA' THEN num ELSE 0 END
|
||||
expressionType: SIMPLE
|
||||
label: SUM(num_california)
|
||||
viz_type: table
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: e49ed2c4-b8a3-5736-bafe-4658790b113a
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,31 @@
|
||||
slice_name: Num Births Trend
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_timeseries_line
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num
|
||||
type: BIGINT
|
||||
expressionType: SIMPLE
|
||||
label: Births
|
||||
optionName: metric_11
|
||||
rich_tooltip: true
|
||||
row_limit: 50000
|
||||
show_legend: true
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: echarts_timeseries_line
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 5b8c76e5-0e5e-45c1-b07e-3b2cb9b9c7e8
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,27 @@
|
||||
slice_name: Number of California Births
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: big_number_total
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metric:
|
||||
aggregate: SUM
|
||||
column:
|
||||
column_name: num_california
|
||||
expression: CASE WHEN state = 'CA' THEN num ELSE 0 END
|
||||
expressionType: SIMPLE
|
||||
label: SUM(num_california)
|
||||
row_limit: 50000
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: big_number_total
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 400ee69f-eda4-5fe8-bc30-299184e08048
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,28 @@
|
||||
slice_name: Number of Girls
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: big_number_total
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator: girl
|
||||
expressionType: SIMPLE
|
||||
operator: ==
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metric: sum__num
|
||||
row_limit: 50000
|
||||
subheader: total female participants
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: big_number_total
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 2f1a8720-7ea6-5b0f-b419-b75163f6bf17
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,21 @@
|
||||
slice_name: Participants
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: big_number
|
||||
params:
|
||||
compare_lag: '5'
|
||||
compare_suffix: over 5Y
|
||||
granularity_sqla: ds
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metric: sum__num
|
||||
row_limit: 50000
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: big_number
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 89ae3c32-eafa-4466-82cf-8c4328420782
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,32 @@
|
||||
slice_name: Pivot Table
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: pivot_table_v2
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby: []
|
||||
groupbyColumns:
|
||||
- state
|
||||
groupbyRows:
|
||||
- name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num
|
||||
type: BIGINT
|
||||
expressionType: SIMPLE
|
||||
label: Births
|
||||
optionName: metric_11
|
||||
row_limit: 50000
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: pivot_table_v2
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: b9038f33-aea3-52de-840b-0a32f4c0eb41
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,39 @@
|
||||
slice_name: Top 10 Boy Name Share
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_area
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator: boy
|
||||
expressionType: SIMPLE
|
||||
operator: ==
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
comparison_type: values
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: 10
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num
|
||||
type: BIGINT
|
||||
expressionType: SIMPLE
|
||||
label: Births
|
||||
optionName: metric_11
|
||||
row_limit: 50000
|
||||
stacked_style: expand
|
||||
time_grain_sqla: P1D
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: echarts_area
|
||||
x_axis_forma: smart_date
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: f35cca46-bb11-440e-8ba1-7f021bfe52a7
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,35 @@
|
||||
slice_name: Top 10 California Names Timeseries
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_timeseries_line
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '10'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num_california
|
||||
expression: CASE WHEN state = 'CA' THEN num ELSE 0 END
|
||||
expressionType: SIMPLE
|
||||
label: SUM(num_california)
|
||||
row_limit: 50000
|
||||
time_range: '100 years ago : now'
|
||||
timeseries_limit_metric:
|
||||
aggregate: SUM
|
||||
column:
|
||||
column_name: num_california
|
||||
expression: CASE WHEN state = 'CA' THEN num ELSE 0 END
|
||||
expressionType: SIMPLE
|
||||
label: SUM(num_california)
|
||||
viz_type: echarts_timeseries_line
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 6a587b9e-e28b-5c2a-abb9-c6c1f4fd56b5
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,39 @@
|
||||
slice_name: Top 10 Girl Name Share
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_area
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator: girl
|
||||
expressionType: SIMPLE
|
||||
operator: ==
|
||||
subject: gender
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
comparison_type: values
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: 10
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num
|
||||
type: BIGINT
|
||||
expressionType: SIMPLE
|
||||
label: Births
|
||||
optionName: metric_11
|
||||
row_limit: 50000
|
||||
stacked_style: expand
|
||||
time_grain_sqla: P1D
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: echarts_area
|
||||
x_axis_forma: smart_date
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: da76899a-d75c-467b-b0ce-cfa4819ed1b1
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,31 @@
|
||||
slice_name: Trends
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_timeseries_line
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
granularity_sqla: ds
|
||||
groupby:
|
||||
- name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: num
|
||||
type: BIGINT
|
||||
expressionType: SIMPLE
|
||||
label: Births
|
||||
optionName: metric_11
|
||||
rich_tooltip: true
|
||||
row_limit: 50000
|
||||
show_legend: true
|
||||
time_range: '100 years ago : now'
|
||||
viz_type: echarts_timeseries_line
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: c6024db9-1695-4aa6-b846-42d9c96bfcbf
|
||||
version: 1.0.0
|
||||
dataset_uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
@@ -0,0 +1,120 @@
|
||||
slice_name: Rise & Fall of Video Game Consoles
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_area
|
||||
params:
|
||||
adhoc_filters: []
|
||||
annotation_layers: []
|
||||
bottom_margin: auto
|
||||
color_scheme: supersetColors
|
||||
comparison_type: values
|
||||
contribution: false
|
||||
datasource: 21__table
|
||||
granularity_sqla: year
|
||||
groupby:
|
||||
- platform
|
||||
label_colors:
|
||||
'0': '#1FA8C9'
|
||||
'1': '#454E7C'
|
||||
'2600': '#666666'
|
||||
3DO: '#B2B2B2'
|
||||
3DS: '#D1C6BC'
|
||||
Action: '#1FA8C9'
|
||||
Adventure: '#454E7C'
|
||||
DC: '#A38F79'
|
||||
DS: '#8FD3E4'
|
||||
Europe: '#5AC189'
|
||||
Fighting: '#5AC189'
|
||||
GB: '#FDE380'
|
||||
GBA: '#ACE1C4'
|
||||
GC: '#5AC189'
|
||||
GEN: '#3CCCCB'
|
||||
GG: '#EFA1AA'
|
||||
Japan: '#FF7F44'
|
||||
Microsoft Game Studios: '#D1C6BC'
|
||||
Misc: '#FF7F44'
|
||||
N64: '#1FA8C9'
|
||||
NES: '#9EE5E5'
|
||||
NG: '#A1A6BD'
|
||||
Nintendo: '#D3B3DA'
|
||||
North America: '#666666'
|
||||
Other: '#E04355'
|
||||
PC: '#EFA1AA'
|
||||
PCFX: '#FDE380'
|
||||
PS: '#A1A6BD'
|
||||
PS2: '#FCC700'
|
||||
PS3: '#3CCCCB'
|
||||
PS4: '#B2B2B2'
|
||||
PSP: '#FEC0A1'
|
||||
PSV: '#FCC700'
|
||||
Platform: '#666666'
|
||||
Puzzle: '#E04355'
|
||||
Racing: '#FCC700'
|
||||
Role-Playing: '#A868B7'
|
||||
SAT: '#A868B7'
|
||||
SCD: '#8FD3E4'
|
||||
SNES: '#454E7C'
|
||||
Shooter: '#3CCCCB'
|
||||
Simulation: '#A38F79'
|
||||
Sports: '#8FD3E4'
|
||||
Strategy: '#A1A6BD'
|
||||
TG16: '#FEC0A1'
|
||||
Take-Two Interactive: '#9EE5E5'
|
||||
WS: '#ACE1C4'
|
||||
Wii: '#A38F79'
|
||||
WiiU: '#E04355'
|
||||
X360: '#A868B7'
|
||||
XB: '#D3B3DA'
|
||||
XOne: '#FF7F44'
|
||||
line_interpolation: linear
|
||||
metrics:
|
||||
- aggregate: SUM
|
||||
column:
|
||||
column_name: global_sales
|
||||
description: null
|
||||
expression: null
|
||||
filterable: true
|
||||
groupby: true
|
||||
id: 887
|
||||
is_dttm: false
|
||||
optionName: _col_Global_Sales
|
||||
python_date_format: null
|
||||
type: DOUBLE PRECISION
|
||||
verbose_name: null
|
||||
expressionType: SIMPLE
|
||||
hasCustomLabel: false
|
||||
isNew: false
|
||||
label: SUM(Global_Sales)
|
||||
optionName: metric_ufl75addr8c_oqqhdumirpn
|
||||
sqlExpression: null
|
||||
order_desc: true
|
||||
queryFields:
|
||||
groupby: groupby
|
||||
metrics: metrics
|
||||
rich_tooltip: true
|
||||
rolling_type: None
|
||||
row_limit: null
|
||||
show_brush: auto
|
||||
show_legend: false
|
||||
slice_id: 659
|
||||
stacked_style: stream
|
||||
time_grain_sqla: null
|
||||
time_range: No filter
|
||||
url_params:
|
||||
preselect_filters: '{"1389": {"platform": ["PS", "PS2", "PS3", "PS4"], "genre":
|
||||
null, "__time_range": "No filter"}}'
|
||||
viz_type: echarts_area
|
||||
x_axis_format: smart_date
|
||||
x_axis_label: Year Published
|
||||
x_axis_showminmax: true
|
||||
x_ticks_layout: auto
|
||||
y_axis_bounds:
|
||||
- null
|
||||
- null
|
||||
y_axis_format: SMART_NUMBER
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 3d926244-6e32-5e42-8ade-7302b83a65d7
|
||||
version: 1.0.0
|
||||
dataset_uuid: 53d47c0c-c03d-47f0-b9ac-81225f808283
|
||||
@@ -0,0 +1,30 @@
|
||||
slice_name: Box plot
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: box_plot
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby:
|
||||
- region
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- sum__SP_POP_TOTL
|
||||
row_limit: 50000
|
||||
show_bubbles: true
|
||||
since: '1960-01-01'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: now
|
||||
viz_type: box_plot
|
||||
whisker_options: Min/max (no outliers)
|
||||
x_ticks_layout: staggered
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: d31ba9c7-798b-4f84-87ef-ab31721680a8
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,29 @@
|
||||
slice_name: Growth Rate
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_timeseries_line
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby:
|
||||
- country_name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- sum__SP_POP_TOTL
|
||||
num_period_compare: '10'
|
||||
row_limit: 50000
|
||||
show_bubbles: true
|
||||
since: '1960-01-01'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: '2014-01-02'
|
||||
viz_type: echarts_timeseries_line
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: cfcd7c5e-4759-4b28-bb7c-e2200508e978
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,51 @@
|
||||
slice_name: Life Expectancy VS Rural %
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: bubble
|
||||
params:
|
||||
adhoc_filters:
|
||||
- clause: WHERE
|
||||
comparator:
|
||||
- TCA
|
||||
- MNP
|
||||
- DMA
|
||||
- MHL
|
||||
- MCO
|
||||
- SXM
|
||||
- CYM
|
||||
- TUV
|
||||
- IMY
|
||||
- KNA
|
||||
- ASM
|
||||
- ADO
|
||||
- AMA
|
||||
- PLW
|
||||
expressionType: SIMPLE
|
||||
filterOptionName: 2745eae5
|
||||
operator: NOT IN
|
||||
subject: country_code
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_name
|
||||
granularity_sqla: year
|
||||
groupby: []
|
||||
limit: 0
|
||||
markup_type: markdown
|
||||
max_bubble_size: '50'
|
||||
row_limit: 50000
|
||||
series: region
|
||||
show_bubbles: true
|
||||
since: '2011-01-01'
|
||||
size: sum__SP_POP_TOTL
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: '2011-01-02'
|
||||
viz_type: bubble
|
||||
x: sum__SP_RUR_TOTL_ZS
|
||||
y: sum__SP_DYN_LE00_IN
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: fa927236-7b66-4d03-ae6c-463d2d394123
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,28 @@
|
||||
slice_name: Most Populated Countries
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: table
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby:
|
||||
- country_name
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- sum__SP_POP_TOTL
|
||||
row_limit: 50000
|
||||
show_bubbles: true
|
||||
since: '2014-01-01'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: '2014-01-02'
|
||||
viz_type: table
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 4183745e-1cc4-4f88-9ae6-973c69845ce4
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,36 @@
|
||||
slice_name: '% Rural'
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: world_map
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metric: sum__SP_RUR_TOTL_ZS
|
||||
num_period_compare: '10'
|
||||
row_limit: 50000
|
||||
secondary_metric:
|
||||
aggregate: SUM
|
||||
column:
|
||||
column_name: SP_RUR_TOTL
|
||||
optionName: _col_SP_RUR_TOTL
|
||||
type: DOUBLE
|
||||
expressionType: SIMPLE
|
||||
hasCustomLabel: true
|
||||
label: Rural Population
|
||||
show_bubbles: true
|
||||
since: '2014-01-01'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: '2014-01-02'
|
||||
viz_type: world_map
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 8d889488-edb5-40cb-a69c-e2c14f009e2b
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,38 @@
|
||||
slice_name: Rural Breakdown
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: sunburst_v2
|
||||
params:
|
||||
columns:
|
||||
- region
|
||||
- country_name
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metric: sum__SP_POP_TOTL
|
||||
row_limit: 50000
|
||||
secondary_metric:
|
||||
aggregate: SUM
|
||||
column:
|
||||
column_name: SP_RUR_TOTL
|
||||
optionName: _col_SP_RUR_TOTL
|
||||
type: DOUBLE
|
||||
expressionType: SIMPLE
|
||||
hasCustomLabel: true
|
||||
label: Rural Population
|
||||
show_bubbles: true
|
||||
since: '2011-01-01'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: '2011-01-02'
|
||||
viz_type: sunburst_v2
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 70a2e07b-0f45-4532-96ae-0c6db52d2e7c
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,28 @@
|
||||
slice_name: Treemap
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: treemap_v2
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby:
|
||||
- region
|
||||
- country_code
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metric: sum__SP_POP_TOTL
|
||||
row_limit: 50000
|
||||
show_bubbles: true
|
||||
since: '1960-01-01'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: now
|
||||
viz_type: treemap_v2
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: fc941a12-88a0-42e7-ac48-c1ec4ed84640
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,28 @@
|
||||
slice_name: World's Pop Growth
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: echarts_area
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: o10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby:
|
||||
- region
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metrics:
|
||||
- sum__SP_POP_TOTL
|
||||
row_limit: 50000
|
||||
show_bubbles: true
|
||||
since: '1960-01-01'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: now
|
||||
viz_type: echarts_area
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: e18b5d28-3a3d-43ea-8a20-b198b44b08e3
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,26 @@
|
||||
slice_name: World's Population
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: big_number
|
||||
params:
|
||||
compare_lag: '10'
|
||||
compare_suffix: over 10Y
|
||||
country_fieldtype: cca3
|
||||
entity: country_code
|
||||
granularity_sqla: year
|
||||
groupby: []
|
||||
limit: '25'
|
||||
markup_type: markdown
|
||||
metric: sum__SP_POP_TOTL
|
||||
row_limit: 50000
|
||||
show_bubbles: true
|
||||
since: '2000'
|
||||
time_range: '2014-01-01 : 2014-01-02'
|
||||
until: '2014-01-02'
|
||||
viz_type: big_number
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: c50fc6e3-96fc-4e72-877b-2ea1a5e25c7a
|
||||
version: 1.0.0
|
||||
dataset_uuid: 3b851597-e0e9-42a1-83e4-55547811742e
|
||||
@@ -0,0 +1,48 @@
|
||||
slice_name: Deck.gl Arcs
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: deck_arc
|
||||
params:
|
||||
color_picker:
|
||||
a: 1
|
||||
b: 135
|
||||
g: 122
|
||||
r: 0
|
||||
datasource: 10__table
|
||||
end_spatial:
|
||||
latCol: LATITUDE_DEST
|
||||
lonCol: LONGITUDE_DEST
|
||||
type: latlong
|
||||
granularity_sqla: null
|
||||
mapbox_style: https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
row_limit: 5000
|
||||
slice_id: 42
|
||||
start_spatial:
|
||||
latCol: LATITUDE
|
||||
lonCol: LONGITUDE
|
||||
type: latlong
|
||||
stroke_width: 1
|
||||
time_grain_sqla: null
|
||||
time_range: ' : '
|
||||
viewport:
|
||||
altitude: 1.5
|
||||
bearing: 8.546256357301871
|
||||
height: 642
|
||||
latitude: 44.596651438714254
|
||||
longitude: -91.84340711201104
|
||||
maxLatitude: 85.05113
|
||||
maxPitch: 60
|
||||
maxZoom: 20
|
||||
minLatitude: -85.05113
|
||||
minPitch: 0
|
||||
minZoom: 0
|
||||
pitch: 60
|
||||
width: 997
|
||||
zoom: 2.929837070560775
|
||||
viz_type: deck_arc
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 51a68f80-d538-4094-bb9e-346aad49b306
|
||||
version: 1.0.0
|
||||
dataset_uuid: 92980b06-cbec-4f34-9c2e-7308edc8c2b9
|
||||
@@ -0,0 +1,43 @@
|
||||
slice_name: Deck.gl Grid
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: deck_grid
|
||||
params:
|
||||
autozoom: false
|
||||
color_picker:
|
||||
a: 1
|
||||
b: 0
|
||||
g: 255
|
||||
r: 14
|
||||
datasource: 5__table
|
||||
extruded: true
|
||||
granularity_sqla: null
|
||||
grid_size: 120
|
||||
groupby: []
|
||||
mapbox_style: https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
point_radius: Auto
|
||||
point_radius_fixed:
|
||||
type: fix
|
||||
value: 2000
|
||||
point_radius_unit: Pixels
|
||||
row_limit: 5000
|
||||
size: count
|
||||
spatial:
|
||||
latCol: LAT
|
||||
lonCol: LON
|
||||
type: latlong
|
||||
time_grain_sqla: null
|
||||
time_range: No filter
|
||||
viewport:
|
||||
bearing: 155.80099696026355
|
||||
latitude: 37.7942314882596
|
||||
longitude: -122.42066918995666
|
||||
pitch: 53.470800300695146
|
||||
zoom: 12.699690845482069
|
||||
viz_type: deck_grid
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: a1b96ab6-3c0b-4cbc-b13a-a70749e84068
|
||||
version: 1.0.0
|
||||
dataset_uuid: 605eaec7-ebf1-4fea-ac4b-07652fcb46e7
|
||||
@@ -0,0 +1,42 @@
|
||||
slice_name: Deck.gl Hexagons
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: deck_hex
|
||||
params:
|
||||
color_picker:
|
||||
a: 1
|
||||
b: 0
|
||||
g: 255
|
||||
r: 14
|
||||
datasource: 5__table
|
||||
extruded: true
|
||||
granularity_sqla: null
|
||||
grid_size: 40
|
||||
groupby: []
|
||||
mapbox_style: https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
point_radius: Auto
|
||||
point_radius_fixed:
|
||||
type: fix
|
||||
value: 2000
|
||||
point_radius_unit: Pixels
|
||||
row_limit: 5000
|
||||
size: count
|
||||
spatial:
|
||||
latCol: LAT
|
||||
lonCol: LON
|
||||
type: latlong
|
||||
time_grain_sqla: null
|
||||
time_range: No filter
|
||||
viewport:
|
||||
bearing: -2.3984797349335167
|
||||
latitude: 37.789795085160335
|
||||
longitude: -122.40632230075536
|
||||
pitch: 54.08961642447763
|
||||
zoom: 13.835465702403654
|
||||
viz_type: deck_hex
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: bdfdce5d-c44d-4c63-8a45-0b2a1a29715b
|
||||
version: 1.0.0
|
||||
dataset_uuid: 605eaec7-ebf1-4fea-ac4b-07652fcb46e7
|
||||
@@ -0,0 +1,48 @@
|
||||
slice_name: Deck.gl Path
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: deck_path
|
||||
params:
|
||||
color_picker:
|
||||
a: 1
|
||||
b: 135
|
||||
g: 122
|
||||
r: 0
|
||||
datasource: 12__table
|
||||
js_columns:
|
||||
- color
|
||||
js_data_mutator: "data => data.map(d => ({\n ...d,\n color: colors.hexToRGB(d.extraProps.color)\n\
|
||||
}));"
|
||||
js_onclick_href: ''
|
||||
js_tooltip: ''
|
||||
line_column: path_json
|
||||
line_type: json
|
||||
line_width: 150
|
||||
mapbox_style: https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
reverse_long_lat: false
|
||||
row_limit: 5000
|
||||
slice_id: 43
|
||||
time_grain_sqla: null
|
||||
time_range: ' : '
|
||||
viewport:
|
||||
altitude: 1.5
|
||||
bearing: 0
|
||||
height: 1094
|
||||
latitude: 37.73671752604488
|
||||
longitude: -122.18885402582598
|
||||
maxLatitude: 85.05113
|
||||
maxPitch: 60
|
||||
maxZoom: 20
|
||||
minLatitude: -85.05113
|
||||
minPitch: 0
|
||||
minZoom: 0
|
||||
pitch: 0
|
||||
width: 669
|
||||
zoom: 9.51847667620428
|
||||
viz_type: deck_path
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 6332daf6-e442-469d-b66c-a6a38423d4c7
|
||||
version: 1.0.0
|
||||
dataset_uuid: 151c283f-c076-437a-8e2f-1cf65fe6db0d
|
||||
@@ -0,0 +1,88 @@
|
||||
slice_name: Deck.gl Polygons
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: deck_polygon
|
||||
params:
|
||||
datasource: 11__table
|
||||
extruded: true
|
||||
fill_color_picker:
|
||||
a: 1
|
||||
b: 73
|
||||
g: 65
|
||||
r: 3
|
||||
filled: true
|
||||
granularity_sqla: null
|
||||
js_columns: []
|
||||
js_data_mutator: ''
|
||||
js_onclick_href: ''
|
||||
js_tooltip: ''
|
||||
legend_format: .1s
|
||||
legend_position: tr
|
||||
line_column: contour
|
||||
line_type: json
|
||||
line_width: 10
|
||||
line_width_unit: meters
|
||||
linear_color_scheme: oranges
|
||||
mapbox_style: https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
metric:
|
||||
aggregate: SUM
|
||||
column:
|
||||
column_name: population
|
||||
description: null
|
||||
expression: null
|
||||
filterable: true
|
||||
groupby: true
|
||||
id: 1332
|
||||
is_dttm: false
|
||||
optionName: _col_population
|
||||
python_date_format: null
|
||||
type: BIGINT
|
||||
verbose_name: null
|
||||
expressionType: SIMPLE
|
||||
hasCustomLabel: true
|
||||
label: Population
|
||||
optionName: metric_t2v4qbfiz1_w6qgpx4h2p
|
||||
sqlExpression: null
|
||||
multiplier: 0.1
|
||||
point_radius_fixed:
|
||||
type: metric
|
||||
value:
|
||||
aggregate: null
|
||||
column: null
|
||||
expressionType: SQL
|
||||
hasCustomLabel: null
|
||||
label: Density
|
||||
optionName: metric_c5rvwrzoo86_293h6yrv2ic
|
||||
sqlExpression: SUM(population)/SUM(area)
|
||||
reverse_long_lat: false
|
||||
slice_id: 41
|
||||
stroke_color_picker:
|
||||
a: 1
|
||||
b: 135
|
||||
g: 122
|
||||
r: 0
|
||||
stroked: false
|
||||
time_grain_sqla: null
|
||||
time_range: ' : '
|
||||
viewport:
|
||||
altitude: 1.5
|
||||
bearing: 37.89506450385642
|
||||
height: 906
|
||||
latitude: 37.752020331384834
|
||||
longitude: -122.43388541747726
|
||||
maxLatitude: 85.05113
|
||||
maxPitch: 60
|
||||
maxZoom: 20
|
||||
minLatitude: -85.05113
|
||||
minPitch: 0
|
||||
minZoom: 0
|
||||
pitch: 60
|
||||
width: 667
|
||||
zoom: 11.133995608594631
|
||||
viz_type: deck_polygon
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: f3236785-149e-4cab-9408-f2cc69afd977
|
||||
version: 1.0.0
|
||||
dataset_uuid: a480e881-e90d-4dc8-818e-f9338c3ca839
|
||||
@@ -0,0 +1,42 @@
|
||||
slice_name: Deck.gl Scatterplot
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: deck_scatter
|
||||
params:
|
||||
color_picker:
|
||||
a: 0.82
|
||||
b: 3
|
||||
g: 0
|
||||
r: 205
|
||||
datasource: 5__table
|
||||
granularity_sqla: null
|
||||
groupby: []
|
||||
mapbox_style: https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
max_radius: 250
|
||||
min_radius: 1
|
||||
multiplier: 10
|
||||
point_radius_fixed:
|
||||
type: metric
|
||||
value: count
|
||||
point_unit: square_m
|
||||
row_limit: 5000
|
||||
size: count
|
||||
spatial:
|
||||
latCol: LAT
|
||||
lonCol: LON
|
||||
type: latlong
|
||||
time_grain_sqla: null
|
||||
time_range: ' : '
|
||||
viewport:
|
||||
bearing: -4.952916738791771
|
||||
latitude: 37.78926922909199
|
||||
longitude: -122.42613341901688
|
||||
pitch: 4.750411100577438
|
||||
zoom: 12.729132798697304
|
||||
viz_type: deck_scatter
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: cc75c4d5-8f79-4ffd-8e75-06162d4a867f
|
||||
version: 1.0.0
|
||||
dataset_uuid: 605eaec7-ebf1-4fea-ac4b-07652fcb46e7
|
||||
@@ -0,0 +1,41 @@
|
||||
slice_name: Deck.gl Screen grid
|
||||
description: null
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
viz_type: deck_screengrid
|
||||
params:
|
||||
color_picker:
|
||||
a: 1
|
||||
b: 0
|
||||
g: 255
|
||||
r: 14
|
||||
datasource: 5__table
|
||||
granularity_sqla: null
|
||||
grid_size: 20
|
||||
groupby: []
|
||||
mapbox_style: https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
point_radius: Auto
|
||||
point_radius_fixed:
|
||||
type: fix
|
||||
value: 2000
|
||||
point_unit: square_m
|
||||
row_limit: 5000
|
||||
size: count
|
||||
spatial:
|
||||
latCol: LAT
|
||||
lonCol: LON
|
||||
type: latlong
|
||||
time_grain_sqla: null
|
||||
time_range: No filter
|
||||
viewport:
|
||||
bearing: -4.952916738791771
|
||||
latitude: 37.76024135844065
|
||||
longitude: -122.41827069521386
|
||||
pitch: 4.750411100577438
|
||||
zoom: 14.161641703941438
|
||||
viz_type: deck_screengrid
|
||||
query_context: null
|
||||
cache_timeout: null
|
||||
uuid: 966c802c-4733-489f-b65b-385083c85d90
|
||||
version: 1.0.0
|
||||
dataset_uuid: 605eaec7-ebf1-4fea-ac4b-07652fcb46e7
|
||||
83
superset/examples/configs/dashboards/Misc_Charts.yaml
Normal file
83
superset/examples/configs/dashboards/Misc_Charts.yaml
Normal file
@@ -0,0 +1,83 @@
|
||||
dashboard_title: Misc Charts
|
||||
description: null
|
||||
css: null
|
||||
slug: misc_charts
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
published: false
|
||||
uuid: 55a4fe9f-2682-4b0d-84c7-49ded4be11db
|
||||
position:
|
||||
CHART-HJOYVMV0E7:
|
||||
children: []
|
||||
id: CHART-HJOYVMV0E7
|
||||
meta:
|
||||
chartId: 30
|
||||
height: 69
|
||||
sliceName: OSM Long/Lat
|
||||
uuid: a4e90860-c8f5-4c50-8c04-06b2e144809c
|
||||
width: 4
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-S1MK4M4A4X
|
||||
- COLUMN-ByUFVf40EQ
|
||||
type: CHART
|
||||
CHART-S1WYNz4AVX:
|
||||
children: []
|
||||
id: CHART-S1WYNz4AVX
|
||||
meta:
|
||||
chartId: 10
|
||||
height: 69
|
||||
sliceName: Parallel Coordinates
|
||||
uuid: 041377c4-0ca9-4a40-8abd-befcd137c0dc
|
||||
width: 4
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-SytNzNA4X
|
||||
type: CHART
|
||||
CHART-rkgF4G4A4X:
|
||||
children: []
|
||||
id: CHART-rkgF4G4A4X
|
||||
meta:
|
||||
chartId: 31
|
||||
height: 69
|
||||
sliceName: Birth in France by department in 2016
|
||||
uuid: 6bd584f1-0ef5-44fc-8a05-61400f83bb62
|
||||
width: 4
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-SytNzNA4X
|
||||
type: CHART
|
||||
DASHBOARD_VERSION_KEY: v2
|
||||
GRID_ID:
|
||||
children:
|
||||
- ROW-SytNzNA4X
|
||||
id: GRID_ID
|
||||
parents:
|
||||
- ROOT_ID
|
||||
type: GRID
|
||||
HEADER_ID:
|
||||
id: HEADER_ID
|
||||
meta:
|
||||
text: Misc Charts
|
||||
type: HEADER
|
||||
ROOT_ID:
|
||||
children:
|
||||
- GRID_ID
|
||||
id: ROOT_ID
|
||||
type: ROOT
|
||||
ROW-SytNzNA4X:
|
||||
children:
|
||||
- CHART-rkgF4G4A4X
|
||||
- CHART-S1WYNz4AVX
|
||||
- CHART-HJOYVMV0E7
|
||||
id: ROW-SytNzNA4X
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
type: ROW
|
||||
version: 1.0.0
|
||||
263
superset/examples/configs/dashboards/USA_Births_Names.yaml
Normal file
263
superset/examples/configs/dashboards/USA_Births_Names.yaml
Normal file
@@ -0,0 +1,263 @@
|
||||
dashboard_title: USA Births Names
|
||||
description: null
|
||||
css: null
|
||||
slug: births
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
published: true
|
||||
uuid: fb7d30bc-b160-4371-861c-235d19bf6e25
|
||||
position:
|
||||
CHART-6GdlekVise:
|
||||
children: []
|
||||
id: CHART-6GdlekVise
|
||||
meta:
|
||||
chartId: 19
|
||||
height: 50
|
||||
sliceName: Top 10 Girl Name Share
|
||||
width: 5
|
||||
uuid: da76899a-d75c-467b-b0ce-cfa4819ed1b1
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-eh0w37bWbR
|
||||
type: CHART
|
||||
CHART-6n9jxb30JG:
|
||||
children: []
|
||||
id: CHART-6n9jxb30JG
|
||||
meta:
|
||||
chartId: 14
|
||||
height: 36
|
||||
sliceName: Genders by State
|
||||
width: 5
|
||||
uuid: 2cc25185-3d8c-494c-aa3c-14f081ac7e54
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW--EyBZQlDi
|
||||
type: CHART
|
||||
CHART-Jj9qh1ol-N:
|
||||
children: []
|
||||
id: CHART-Jj9qh1ol-N
|
||||
meta:
|
||||
chartId: 18
|
||||
height: 50
|
||||
sliceName: Boy Name Cloud
|
||||
width: 4
|
||||
uuid: 6994ec83-0cf2-4a26-97e2-1e30b0002aa0
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-kzWtcvo8R1
|
||||
type: CHART
|
||||
CHART-ODvantb_bF:
|
||||
children: []
|
||||
id: CHART-ODvantb_bF
|
||||
meta:
|
||||
chartId: 20
|
||||
height: 50
|
||||
sliceName: Top 10 Boy Name Share
|
||||
width: 5
|
||||
uuid: f35cca46-bb11-440e-8ba1-7f021bfe52a7
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-kzWtcvo8R1
|
||||
type: CHART
|
||||
CHART-PAXUUqwmX9:
|
||||
children: []
|
||||
id: CHART-PAXUUqwmX9
|
||||
meta:
|
||||
chartId: 12
|
||||
height: 34
|
||||
sliceName: Genders
|
||||
width: 3
|
||||
uuid: fb05dca0-bd3e-4953-a0a5-94b51de3a653
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-2n0XgiHDgs
|
||||
type: CHART
|
||||
CHART-_T6n_K9iQN:
|
||||
children: []
|
||||
id: CHART-_T6n_K9iQN
|
||||
meta:
|
||||
chartId: 13
|
||||
height: 36
|
||||
sliceName: Trends
|
||||
width: 7
|
||||
uuid: c6024db9-1695-4aa6-b846-42d9c96bfcbf
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW--EyBZQlDi
|
||||
type: CHART
|
||||
CHART-eNY0tcE_ic:
|
||||
children: []
|
||||
id: CHART-eNY0tcE_ic
|
||||
meta:
|
||||
chartId: 11
|
||||
height: 34
|
||||
sliceName: Participants
|
||||
width: 3
|
||||
uuid: 89ae3c32-eafa-4466-82cf-8c4328420782
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-2n0XgiHDgs
|
||||
type: CHART
|
||||
CHART-g075mMgyYb:
|
||||
children: []
|
||||
id: CHART-g075mMgyYb
|
||||
meta:
|
||||
chartId: 15
|
||||
height: 50
|
||||
sliceName: Girls
|
||||
width: 3
|
||||
uuid: 44cfa30e-af8e-4176-8612-4df0c0609516
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-eh0w37bWbR
|
||||
type: CHART
|
||||
CHART-n-zGGE6S1y:
|
||||
children: []
|
||||
id: CHART-n-zGGE6S1y
|
||||
meta:
|
||||
chartId: 16
|
||||
height: 50
|
||||
sliceName: Girl Name Cloud
|
||||
width: 4
|
||||
uuid: ba6574fe-a6c0-41ef-9499-1ea6ff36bd2d
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-eh0w37bWbR
|
||||
type: CHART
|
||||
CHART-vJIPjmcbD3:
|
||||
children: []
|
||||
id: CHART-vJIPjmcbD3
|
||||
meta:
|
||||
chartId: 17
|
||||
height: 50
|
||||
sliceName: Boys
|
||||
width: 3
|
||||
uuid: 0af97164-82f0-42bb-a611-7093e5c56596
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-kzWtcvo8R1
|
||||
type: CHART
|
||||
DASHBOARD_VERSION_KEY: v2
|
||||
GRID_ID:
|
||||
children:
|
||||
- ROW-2n0XgiHDgs
|
||||
- ROW--EyBZQlDi
|
||||
- ROW-eh0w37bWbR
|
||||
- ROW-kzWtcvo8R1
|
||||
- ROW-N-0P6H2KVI
|
||||
id: GRID_ID
|
||||
parents:
|
||||
- ROOT_ID
|
||||
type: GRID
|
||||
HEADER_ID:
|
||||
id: HEADER_ID
|
||||
meta:
|
||||
text: Births
|
||||
type: HEADER
|
||||
MARKDOWN-zaflB60tbC:
|
||||
children: []
|
||||
id: MARKDOWN-zaflB60tbC
|
||||
meta:
|
||||
code: <div style="text-align:center"> <h1>Birth Names Dashboard</h1> <img
|
||||
src="/static/assets/images/babies.png" style="width:50%;"></div>
|
||||
height: 34
|
||||
width: 6
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-2n0XgiHDgs
|
||||
type: MARKDOWN
|
||||
ROOT_ID:
|
||||
children:
|
||||
- GRID_ID
|
||||
id: ROOT_ID
|
||||
type: ROOT
|
||||
ROW--EyBZQlDi:
|
||||
children:
|
||||
- CHART-_T6n_K9iQN
|
||||
- CHART-6n9jxb30JG
|
||||
id: ROW--EyBZQlDi
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
type: ROW
|
||||
ROW-2n0XgiHDgs:
|
||||
children:
|
||||
- CHART-eNY0tcE_ic
|
||||
- MARKDOWN-zaflB60tbC
|
||||
- CHART-PAXUUqwmX9
|
||||
id: ROW-2n0XgiHDgs
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
type: ROW
|
||||
ROW-eh0w37bWbR:
|
||||
children:
|
||||
- CHART-g075mMgyYb
|
||||
- CHART-n-zGGE6S1y
|
||||
- CHART-6GdlekVise
|
||||
id: ROW-eh0w37bWbR
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
type: ROW
|
||||
ROW-kzWtcvo8R1:
|
||||
children:
|
||||
- CHART-vJIPjmcbD3
|
||||
- CHART-Jj9qh1ol-N
|
||||
- CHART-ODvantb_bF
|
||||
id: ROW-kzWtcvo8R1
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
type: ROW
|
||||
ROW-N-0P6H2KVI:
|
||||
children:
|
||||
- CHART-A62J4Z7R
|
||||
id: ROW-N-0P6H2KVI
|
||||
meta:
|
||||
'0': ROOT_ID
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
CHART-A62J4Z7R:
|
||||
children: []
|
||||
id: CHART-A62J4Z7R
|
||||
meta:
|
||||
chartId: 21
|
||||
height: 50
|
||||
sliceName: Pivot Table v2
|
||||
uuid: 86778b63-19d8-4278-a79f-c90a1b31e162
|
||||
width: 4
|
||||
type: CHART
|
||||
parents:
|
||||
- ROOT_ID
|
||||
- GRID_ID
|
||||
- ROW-N-0P6H2KVI
|
||||
metadata:
|
||||
label_colors:
|
||||
Girls: '#FF69B4'
|
||||
Boys: '#ADD8E6'
|
||||
girl: '#FF69B4'
|
||||
boy: '#ADD8E6'
|
||||
version: 1.0.0
|
||||
175
superset/examples/configs/dashboards/World_Banks_Data.yaml
Normal file
175
superset/examples/configs/dashboards/World_Banks_Data.yaml
Normal file
@@ -0,0 +1,175 @@
|
||||
dashboard_title: World Bank's Data
|
||||
description: null
|
||||
css: null
|
||||
slug: world_health
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
published: true
|
||||
uuid: d37232b3-43b9-486a-a132-e387dc0ff8de
|
||||
position:
|
||||
CHART-37982887:
|
||||
children: []
|
||||
id: CHART-37982887
|
||||
meta:
|
||||
chartId: 1
|
||||
height: 52
|
||||
sliceName: World's Population
|
||||
width: 2
|
||||
uuid: c50fc6e3-96fc-4e72-877b-2ea1a5e25c7a
|
||||
type: CHART
|
||||
CHART-17e0f8d8:
|
||||
children: []
|
||||
id: CHART-17e0f8d8
|
||||
meta:
|
||||
chartId: 2
|
||||
height: 92
|
||||
sliceName: Most Populated Countries
|
||||
width: 3
|
||||
uuid: 4183745e-1cc4-4f88-9ae6-973c69845ce4
|
||||
type: CHART
|
||||
CHART-2ee52f30:
|
||||
children: []
|
||||
id: CHART-2ee52f30
|
||||
meta:
|
||||
chartId: 3
|
||||
height: 38
|
||||
sliceName: Growth Rate
|
||||
width: 6
|
||||
uuid: cfcd7c5e-4759-4b28-bb7c-e2200508e978
|
||||
type: CHART
|
||||
CHART-2d5b6871:
|
||||
children: []
|
||||
id: CHART-2d5b6871
|
||||
meta:
|
||||
chartId: 4
|
||||
height: 52
|
||||
sliceName: '% Rural'
|
||||
width: 7
|
||||
uuid: 8d889488-edb5-40cb-a69c-e2c14f009e2b
|
||||
type: CHART
|
||||
CHART-0fd0d252:
|
||||
children: []
|
||||
id: CHART-0fd0d252
|
||||
meta:
|
||||
chartId: 5
|
||||
height: 50
|
||||
sliceName: Life Expectancy VS Rural %
|
||||
width: 8
|
||||
uuid: fa927236-7b66-4d03-ae6c-463d2d394123
|
||||
type: CHART
|
||||
CHART-97f4cb48:
|
||||
children: []
|
||||
id: CHART-97f4cb48
|
||||
meta:
|
||||
chartId: 6
|
||||
height: 38
|
||||
sliceName: Rural Breakdown
|
||||
width: 3
|
||||
uuid: 70a2e07b-0f45-4532-96ae-0c6db52d2e7c
|
||||
type: CHART
|
||||
CHART-b5e05d6f:
|
||||
children: []
|
||||
id: CHART-b5e05d6f
|
||||
meta:
|
||||
chartId: 7
|
||||
height: 50
|
||||
sliceName: World's Pop Growth
|
||||
width: 4
|
||||
uuid: e18b5d28-3a3d-43ea-8a20-b198b44b08e3
|
||||
type: CHART
|
||||
CHART-e76e9f5f:
|
||||
children: []
|
||||
id: CHART-e76e9f5f
|
||||
meta:
|
||||
chartId: 8
|
||||
height: 50
|
||||
sliceName: Box plot
|
||||
width: 4
|
||||
uuid: d31ba9c7-798b-4f84-87ef-ab31721680a8
|
||||
type: CHART
|
||||
CHART-a4808bba:
|
||||
children: []
|
||||
id: CHART-a4808bba
|
||||
meta:
|
||||
chartId: 9
|
||||
height: 50
|
||||
sliceName: Treemap
|
||||
width: 8
|
||||
uuid: fc941a12-88a0-42e7-ac48-c1ec4ed84640
|
||||
type: CHART
|
||||
COLUMN-071bbbad:
|
||||
children:
|
||||
- ROW-1e064e3c
|
||||
- ROW-afdefba9
|
||||
id: COLUMN-071bbbad
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
width: 9
|
||||
type: COLUMN
|
||||
COLUMN-fe3914b8:
|
||||
children:
|
||||
- CHART-37982887
|
||||
id: COLUMN-fe3914b8
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
width: 2
|
||||
type: COLUMN
|
||||
GRID_ID:
|
||||
children:
|
||||
- ROW-46632bc2
|
||||
- ROW-3fa26c5d
|
||||
- ROW-812b3f13
|
||||
id: GRID_ID
|
||||
type: GRID
|
||||
HEADER_ID:
|
||||
id: HEADER_ID
|
||||
meta:
|
||||
text: World's Bank Data
|
||||
type: HEADER
|
||||
ROOT_ID:
|
||||
children:
|
||||
- GRID_ID
|
||||
id: ROOT_ID
|
||||
type: ROOT
|
||||
ROW-1e064e3c:
|
||||
children:
|
||||
- COLUMN-fe3914b8
|
||||
- CHART-2d5b6871
|
||||
id: ROW-1e064e3c
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
ROW-3fa26c5d:
|
||||
children:
|
||||
- CHART-b5e05d6f
|
||||
- CHART-0fd0d252
|
||||
id: ROW-3fa26c5d
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
ROW-46632bc2:
|
||||
children:
|
||||
- COLUMN-071bbbad
|
||||
- CHART-17e0f8d8
|
||||
id: ROW-46632bc2
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
ROW-812b3f13:
|
||||
children:
|
||||
- CHART-a4808bba
|
||||
- CHART-e76e9f5f
|
||||
id: ROW-812b3f13
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
ROW-afdefba9:
|
||||
children:
|
||||
- CHART-2ee52f30
|
||||
- CHART-97f4cb48
|
||||
id: ROW-afdefba9
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
DASHBOARD_VERSION_KEY: v2
|
||||
version: 1.0.0
|
||||
130
superset/examples/configs/dashboards/deck.gl_Demo.yaml
Normal file
130
superset/examples/configs/dashboards/deck.gl_Demo.yaml
Normal file
@@ -0,0 +1,130 @@
|
||||
dashboard_title: deck.gl Demo
|
||||
description: null
|
||||
css: null
|
||||
slug: deck
|
||||
certified_by: null
|
||||
certification_details: null
|
||||
published: true
|
||||
uuid: b78795f1-0b33-41a9-a6c7-186f38a526ad
|
||||
position:
|
||||
CHART-3afd9d70:
|
||||
meta:
|
||||
chartId: 32
|
||||
sliceName: Deck.gl Scatterplot
|
||||
width: 6
|
||||
height: 50
|
||||
uuid: cc75c4d5-8f79-4ffd-8e75-06162d4a867f
|
||||
type: CHART
|
||||
id: CHART-3afd9d70
|
||||
children: []
|
||||
CHART-2ee7fa5e:
|
||||
meta:
|
||||
chartId: 33
|
||||
sliceName: Deck.gl Screen grid
|
||||
width: 6
|
||||
height: 50
|
||||
uuid: 966c802c-4733-489f-b65b-385083c85d90
|
||||
type: CHART
|
||||
id: CHART-2ee7fa5e
|
||||
children: []
|
||||
CHART-201f7715:
|
||||
meta:
|
||||
chartId: 34
|
||||
sliceName: Deck.gl Hexagons
|
||||
width: 6
|
||||
height: 50
|
||||
uuid: bdfdce5d-c44d-4c63-8a45-0b2a1a29715b
|
||||
type: CHART
|
||||
id: CHART-201f7715
|
||||
children: []
|
||||
CHART-d02f6c40:
|
||||
meta:
|
||||
chartId: 35
|
||||
sliceName: Deck.gl Grid
|
||||
width: 6
|
||||
height: 50
|
||||
uuid: a1b96ab6-3c0b-4cbc-b13a-a70749e84068
|
||||
type: CHART
|
||||
id: CHART-d02f6c40
|
||||
children: []
|
||||
CHART-2673431d:
|
||||
meta:
|
||||
chartId: 36
|
||||
sliceName: Deck.gl Polygons
|
||||
width: 6
|
||||
height: 50
|
||||
uuid: f3236785-149e-4cab-9408-f2cc69afd977
|
||||
type: CHART
|
||||
id: CHART-2673431d
|
||||
children: []
|
||||
CHART-85265a60:
|
||||
meta:
|
||||
chartId: 37
|
||||
sliceName: Deck.gl Arcs
|
||||
width: 6
|
||||
height: 50
|
||||
uuid: 51a68f80-d538-4094-bb9e-346aad49b306
|
||||
type: CHART
|
||||
id: CHART-85265a60
|
||||
children: []
|
||||
CHART-2b87513c:
|
||||
meta:
|
||||
chartId: 38
|
||||
sliceName: Deck.gl Path
|
||||
width: 6
|
||||
height: 50
|
||||
uuid: 6332daf6-e442-469d-b66c-a6a38423d4c7
|
||||
type: CHART
|
||||
id: CHART-2b87513c
|
||||
children: []
|
||||
GRID_ID:
|
||||
type: GRID
|
||||
id: GRID_ID
|
||||
children:
|
||||
- ROW-a7b16cb5
|
||||
- ROW-72c218a5
|
||||
- ROW-957ba55b
|
||||
- ROW-af041bdd
|
||||
HEADER_ID:
|
||||
meta:
|
||||
text: deck.gl Demo
|
||||
type: HEADER
|
||||
id: HEADER_ID
|
||||
ROOT_ID:
|
||||
type: ROOT
|
||||
id: ROOT_ID
|
||||
children:
|
||||
- GRID_ID
|
||||
ROW-72c218a5:
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
id: ROW-72c218a5
|
||||
children:
|
||||
- CHART-d02f6c40
|
||||
- CHART-201f7715
|
||||
ROW-957ba55b:
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
id: ROW-957ba55b
|
||||
children:
|
||||
- CHART-2673431d
|
||||
- CHART-85265a60
|
||||
ROW-a7b16cb5:
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
id: ROW-a7b16cb5
|
||||
children:
|
||||
- CHART-3afd9d70
|
||||
- CHART-2ee7fa5e
|
||||
ROW-af041bdd:
|
||||
meta:
|
||||
background: BACKGROUND_TRANSPARENT
|
||||
type: ROW
|
||||
id: ROW-af041bdd
|
||||
children:
|
||||
- CHART-2b87513c
|
||||
DASHBOARD_VERSION_KEY: v2
|
||||
version: 1.0.0
|
||||
80
superset/examples/configs/datasets/examples/bart_lines.yaml
Normal file
80
superset/examples/configs/datasets/examples/bart_lines.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
table_name: bart_lines
|
||||
main_dttm_col: null
|
||||
description: BART lines
|
||||
default_endpoint: null
|
||||
offset: 0
|
||||
cache_timeout: null
|
||||
catalog: null
|
||||
schema: public
|
||||
sql: null
|
||||
params: null
|
||||
template_params: null
|
||||
filter_select_enabled: true
|
||||
fetch_values_predicate: null
|
||||
extra: null
|
||||
normalize_columns: false
|
||||
always_filter_main_dttm: false
|
||||
folders: null
|
||||
uuid: 151c283f-c076-437a-8e2f-1cf65fe6db0d
|
||||
metrics:
|
||||
- metric_name: count
|
||||
verbose_name: COUNT(*)
|
||||
metric_type: count
|
||||
expression: COUNT(*)
|
||||
description: null
|
||||
d3format: null
|
||||
currency: null
|
||||
extra: null
|
||||
warning_text: null
|
||||
columns:
|
||||
- column_name: name
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: VARCHAR(255)
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: color
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: VARCHAR(255)
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: path_json
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: polyline
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
version: 1.0.0
|
||||
database_uuid: a2dc77af-e654-49bb-b321-40f6b559a1ee
|
||||
data: https://cdn.jsdelivr.net/gh/apache-superset/examples-data@master/bart-lines.json.gz
|
||||
@@ -0,0 +1,209 @@
|
||||
table_name: birth_france_by_region
|
||||
main_dttm_col: dttm
|
||||
description: null
|
||||
default_endpoint: null
|
||||
offset: 0
|
||||
cache_timeout: null
|
||||
catalog: null
|
||||
schema: public
|
||||
sql: null
|
||||
params: null
|
||||
template_params: null
|
||||
filter_select_enabled: true
|
||||
fetch_values_predicate: null
|
||||
extra: null
|
||||
normalize_columns: false
|
||||
always_filter_main_dttm: false
|
||||
folders: null
|
||||
uuid: c21dd48d-9a4b-4a08-a926-47c3601c2a8d
|
||||
metrics:
|
||||
- metric_name: avg__2004
|
||||
verbose_name: null
|
||||
metric_type: null
|
||||
expression: AVG("2004")
|
||||
description: null
|
||||
d3format: null
|
||||
currency: null
|
||||
extra: null
|
||||
warning_text: null
|
||||
- metric_name: count
|
||||
verbose_name: COUNT(*)
|
||||
metric_type: count
|
||||
expression: COUNT(*)
|
||||
description: null
|
||||
d3format: null
|
||||
currency: null
|
||||
extra: null
|
||||
warning_text: null
|
||||
columns:
|
||||
- column_name: DEPT_ID
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: VARCHAR(10)
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2010'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2003'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2004'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2005'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2006'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2007'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2008'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2009'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2011'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2012'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2013'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: '2014'
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: dttm
|
||||
verbose_name: null
|
||||
is_dttm: true
|
||||
is_active: true
|
||||
type: DATE
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
version: 1.0.0
|
||||
database_uuid: a2dc77af-e654-49bb-b321-40f6b559a1ee
|
||||
data: https://cdn.jsdelivr.net/gh/apache-superset/examples-data@master/paris_iris.json.gz
|
||||
137
superset/examples/configs/datasets/examples/birth_names.yaml
Normal file
137
superset/examples/configs/datasets/examples/birth_names.yaml
Normal file
@@ -0,0 +1,137 @@
|
||||
table_name: birth_names
|
||||
main_dttm_col: ds
|
||||
description: null
|
||||
default_endpoint: null
|
||||
offset: 0
|
||||
cache_timeout: null
|
||||
catalog: null
|
||||
schema: public
|
||||
sql: null
|
||||
params: null
|
||||
template_params: null
|
||||
filter_select_enabled: true
|
||||
fetch_values_predicate: null
|
||||
extra: null
|
||||
normalize_columns: false
|
||||
always_filter_main_dttm: false
|
||||
folders: null
|
||||
uuid: 4ec507ac-bece-4d2b-8dc3-cfb7c3515e76
|
||||
metrics:
|
||||
- metric_name: count
|
||||
verbose_name: COUNT(*)
|
||||
metric_type: count
|
||||
expression: COUNT(*)
|
||||
description: null
|
||||
d3format: null
|
||||
currency: null
|
||||
extra: null
|
||||
warning_text: null
|
||||
- metric_name: sum__num
|
||||
verbose_name: null
|
||||
metric_type: null
|
||||
expression: SUM(num)
|
||||
description: null
|
||||
d3format: null
|
||||
currency: null
|
||||
extra: null
|
||||
warning_text: null
|
||||
columns:
|
||||
- column_name: num_california
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: null
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: CASE WHEN state = 'CA' THEN num ELSE 0 END
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: ds
|
||||
verbose_name: null
|
||||
is_dttm: true
|
||||
is_active: true
|
||||
type: TIMESTAMP WITHOUT TIME ZONE
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: state
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: VARCHAR(10)
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: gender
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: VARCHAR(16)
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: name
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: VARCHAR(255)
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: num_boys
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: num_girls
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: num
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
version: 1.0.0
|
||||
database_uuid: a2dc77af-e654-49bb-b321-40f6b559a1ee
|
||||
data: https://cdn.jsdelivr.net/gh/apache-superset/examples-data@master/birth_names2.json.gz
|
||||
560
superset/examples/configs/datasets/examples/flights.yaml
Normal file
560
superset/examples/configs/datasets/examples/flights.yaml
Normal file
@@ -0,0 +1,560 @@
|
||||
table_name: flights
|
||||
main_dttm_col: ds
|
||||
description: Random set of flights in the US
|
||||
default_endpoint: null
|
||||
offset: 0
|
||||
cache_timeout: null
|
||||
catalog: null
|
||||
schema: public
|
||||
sql: null
|
||||
params: null
|
||||
template_params: null
|
||||
filter_select_enabled: true
|
||||
fetch_values_predicate: null
|
||||
extra: null
|
||||
normalize_columns: false
|
||||
always_filter_main_dttm: false
|
||||
folders: null
|
||||
uuid: 92980b06-cbec-4f34-9c2e-7308edc8c2b9
|
||||
metrics:
|
||||
- metric_name: count
|
||||
verbose_name: COUNT(*)
|
||||
metric_type: count
|
||||
expression: COUNT(*)
|
||||
description: null
|
||||
d3format: null
|
||||
currency: null
|
||||
extra: null
|
||||
warning_text: null
|
||||
columns:
|
||||
- column_name: ds
|
||||
verbose_name: null
|
||||
is_dttm: true
|
||||
is_active: true
|
||||
type: TIMESTAMP WITHOUT TIME ZONE
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: LATE_AIRCRAFT_DELAY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: ARRIVAL_DELAY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: DEPARTURE_DELAY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: WEATHER_DELAY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: AIRLINE_DELAY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: AIR_SYSTEM_DELAY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: ARRIVAL_TIME
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: SECURITY_DELAY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: LATITUDE_DEST
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: ELAPSED_TIME
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: DEPARTURE_TIME
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: LATITUDE
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: AIR_TIME
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: TAXI_IN
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: TAXI_OUT
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: LONGITUDE_DEST
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: LONGITUDE
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: WHEELS_OFF
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: WHEELS_ON
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: CANCELLATION_REASON
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: SCHEDULED_ARRIVAL
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: DESTINATION_AIRPORT
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: CANCELLED
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: SCHEDULED_DEPARTURE
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: DISTANCE
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: DAY_OF_WEEK
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: DAY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: TAIL_NUMBER
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: YEAR
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: STATE_DEST
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: AIRPORT_DEST
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: AIRLINE
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: STATE
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: ORIGIN_AIRPORT
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: AIRPORT
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: FLIGHT_NUMBER
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: SCHEDULED_TIME
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: DIVERTED
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: MONTH
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: CITY_DEST
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: COUNTRY_DEST
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: CITY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: COUNTRY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
version: 1.0.0
|
||||
database_uuid: a2dc77af-e654-49bb-b321-40f6b559a1ee
|
||||
data: https://cdn.jsdelivr.net/gh/apache-superset/examples-data@master/flight_data.csv.gz
|
||||
212
superset/examples/configs/datasets/examples/long_lat.yaml
Normal file
212
superset/examples/configs/datasets/examples/long_lat.yaml
Normal file
@@ -0,0 +1,212 @@
|
||||
table_name: long_lat
|
||||
main_dttm_col: datetime
|
||||
description: null
|
||||
default_endpoint: null
|
||||
offset: 0
|
||||
cache_timeout: null
|
||||
catalog: null
|
||||
schema: public
|
||||
sql: null
|
||||
params: null
|
||||
template_params: null
|
||||
filter_select_enabled: true
|
||||
fetch_values_predicate: null
|
||||
extra: null
|
||||
normalize_columns: false
|
||||
always_filter_main_dttm: false
|
||||
folders: null
|
||||
uuid: 605eaec7-ebf1-4fea-ac4b-07652fcb46e7
|
||||
metrics:
|
||||
- metric_name: count
|
||||
verbose_name: COUNT(*)
|
||||
metric_type: count
|
||||
expression: COUNT(*)
|
||||
description: null
|
||||
d3format: null
|
||||
currency: null
|
||||
extra: null
|
||||
warning_text: null
|
||||
columns:
|
||||
- column_name: datetime
|
||||
verbose_name: null
|
||||
is_dttm: true
|
||||
is_active: true
|
||||
type: TIMESTAMP WITHOUT TIME ZONE
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: LAT
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: DISTRICT
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: CITY
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: ID
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: REGION
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: LON
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: radius_miles
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: occupancy
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: DOUBLE PRECISION
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: delimited
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: VARCHAR(60)
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: geohash
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: VARCHAR(12)
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: POSTCODE
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: BIGINT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: NUMBER
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: STREET
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
- column_name: UNIT
|
||||
verbose_name: null
|
||||
is_dttm: false
|
||||
is_active: true
|
||||
type: TEXT
|
||||
advanced_data_type: null
|
||||
groupby: true
|
||||
filterable: true
|
||||
expression: null
|
||||
description: null
|
||||
python_date_format: null
|
||||
extra: null
|
||||
version: 1.0.0
|
||||
database_uuid: a2dc77af-e654-49bb-b321-40f6b559a1ee
|
||||
data: https://cdn.jsdelivr.net/gh/apache-superset/examples-data@master/san_francisco.csv.gz
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user