Compare commits

..

60 Commits

Author SHA1 Message Date
Maxime Beauchemin
6d7467bafd feat: add devcontainer profiles for MCP development
- Split devcontainer config into base + profile structure
  - default: Standard Superset development (port 9001)
  - with-mcp: Superset + MCP service (ports 9001, 5008)
- Add MCP service to docker-compose-light.yml as optional profile
- Update start-superset.sh to conditionally start MCP when ENABLE_MCP=true
- Add MCP case to docker-bootstrap.sh for starting MCP service
- Preserve original devcontainer.json as .old for migration reference

This allows developers to choose their development environment:
- Use default profile for standard Superset development
- Use with-mcp profile for MCP/LLM agent development
- MCP service runs on port 5008 when enabled via profile

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-30 18:59:27 -07:00
Amin Ghadersohi
71f294b7d3 docs: update MCP Chart Test Plan with accurate implementation details
- Add authentication setup and dataset discovery prerequisites
- Remove references to unsupported preview formats (base64, interactive, vega_lite)
- Fix schema examples: operator -> op, xlsx -> excel
- Add performance limits for preview formats
- Update error expectations for empty query results
- Enhance cache testing with cache_timeout parameter
- Add Important Schema Notes section for common gotchas
- Update response patterns to match actual implementation
2025-07-30 20:15:41 -04:00
Amin Ghadersohi
e839d0989a refactor: remove redundant docs folder from mcp_service 2025-07-30 19:41:05 -04:00
Amin Ghadersohi
714e21b3ec feat: enhance MCP documentation with Superset best practices
- Add Docusaurus admonitions (:::note, :::tip, :::warning) following Superset patterns
- Improve code block formatting and language tags
- Add important notes about authentication and installation
- Ensure consistency with Superset documentation style
- Maintain accuracy with FastMCP specifications
2025-07-30 19:26:08 -04:00
Amin Ghadersohi
bf78eb69ed fix: resolve Mermaid diagram syntax errors in development guide
Replace @ symbols in node names that cause Mermaid parsing errors
2025-07-30 19:21:45 -04:00
Amin Ghadersohi
a13c1ba8c2 fix: update broken README.md links in MCP documentation
Replace ./README.md references with proper Docusaurus links to intro page
2025-07-30 19:20:38 -04:00
Amin Ghadersohi
422c34a6ee feat: integrate MCP service documentation into Superset Docusaurus site
- Add comprehensive MCP service documentation to docs/docs/mcp-service/
- Create 7 new documentation files covering all aspects of MCP service
- Add MCP Service section to Docusaurus sidebar configuration
- Update README files to reference official documentation
- Remove outdated standalone architecture documentation
- Install missing docs dependencies to fix eslint-docs pre-commit hook

Documentation includes:
- Introduction and overview with getting started guide
- Complete API reference with all 16 tools and examples
- Authentication setup with JWT and identity provider integration
- Development guide with internal architecture and patterns
- System architecture overview with deployment considerations
- Preset integration guide for enterprise features
2025-07-30 19:17:18 -04:00
Amin Ghadersohi
cd213fc57d update: enhance MCP service README with comprehensive setup instructions
Improve README with official Superset installation instructions, detailed Claude Desktop
connection steps using FastMCP proxy, and troubleshooting guide. Adds verification steps
and clear instructions for HTTP-based MCP service deployment.
2025-07-30 18:24:54 -04:00
Amin Ghadersohi
0f20a88598 feat(mcp): major enhancements to chart generation with validation, pooled screenshots, and simplified tests
This commit introduces significant improvements to the MCP chart generation system:

## Core Features
- Enhanced chart generation with comprehensive validation and error handling
- Implemented WebDriver pooling for 10x faster screenshot generation
- Added detailed validation for chart configurations with helpful error messages
- Support for both table and XY (line/bar/area/scatter) chart types

## Validation & Error Handling
- Column existence validation with similarity-based suggestions
- Data type compatibility checks for aggregations (SUM/AVG for numbers only)
- Filter operator validation
- Detailed error responses with actionable suggestions and available columns/metrics

## Performance Improvements
- WebDriver connection pooling eliminates browser startup overhead
- Reuses browser instances across multiple screenshot requests
- Automatic health checking and recovery of WebDriver instances
- Configurable pool size and timeout settings

## Screenshot Enhancements
- New PooledChartScreenshot and PooledExploreScreenshot classes
- FastAPI endpoints for serving chart and explore screenshots as PNG images
- Proper caching with TTL support
- Clean chart-only screenshots for explore views (hides UI elements)

## Test Improvements
- Simplified test structure focusing on schema validation over complex mocking
- Removed brittle integration-style tests in favor of unit tests
- Fixed all test failures with cleaner, more maintainable approach
- Added comprehensive test plan for manual testing with Claude Desktop

## API Enhancements
- Centralized URL generation for screenshots and explore links
- Consistent error response format across all endpoints
- Support for UUID-based chart lookups alongside numeric IDs

## Code Quality
- Fixed all pre-commit issues (mypy, ruff, long lines, complexity)
- Extracted helper functions to reduce cyclomatic complexity
- Proper type annotations throughout
- Better separation of concerns

## Documentation
- Added comprehensive test plan for chart tools
- Detailed guides for table chart configuration
- WebDriver pooling architecture documentation
- Demo scripts for testing functionality

The changes maintain backward compatibility while significantly improving
performance, reliability, and developer experience for chart generation in the
MCP service.
2025-07-30 18:09:23 -04:00
Amin Ghadersohi
33d16eaca1 refactor: rename pydantic_schemas to schemas for MCP service
Simplified the directory structure by renaming `pydantic_schemas` to `schemas` throughout the MCP service codebase. This follows standard Python conventions and makes imports more concise.

Changes:
- Renamed directory: superset/mcp_service/pydantic_schemas/ → superset/mcp_service/schemas/
- Updated all import statements across 42 files
- Updated documentation references in README files
- All tests pass, pre-commit hooks clean
2025-07-30 14:20:42 -04:00
Amin Ghadersohi
5b56bc622b feat: major MCP service enhancements with cache control, testing infrastructure, and bug fixes
This comprehensive update adds significant functionality and improvements to the MCP service:

  New Features:
  - Cache control utilities for metadata operations with configurable TTL
  - Entity testing plan documentation for systematic API validation
  - Chart data retrieval tool with caching support
  - Comprehensive request schema validation framework
  - Sortable columns testing infrastructure

  Documentation Updates:
  - Expanded README with architecture details and usage examples
  - Phase 1 status tracking and implementation notes
  - Enhanced schema documentation with cache control patterns
  - LLMS.md reference added to CLAUDE.md

  Search Functionality Fixes:
  - Dashboard search: removed invalid columns (owners, roles, tags, published)
  - Dataset search: removed non-existent 'tags' and problematic 'catalog' columns
  - Chart search: maintained correct text-only columns (slice_name, description)
  - Updated corresponding unit tests to match implementations

  Infrastructure Improvements:
  - Added comprehensive cache control test coverage
  - Request schema validation testing
  - Sortable columns validation across all entity types
  - Enhanced error handling and logging

  Technical Debt:
  - Standardized import patterns across MCP tools
  - Improved type safety in Pydantic schemas
  - Better separation of concerns for cache operations
2025-07-30 14:20:42 -04:00
Amin Ghadersohi
8cbfe027b5 update: add chart update tools and centralized URL configuration
- Add update_chart and update_chart_preview tools for modifying saved and cached charts
  - Implement centralized URL configuration using SUPERSET_WEBSERVER_ADDRESS
  - Add comprehensive MCP audit logging with impersonation tracking and payload  sanitization
  - Refactor duplicate chart analysis functions to chart_utils.py
  - Update all README documentation with new tools and features
  - Add 194+ unit tests covering URL utils, audit logging, and chart updates
  - Fix all pre-commit issues and ensure test coverage
2025-07-30 14:20:42 -04:00
Amin Ghadersohi
c3b3edc6ba refactor: rename create_chart to generate_chart and standardize MCP test naming
- Rename create_chart function to generate_chart throughout MCP service
  - Update CreateChartRequest to GenerateChartRequest schema
  - Rename test files to follow test_*.py convention consistently
  - Fix test class and method names (TestCreateChart → TestGenerateChart)
  - Update all documentation and README files with new naming
  - Standardize auth scope mappings and tool references
  - Maintain backward compatibility for Superset CreateChartCommand references
2025-07-30 14:20:42 -04:00
Amin Ghadersohi
aa06bb9fda revert unnecessary config change 2025-07-30 14:20:41 -04:00
Amin Ghadersohi
0f222b9034 update: revert comment change 2025-07-30 14:20:41 -04:00
Amin Ghadersohi
7044153ca4 revert: switch webdriver back to Firefox for chart
screenshots

  Changed WEBDRIVER_TYPE from "chrome" to "firefox" and removed
   Chrome-specific
  binary path from WEBDRIVER_CONFIGURATION. Firefox provides
  more reliable
  screenshot generation for the MCP service chart preview
  functionality.

  - Set WEBDRIVER_TYPE = "firefox"
  - Cleared binary_location in WEBDRIVER_CONFIGURATION to use
  default Firefox
  - Chart preview tools now use geckodriver instead of Chrome
  headless mode

  This resolves webdriver timeout issues in chart screenshot
  generation.
2025-07-30 14:20:41 -04:00
Amin Ghadersohi
ce82c35bb6 docs: clarify MCP service capabilities and available operations
Updated all MCP service READMEs to accurately reflect available operations:

- Removed overuse of 'comprehensive' and 'complete' where it overstated capabilities
- Added clear 'Available Operations' section showing:
  - Read operations available for all entities
  - Create operations only for charts and dashboards
  - No update or delete operations available
- Updated tool descriptions to be more specific about capabilities
- Changed 'complete workflow automation' to 'dashboard creation and chart addition'
- Clarified that we have 5 chart types, not 'comprehensive' support
- Updated test count references to be consistent (185+ tests)

This ensures documentation accurately represents Phase 1 capabilities without
overstating what operations are available.
2025-07-30 14:20:41 -04:00
Amin Ghadersohi
9b25bd973f docs: update MCP service documentation to reflect Phase 1 completion
Updated all MCP service README files to reflect current status:

- README_ARCHITECTURE.md: Updated to 95% completion status, documented new
  dashboard generation and SQL Lab tools, corrected file references
- README_PHASE1_STATUS.md: Marked all core epics as complete, updated tool
  count to 16, reflected recent technical completions
- README_SCHEMAS.md: Updated test coverage numbers, documented new schema
  enhancements, marked Phase 1 features as complete

Changes reflect the successful completion of core MCP service functionality
including BaseDAO enhancements, comprehensive testing coverage, and all
planned Phase 1 tools and features.
2025-07-30 14:20:41 -04:00
Amin Ghadersohi
5f7502b85c feat: comprehensive MCP service enhancements with type safety and new tools
Core Improvements:
- Enhanced BaseDAO with type-safe UUID handling and flexible column support
- Added comprehensive test coverage (185+ passing unit tests)
- Eliminated hardcoded string checks with SQLAlchemy type inspection
- Removed unnecessary async patterns for better performance consistency

New MCP Tools & Features:
- Dashboard generation: create_dashboard and add_chart_to_existing_dashboard
- Chart data/preview: get_chart_data and get_chart_preview with multiple formats
- SQL Lab integration: open_sql_lab_with_context with proper parameter handling
- Enhanced chart creation with comprehensive type support

Technical Enhancements:
- Fixed SQL Lab parameter naming (dbid) for frontend compatibility
- Improved error handling with graceful UUID conversion fallbacks
- Enhanced multi-identifier support (ID, UUID, slug) across all tools
- Comprehensive request schema pattern eliminating LLM validation issues

Testing & Quality:
- Added BaseDAO unit tests with edge cases and error scenarios
- Enhanced MCP tool testing with proper mocking patterns
- Fixed trailing whitespace and pre-commit compliance
- Professional error handling and type validation

Documentation:
- Updated all MCP service READMEs with current status (95% complete)
- Documented all 16 MCP tools with examples and usage patterns
- Enhanced architecture documentation with recent improvements
- Updated Phase 1 status reflecting completion of core features

This commit consolidates Phase 1 MCP service development with production-ready
authentication, comprehensive testing, and complete tool functionality.
2025-07-30 14:20:41 -04:00
Amin Ghadersohi
1d5372210f update: read files 2025-07-30 14:20:41 -04:00
Amin Ghadersohi
64af53f6f6 update: enhance MCP service auth with factory pattern and code cleanup
- Implement configurable auth factory pattern as suggested by @dpgaspar
  - Add unit test coverage
  - Refactor auth.py for better pythonic patterns and error handling
  - Add proper type annotations and logging throughout
  - Split complex functions into focused helper functions
  - Add detailed architecture documentation with Mermaid diagrams
  - Improve JWT token extraction with robust fallback logic
2025-07-30 14:20:41 -04:00
Amin Ghadersohi
56b308340e update: use real Superset user for MCP authentication instead of MCPUser
Replace MCPUser wrapper with actual Flask-AppBuilder User from database
  to enable proper RBAC permission filtering. Fixes MCP service returning
  0 counts due to empty permission queries.
2025-07-30 14:20:41 -04:00
Amin Ghadersohi
3b2b7609a8 update: add optional JWT Bearer authentication to MCP service
Implement comprehensive JWT authentication system for production deployments:

  • Add BearerAuthProvider integration with FastMCP for RS256 token validation
  • Support both static public keys and JWKS endpoints for enterprise key rotation
  • Implement scope-based authorization (dashboard:read, chart:write, etc.)
  • Extract user identity from JWT sub claim for proper audit trails
  • Maintain backward compatibility with admin fallback when auth disabled
  • Add comprehensive test coverage for authentication flows
  • Update documentation with human-friendly security setup guide

  Authentication is disabled by default for development convenience.
  Configure via environment variables for production use.
2025-07-30 14:20:40 -04:00
Amin Ghadersohi
61a91f80fe update deps 2025-07-30 14:20:40 -04:00
Amin Ghadersohi
ed3c5ecbc2 enhance MCP service with multi-identifier support and comprehensive UUID/slug functionality
- Add UUID field to ChartInfo and DatasetInfo Pydantic schemas for complete serialization
  - Include UUID in chart and dataset serialization functions (serialize_chart_object,
  serialize_dataset_object)
  - UUID and slug are now included in default response columns for better discoverability:
    * Dashboards: UUID and slug in DEFAULT_DASHBOARD_COLUMNS and returned by default
    * Charts: UUID in DEFAULT_CHART_COLUMNS and returned by default
    * Datasets: UUID in DEFAULT_DATASET_COLUMNS and returned by default
  - Search functionality enhanced to include UUID/slug fields across all relevant tools
  - Add comprehensive test coverage for UUID/slug functionality:
    * Default column verification tests ensuring UUID/slug are in default responses
    * Response data verification tests confirming UUID/slug values are returned
    * Custom column selection tests for explicit UUID/slug requests
    * Metadata accuracy tests verifying columns_requested/columns_loaded tracking
  - Update documentation to reflect enhanced multi-identifier capabilities
  - All 132 tests pass with comprehensive verification of UUID/slug support
2025-07-30 14:20:40 -04:00
Amin Ghadersohi
c8fbd4233c add multi-identifier support and fix columns metadata accuracy
**Multi-Identifier Support:**
  - Enhance ModelGetInfoTool to support ID, UUID, and slug lookup
  - Add intelligent identifier detection for get_*_info tools
  - Dashboards: support ID, UUID, and slug via id_or_slug_filter
  - Datasets/Charts: support ID and UUID via direct database queries
  - Add GetDashboardInfoRequest, GetDatasetInfoRequest, GetChartInfoRequest schemas

  **Enhanced Default Columns & Metadata:**
  - Add uuid to default columns for all list tools (dashboards, datasets, charts)
  - Include uuid/slug in search columns for better discoverability
  - Fix columns_requested to accurately reflect user input vs defaults
  - Fix columns_loaded to show actual DAO columns requested vs serialized fields

  **Testing & Code Quality:**
  - Add multi-identifier tests for all get_*_info tools (ID/UUID/slug scenarios)
  - Remove unused serialized_columns variable in ModelListTool
  - Fix linting issues (line length, docstrings)

  This provides flexible identifier support while ensuring accurate metadata
  tracking for better LLM compatibility and debugging.
2025-07-30 14:20:40 -04:00
Amin Ghadersohi
fc85f68585 update: implement FastMCP complex inputs pattern for MCP tools
- Replace individual parameters with structured request schemas for list_datasets, list_charts, and
  list_dashboards
  - Fix validation issues where LLMs passed arrays/objects as strings
  - Add ListDatasetsRequest, ListChartsRequest, ListDashboardsRequest schemas
  - Fix object serialization (charts, dashboards, datasets)
  - Add validation to prevent search+filters conflicts
  - Update all tests and fix linting issues

  Resolves string/array validation ambiguity that caused tool failures when LLMs sent complex
  parameters incorrectly.
2025-07-30 14:20:40 -04:00
Amin Ghadersohi
e825bbe1f4 update: generate_explore_link and create_chart working well 2025-07-30 14:20:40 -04:00
Amin Ghadersohi
a80b637a2a update: add a save_chart boolean parameter to create chart so that generate link to explore can use its code and just pass false 2025-07-30 14:20:40 -04:00
Amin Ghadersohi
541b3bd727 update: add docs to proxy 2025-07-30 14:20:40 -04:00
Amin Ghadersohi
a909799e5c update: simplify create chart a lot: support area, line, bar, scatter and table charts.
example prompt:
- can you use superset dataset 2 to plot popular baby names in 2024

- plot airline delay by day of week and group by airline use dataset 6
2025-07-30 14:20:39 -04:00
Amin Ghadersohi
01329f1c62 update: tests and deps 2025-07-30 14:20:39 -04:00
Amin Ghadersohi
67f621d360 update: remove create_chart_simple tool 2025-07-30 14:20:39 -04:00
Amin Ghadersohi
b552dbf4a1 update: fix all lint issues 2025-07-30 14:20:39 -04:00
Amin Ghadersohi
364af98c04 update: create_chart: Remove x_axis from groupby if present, update docs and tests
- Updates create_chart logic to automatically remove x_axis from groupby for ECharts timeseries charts, preventing duplicate dimension usage.
- Updates and expands unit test to verify x_axis is excluded from groupby, using improved test mocks for accurate backend simulation.
- Updates documentation (README.md, README_ARCHITECTURE.md, README_PHASE1_STATUS.md, README_SCHEMAS.md) to clarify create_chart tool behavior and schema, including new groupby/x_axis handling.
- No breaking changes to tool signatures; behavior is now more robust and LLM-friendly.
2025-07-30 14:20:39 -04:00
Amin Ghadersohi
afdb8b38a6 update: Add columns and metrics to DatasetInfo/list_datasets, improve test coverage
- Updated DatasetInfo schema to include columns and metrics fields, with new TableColumnInfo and SqlMetricInfo models.
- Updated serialize_dataset_object to serialize columns and metrics for each dataset.
- Modified list_datasets tool to use serialize_dataset_object and include columns/metrics by default.
- Improved and fixed all related unit tests to use proper MagicMock objects for columns/metrics and to parse JSON responses.
- Ensured LLM/OpenAPI compatibility for dataset listing and info tools.
2025-07-30 14:20:39 -04:00
Amin Ghadersohi
fc7ea804bc update: chart create working with union of types 2025-07-30 14:20:39 -04:00
Amin Ghadersohi
7c256ae9aa update: docs 2025-07-30 14:20:39 -04:00
Amin Ghadersohi
1b190abc3b update: add new ModelGetAvailableFiltersTool 2025-07-30 14:20:39 -04:00
Amin Ghadersohi
c6c71bf835 update: update tool registration to use decorators, and update all tests to use mcp client directly in the tests as recommended 2025-07-30 14:20:38 -04:00
Amin Ghadersohi
cd5ead7f11 update: update tool registration 2025-07-30 14:20:38 -04:00
Amin Ghadersohi
e5eebe28f9 update: refactor auth and model tools 2025-07-30 14:20:38 -04:00
Amin Ghadersohi
9eac6ef433 update: add tests for mcp list and info tools 2025-07-30 14:20:38 -04:00
Amin Ghadersohi
d523d523e5 update: refactor tools directories 2025-07-30 14:20:38 -04:00
Amin Ghadersohi
91a3214ed4 update: wip 2025-07-30 14:20:38 -04:00
Amin Ghadersohi
95b787f024 update: wip 2025-07-30 14:20:38 -04:00
Amin Ghadersohi
39121791e8 update: wip 2025-07-30 14:20:38 -04:00
Amin Ghadersohi
9d40fe913f update: wip 2025-07-30 14:20:37 -04:00
Amin Ghadersohi
748ae49c8c update: wip 2025-07-30 14:20:37 -04:00
Amin Ghadersohi
a9d543b6f4 update: unify FastMCP server, modularize tools, and document new DAO-based architecture
- Replace dual Flask/FastAPI setup with a single, unified FastMCP server (`server.py`)
- Introduce `MCPDAOWrapper` for secure, context-aware DAO access (`dao_wrapper.py`)
- Refactor all MCP tools to be modular and domain-organized (`tools/dashboard/`, `tools/chart/`, `tools/dataset/`, `tools/system/`)
- Strongly type all tool contracts with Pydantic v2 models, including full field documentation for LLM/OpenAPI compatibility
- Refactor and extend `BaseDAO` for robust, generic CRUD/list operations
- Add and update documentation:
  - Architecture and flow diagrams (`README_ARCHITECTURE.md`)
  - Tool schema reference and usage instructions (`README.md`, `README_SCHEMAS.md`)
  - Phase 1 status and roadmap (`README_PHASE1_STATUS.md`)
- Implement and test all core list/info tools for dashboards, datasets, and charts, with full search and filter support
- Add chart creation tool (`create_chart_simple`)
- Provide extension points for Preset-specific auth, RBAC, and logging (stubbed in Phase 1)
- Prepare for LLM/agent workflows and future command-based mutations (create/update/delete)
- Expand and update unit/integration test coverage for all tools
2025-07-30 14:20:37 -04:00
Amin Ghadersohi
55d6130fc4 update: docs and deps 2025-07-30 14:20:37 -04:00
Amin Ghadersohi
b98e3eb309 Superset MCP Service - Adding get_instance_info tool 2025-07-30 14:20:37 -04:00
Amin Ghadersohi
b469077e0e update: add list function to BaseDAO and use it to get and filter the dashboards 2025-07-30 14:20:37 -04:00
Amin Ghadersohi
397b4e450b Superset MCP Service - Initial Commit DRAFT 2025-07-30 14:20:37 -04:00
Amin Ghadersohi
0f97002520 update: Refactor BaseDAO: enhance column operator logic and expand test coverage
- Improved the BaseDAO class to robustly handle column operator logic, ensuring all supported operators (eq, ne, sw, ew, in, nin, gt, gte, lt, lte, like, ilike, is_null, is_not_null) are consistently applied via ColumnOperatorEnum.
- Refactored the apply_column_operators and list methods for clarity and reliability, including better handling of columns, relationships, and search.
- Removed 1 index base page handing from list
2025-07-30 14:20:37 -04:00
Amin Ghadersohi
2312250127 update: add idea of ColumnOperator 2025-07-30 14:20:36 -04:00
Amin Ghadersohi
cd52193869 update: add helper for applying base filter 2025-07-30 14:20:36 -04:00
Amin Ghadersohi
9ffe680aaa feat(dao): add robust generic list and count methods to BaseDAO with full test coverage
- Introduced generic `list` and `count` methods to BaseDAO for consistent querying and counting across all DAOs.
- Both methods support filtering (including IN queries), ordering, pagination, search across columns, custom FAB-style filters, and always-on base filters.
- Added comprehensive unit tests for `list` and `count` in `tests/unit_tests/dao/base_test.py`, covering:
  - Filtering (including boolean, None, and IN queries)
  - Ordering (asc/desc, multiple columns)
  - Pagination (including out-of-range)
  - Search across columns
  - Custom filter logic
  - Always-on base filter logic
  - Edge cases and skip_base_filter
- Moved common test fixtures to `conftest.py` for reuse.
2025-07-30 14:20:36 -04:00
dependabot[bot]
5c2eb0a68c build(deps): bump reselect from 4.1.7 to 5.1.1 in /superset-frontend (#30119)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2025-07-30 08:54:58 -07:00
dependabot[bot]
0cbf4d5d4d chore(deps): bump d3-scale from 3.3.0 to 4.0.2 in /superset-frontend/packages/superset-ui-core (#31534)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: Đỗ Trọng Hải <41283691+hainenber@users.noreply.github.com>
2025-07-30 08:52:30 -07:00
215 changed files with 29711 additions and 8372 deletions

View File

@@ -0,0 +1,19 @@
{
// 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"
}

View File

@@ -0,0 +1,39 @@
{
"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"
]
}
}
}

View File

@@ -4,6 +4,11 @@
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
@@ -21,7 +26,7 @@ fi
# Clean up any existing containers
echo "🧹 Cleaning up existing containers..."
docker-compose -f docker-compose-light.yml down
docker-compose -f docker-compose-light.yml --profile mcp down
# Start services
echo "🏗️ Building and starting services..."
@@ -33,7 +38,12 @@ echo ""
echo "📋 Running in foreground with live logs (Ctrl+C to stop)..."
# Run docker-compose and capture exit code
docker-compose -f docker-compose-light.yml up
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
EXIT_CODE=$?
# If it failed, provide helpful instructions

View File

@@ -0,0 +1,29 @@
{
// 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"
}
}

View File

@@ -55,7 +55,6 @@ esm/*
tsconfig.tsbuildinfo
.*ipynb
.*yml
.*yaml
.*iml
.esprintrc
.prettierignore

215
CHART_METADATA_API.md Normal file
View File

@@ -0,0 +1,215 @@
# 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

View File

@@ -180,6 +180,7 @@ 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

View File

@@ -23,8 +23,6 @@ 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.

View File

@@ -20,6 +20,9 @@
# 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:

View File

@@ -25,6 +25,12 @@
# - 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
# -----------------------------------------------------------------------
@@ -150,6 +156,37 @@ 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

View File

@@ -20,6 +20,9 @@
# 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

View File

@@ -20,6 +20,9 @@
# 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

View File

@@ -53,7 +53,12 @@ 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
@@ -66,4 +71,3 @@ 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

View File

@@ -78,6 +78,10 @@ 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!!!"
;;

View File

@@ -20,4 +20,5 @@
# DON'T ignore the .gitignore
!.gitignore
!superset_config.py
!superset_config_docker_light.py
!superset_config_local.example

View File

@@ -14,7 +14,6 @@
# 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

View File

@@ -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 examples load
superset load-examples
# Start the Flask dev web server from inside your virtualenv.
# Note that your page may not have CSS at this point.

View File

@@ -26,11 +26,14 @@ 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 3 major ways we support to run `docker compose`:
Note that there are 4 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
@@ -44,7 +47,7 @@ Note that there are 3 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 two approaches after setting up the requirements for either.
More on these approaches after setting up the requirements for either.
## Requirements
@@ -103,13 +106,36 @@ 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 - build a set of immutable images from the local branch
### 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
```bash
docker compose -f docker-compose-non-dev.yml up
```
### Option #3 - boot up an official release
### Option #4 - boot up an official release
```bash
# Set the version you want to run

View File

@@ -151,7 +151,7 @@ Finish installing by running through the following commands:
superset fab create-admin
# Load some data to play with
superset examples load
superset load_examples
# Create default roles and permissions
superset init

View File

@@ -0,0 +1,741 @@
---
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)

View File

@@ -0,0 +1,191 @@
---
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).

View File

@@ -0,0 +1,434 @@
---
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.

View File

@@ -0,0 +1,705 @@
---
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)

View File

@@ -0,0 +1,124 @@
---
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.

View File

@@ -0,0 +1,196 @@
---
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.

View File

@@ -0,0 +1,483 @@
---
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.

View File

@@ -87,6 +87,16 @@ const sidebars = {
},
],
},
{
type: 'category',
label: 'MCP Service',
items: [
{
type: 'autogenerated',
dirName: 'mcp-service',
},
],
},
{
type: 'doc',
label: 'FAQ',

View File

@@ -133,6 +133,7 @@ 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"]
@@ -202,6 +203,7 @@ 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",

View File

@@ -16,4 +16,4 @@
# specific language governing permissions and limitations
# under the License.
#
-e .[development,bigquery,druid,gevent,gsheets,mysql,postgres,presto,prophet,trino,thumbnails]
-e .[development,bigquery,druid,fastmcp,gevent,gsheets,mysql,postgres,presto,prophet,trino,thumbnails]

View File

@@ -10,6 +10,14 @@ 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
@@ -24,11 +32,14 @@ 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
@@ -77,6 +88,8 @@ celery==5.5.2
certifi==2025.6.15
# via
# -c requirements/base.txt
# httpcore
# httpx
# requests
# selenium
cffi==1.17.1
@@ -101,6 +114,7 @@ click==8.2.1
# click-repl
# flask
# flask-appbuilder
# uvicorn
click-didyoumean==0.3.1
# via
# -c requirements/base.txt
@@ -140,10 +154,13 @@ 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
@@ -168,14 +185,23 @@ 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
@@ -327,6 +353,8 @@ gunicorn==23.0.0
h11==0.16.0
# via
# -c requirements/base.txt
# httpcore
# uvicorn
# wsproto
hashids==1.3.1
# via
@@ -337,6 +365,14 @@ 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
@@ -346,7 +382,9 @@ identify==2.5.36
idna==3.10
# via
# -c requirements/base.txt
# anyio
# email-validator
# httpx
# requests
# trio
# url-normalize
@@ -378,6 +416,7 @@ jsonschema==4.23.0
# via
# -c requirements/base.txt
# flask-appbuilder
# mcp
# openapi-schema-validator
# openapi-spec-validator
jsonschema-path==0.3.4
@@ -437,6 +476,8 @@ 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
@@ -475,6 +516,8 @@ 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
@@ -607,6 +650,16 @@ 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
@@ -642,6 +695,8 @@ 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
@@ -649,8 +704,11 @@ 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
@@ -674,12 +732,16 @@ 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
@@ -734,7 +796,12 @@ 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
@@ -779,6 +846,7 @@ slack-sdk==3.35.0
sniffio==1.3.1
# via
# -c requirements/base.txt
# anyio
# trio
sortedcontainers==2.4.0
# via
@@ -808,10 +876,14 @@ 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
@@ -839,13 +911,23 @@ 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
@@ -864,6 +946,8 @@ urllib3==2.5.0
# requests
# requests-cache
# selenium
uvicorn==0.35.0
# via mcp
vine==5.1.0
# via
# -c requirements/base.txt

View File

@@ -33,4 +33,4 @@ superset load-test-users
echo "Running tests"
pytest --durations-min=2 --cov-report= --cov=superset ./tests/integration_tests "$@"
pytest --durations-min=2 --maxfail=1 --cov-report= --cov=superset ./tests/integration_tests "$@"

View File

@@ -10877,6 +10877,12 @@
"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",
@@ -24605,6 +24611,12 @@
"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",
@@ -50629,9 +50641,9 @@
"license": "MIT"
},
"node_modules/reselect": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
"integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/resize-observer-polyfill": {
@@ -59085,7 +59097,7 @@
"csstype": "^3.1.3",
"d3-format": "^1.3.2",
"d3-interpolate": "^3.0.1",
"d3-scale": "^3.0.0",
"d3-scale": "^4.0.2",
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"dayjs": "^1.11.13",
@@ -59108,7 +59120,7 @@
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.1",
"reselect": "^4.0.0",
"reselect": "^5.1.1",
"rison": "^0.1.1",
"seedrandom": "^3.0.5",
"xss": "^1.0.14"
@@ -59187,16 +59199,19 @@
"license": "BSD-3-Clause"
},
"packages/superset-ui-core/node_modules/d3-scale": {
"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",
"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",
"dependencies": {
"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"
"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"
}
},
"packages/superset-ui-core/node_modules/d3-scale/node_modules/d3-interpolate": {

View File

@@ -37,7 +37,7 @@
"d3-format": "^1.3.2",
"dayjs": "^1.11.13",
"d3-interpolate": "^3.0.1",
"d3-scale": "^3.0.0",
"d3-scale": "^4.0.2",
"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": "^4.0.0",
"reselect": "^5.1.1",
"rison": "^0.1.1",
"seedrandom": "^3.0.5",
"@visx/responsive": "^3.12.0",

View File

@@ -17,11 +17,8 @@
* 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 } from 'reselect';
import { createSelector, lruMemoize } from 'reselect';
import {
AppSection,
Behavior,
@@ -37,7 +34,7 @@ import {
SetDataMaskHook,
} from '../types/Base';
import { QueryData, DataRecordFilters } from '..';
import { SupersetTheme } from '../../theme';
import { supersetTheme, SupersetTheme } from '../../theme';
// TODO: more specific typing for these fields of ChartProps
type AnnotationData = PlainObject;
@@ -109,6 +106,8 @@ export interface ChartPropsConfig {
theme: SupersetTheme;
/* legend index */
legendIndex?: number;
inContextMenu?: boolean;
emitCrossFilters?: boolean;
}
const DEFAULT_WIDTH = 800;
@@ -161,7 +160,11 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
theme: SupersetTheme;
constructor(config: ChartPropsConfig & { formData?: FormData } = {}) {
constructor(
config: ChartPropsConfig & { formData?: FormData } = {
theme: supersetTheme,
},
) {
const {
annotationData = {},
datasource = {},
@@ -276,5 +279,16 @@ 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,
},
},
);
};

View File

@@ -119,7 +119,7 @@ describe('ChartProps', () => {
});
expect(props1).not.toBe(props2);
});
it('selector returns a new chartProps if some input fields change', () => {
it('selector returns a new chartProps if some input fields change and returns memoized chart props', () => {
const props1 = selector({
width: 800,
height: 600,
@@ -145,7 +145,7 @@ describe('ChartProps', () => {
theme: supersetTheme,
});
expect(props1).not.toBe(props2);
expect(props1).not.toBe(props3);
expect(props1).toBe(props3);
});
});
});

View File

@@ -30,7 +30,6 @@ 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")
@@ -41,41 +40,51 @@ 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")
load_energy(only_metadata, force)
examples.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]")
load_tabbed_dashboard(only_metadata)
examples.load_tabbed_dashboard(only_metadata)
logger.info("Loading [Supported Charts Dashboard]")
load_supported_charts_dashboard()
examples.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)
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 [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:
logger.info("Loading big synthetic data for tests")
load_big_data_func()
examples.load_big_data()
# 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)
@@ -103,222 +112,4 @@ 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)

43
superset/cli/mcp.py Normal file
View File

@@ -0,0 +1,43 @@
# 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)

View File

@@ -16,7 +16,6 @@
# under the License.
import copy
import logging
from inspect import isclass
from typing import Any
@@ -28,8 +27,6 @@ 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:
"""
@@ -66,13 +63,10 @@ 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)

View File

@@ -123,9 +123,6 @@ 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():

View File

@@ -166,14 +166,11 @@ 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()

View File

@@ -46,13 +46,10 @@ 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:

View File

@@ -124,13 +124,11 @@ 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()
@@ -211,12 +209,7 @@ 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)
# 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")
df = pd.read_csv(data, encoding="utf-8")
dtype = get_dtype(df, dataset)
# convert temporal columns

View File

@@ -37,6 +37,10 @@ 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:
@@ -47,7 +51,7 @@ class CreateFormDataCommand(BaseCommand):
form_data = self._cmd_params.form_data
check_access(datasource_id, chart_id, datasource_type)
contextual_key = cache_key(
session.get("_id"), tab_id, datasource_id, chart_id, datasource_type
self._get_session_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:

View File

@@ -195,5 +195,4 @@ class ImportExamplesCommand(ImportModelsCommand):
{"dashboard_id": dashboard_id, "slice_id": chart_id}
for (dashboard_id, chart_id) in dashboard_chart_ids
]
if values:
db.session.execute(dashboard_slices.insert(), values)
db.session.execute(dashboard_slices.insert(), values)

View File

@@ -1155,7 +1155,7 @@ class CeleryConfig: # pylint: disable=too-few-public-methods
}
CELERY_CONFIG: type[CeleryConfig] = CeleryConfig
CELERY_CONFIG: type[CeleryConfig] | None = CeleryConfig
# Set celery config to None to disable all the above configuration
# CELERY_CONFIG = None

View File

@@ -16,18 +16,87 @@
# under the License.
from __future__ import annotations
from typing import Any, Generic, get_args, TypeVar
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 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
@@ -50,45 +119,128 @@ class BaseDAO(Generic[T]):
)[0]
@classmethod
def find_by_id(
cls,
model_id: str | int,
skip_base_filter: bool = False,
) -> T | None:
def _apply_base_filter(
cls, query: Any, skip_base_filter: bool = False, data_model: Any = None
) -> Any:
"""
Find a model by id, if defined applies `base_filter`
Apply the base_filter to the query if it exists and skip_base_filter is False.
"""
query = db.session.query(cls.model_cls)
if cls.base_filter and not skip_base_filter:
data_model = SQLAInterface(cls.model_cls, db.session)
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)
id_column = getattr(cls.model_cls, cls.id_column_name)
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(
cls,
column_name: str,
value: 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
"""
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
try:
return query.filter(id_column == model_id).one_or_none()
return query.filter(column == converted_value).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: list[str] | list[int],
model_ids: Sequence[str | 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)
"""
id_col = getattr(cls.model_cls, cls.id_column_name, None)
column = id_column or cls.id_column_name
id_col = getattr(cls.model_cls, column, None)
if id_col is None:
return []
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)
# 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)
return query.all()
@classmethod
@@ -97,11 +249,7 @@ class BaseDAO(Generic[T]):
Get all that fit the `base_filter`
"""
query = db.session.query(cls.model_cls)
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)
query = cls._apply_base_filter(query)
return query.all()
@classmethod
@@ -110,11 +258,7 @@ class BaseDAO(Generic[T]):
Get the first that fit the `base_filter`
"""
query = db.session.query(cls.model_cls)
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)
query = cls._apply_base_filter(query)
return query.filter_by(**filter_by).one_or_none()
@classmethod
@@ -184,3 +328,247 @@ 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()

View File

@@ -18,7 +18,7 @@ from __future__ import annotations
import logging
from datetime import datetime
from typing import TYPE_CHECKING
from typing import Dict, List, TYPE_CHECKING
from superset.charts.filters import ChartFilter
from superset.daos.base import BaseDAO
@@ -36,6 +36,20 @@ 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]

View File

@@ -18,7 +18,7 @@ from __future__ import annotations
import logging
from datetime import datetime
from typing import Any
from typing import Any, Dict, List
from flask import g
from flask_appbuilder.models.sqla.interface import SQLAInterface
@@ -48,6 +48,20 @@ 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):

View File

@@ -18,7 +18,7 @@ from __future__ import annotations
import logging
from datetime import datetime
from typing import Any
from typing import Any, Dict, List
import dateutil.parser
from sqlalchemy.exc import SQLAlchemyError
@@ -37,6 +37,13 @@ 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
@@ -351,6 +358,18 @@ 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

View File

@@ -0,0 +1,71 @@
# 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()

View File

@@ -0,0 +1,869 @@
# 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

View File

@@ -1,26 +0,0 @@
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

View File

@@ -1,21 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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

View File

@@ -1,30 +0,0 @@
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

View File

@@ -1,32 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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

View File

@@ -1,30 +0,0 @@
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

View File

@@ -1,28 +0,0 @@
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

View File

@@ -1,22 +0,0 @@
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

View File

@@ -1,44 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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

View File

@@ -1,30 +0,0 @@
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

View File

@@ -1,36 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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

View File

@@ -1,27 +0,0 @@
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

View File

@@ -1,28 +0,0 @@
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

View File

@@ -1,21 +0,0 @@
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

View File

@@ -1,32 +0,0 @@
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

View File

@@ -1,39 +0,0 @@
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

View File

@@ -1,35 +0,0 @@
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

View File

@@ -1,39 +0,0 @@
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

View File

@@ -1,31 +0,0 @@
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

View File

@@ -1,120 +0,0 @@
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

View File

@@ -1,30 +0,0 @@
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

View File

@@ -1,29 +0,0 @@
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

View File

@@ -1,51 +0,0 @@
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

View File

@@ -1,28 +0,0 @@
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

View File

@@ -1,36 +0,0 @@
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

View File

@@ -1,38 +0,0 @@
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

View File

@@ -1,28 +0,0 @@
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

View File

@@ -1,28 +0,0 @@
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

View File

@@ -1,26 +0,0 @@
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

View File

@@ -1,48 +0,0 @@
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

View File

@@ -1,43 +0,0 @@
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

View File

@@ -1,42 +0,0 @@
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

View File

@@ -1,48 +0,0 @@
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

View File

@@ -1,88 +0,0 @@
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

View File

@@ -1,42 +0,0 @@
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

View File

@@ -1,41 +0,0 @@
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

View File

@@ -1,83 +0,0 @@
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

View File

@@ -1,263 +0,0 @@
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

View File

@@ -1,175 +0,0 @@
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

View File

@@ -1,130 +0,0 @@
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

View File

@@ -1,80 +0,0 @@
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

View File

@@ -1,209 +0,0 @@
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

View File

@@ -1,137 +0,0 @@
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

View File

@@ -1,560 +0,0 @@
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

Some files were not shown because too many files have changed in this diff Show More