mirror of
https://github.com/apache/superset.git
synced 2026-04-07 10:31:50 +00:00
docs: bifurcate documentation into user and admin sections (#38196)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
590
docs/developer_docs/api.mdx
Normal file
590
docs/developer_docs/api.mdx
Normal file
@@ -0,0 +1,590 @@
|
||||
---
|
||||
title: API Reference
|
||||
hide_title: true
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
import { Alert } from 'antd';
|
||||
|
||||
## REST API Reference
|
||||
|
||||
Superset exposes a comprehensive **REST API** that follows the [OpenAPI specification](https://swagger.io/specification/).
|
||||
You can use this API to programmatically interact with Superset for automation, integrations, and custom applications.
|
||||
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="Code Samples & Schema Documentation"
|
||||
description={
|
||||
<span>
|
||||
Each endpoint includes ready-to-use code samples in <strong>cURL</strong>, <strong>Python</strong>, and <strong>JavaScript</strong>.
|
||||
The sidebar includes <strong>Schema definitions</strong> for detailed data model documentation.
|
||||
</span>
|
||||
}
|
||||
style={{ marginBottom: '24px' }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
### Authentication
|
||||
|
||||
Most API endpoints require authentication via JWT tokens.
|
||||
|
||||
#### Quick Start
|
||||
|
||||
```bash
|
||||
# 1. Get a JWT token
|
||||
curl -X POST http://localhost:8088/api/v1/security/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "admin", "password": "admin", "provider": "db"}'
|
||||
|
||||
# 2. Use the access_token from the response
|
||||
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||
http://localhost:8088/api/v1/dashboard/
|
||||
```
|
||||
|
||||
#### Security Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get the CSRF token](./api/get-the-csrf-token) | `/api/v1/security/csrf_token/` |
|
||||
| `POST` | [Get a guest token](./api/get-a-guest-token) | `/api/v1/security/guest_token/` |
|
||||
| `POST` | [Create security login](./api/create-security-login) | `/api/v1/security/login` |
|
||||
| `POST` | [Create security refresh](./api/create-security-refresh) | `/api/v1/security/refresh` |
|
||||
|
||||
---
|
||||
|
||||
### API Endpoints
|
||||
|
||||
#### Core Resources
|
||||
|
||||
<details>
|
||||
<summary><strong>Dashboards</strong> (26 endpoints) — Create, read, update, and delete dashboards.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DELETE` | [Bulk delete dashboards](./api/bulk-delete-dashboards) | `/api/v1/dashboard/` |
|
||||
| `GET` | [Get a list of dashboards](./api/get-a-list-of-dashboards) | `/api/v1/dashboard/` |
|
||||
| `POST` | [Create a new dashboard](./api/create-a-new-dashboard) | `/api/v1/dashboard/` |
|
||||
| `GET` | [Get metadata information about this API resource (dashboard--info)](./api/get-metadata-information-about-this-api-resource-dashboard-info) | `/api/v1/dashboard/_info` |
|
||||
| `GET` | [Get a dashboard detail information](./api/get-a-dashboard-detail-information) | `/api/v1/dashboard/{id_or_slug}` |
|
||||
| `GET` | [Get a dashboard's chart definitions.](./api/get-a-dashboards-chart-definitions) | `/api/v1/dashboard/{id_or_slug}/charts` |
|
||||
| `POST` | [Create a copy of an existing dashboard](./api/create-a-copy-of-an-existing-dashboard) | `/api/v1/dashboard/{id_or_slug}/copy/` |
|
||||
| `GET` | [Get dashboard's datasets](./api/get-dashboards-datasets) | `/api/v1/dashboard/{id_or_slug}/datasets` |
|
||||
| `DELETE` | [Delete a dashboard's embedded configuration](./api/delete-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
|
||||
| `GET` | [Get the dashboard's embedded configuration](./api/get-the-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
|
||||
| `POST` | [Set a dashboard's embedded configuration](./api/set-a-dashboards-embedded-configuration) | `/api/v1/dashboard/{id_or_slug}/embedded` |
|
||||
| `PUT` | [Update dashboard by id_or_slug embedded](./api/update-dashboard-by-id-or-slug-embedded) | `/api/v1/dashboard/{id_or_slug}/embedded` |
|
||||
| `GET` | [Get dashboard's tabs](./api/get-dashboards-tabs) | `/api/v1/dashboard/{id_or_slug}/tabs` |
|
||||
| `DELETE` | [Delete a dashboard](./api/delete-a-dashboard) | `/api/v1/dashboard/{pk}` |
|
||||
| `PUT` | [Update a dashboard](./api/update-a-dashboard) | `/api/v1/dashboard/{pk}` |
|
||||
| `POST` | [Compute and cache a screenshot (dashboard-pk-cache-dashboard-screenshot)](./api/compute-and-cache-a-screenshot-dashboard-pk-cache-dashboard-screenshot) | `/api/v1/dashboard/{pk}/cache_dashboard_screenshot/` |
|
||||
| `PUT` | [Update colors configuration for a dashboard.](./api/update-colors-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/colors` |
|
||||
| `DELETE` | [Remove the dashboard from the user favorite list](./api/remove-the-dashboard-from-the-user-favorite-list) | `/api/v1/dashboard/{pk}/favorites/` |
|
||||
| `POST` | [Mark the dashboard as favorite for the current user](./api/mark-the-dashboard-as-favorite-for-the-current-user) | `/api/v1/dashboard/{pk}/favorites/` |
|
||||
| `PUT` | [Update native filters configuration for a dashboard.](./api/update-native-filters-configuration-for-a-dashboard) | `/api/v1/dashboard/{pk}/filters` |
|
||||
| `GET` | [Get a computed screenshot from cache (dashboard-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-dashboard-pk-screenshot-digest) | `/api/v1/dashboard/{pk}/screenshot/{digest}/` |
|
||||
| `GET` | [Get dashboard's thumbnail](./api/get-dashboards-thumbnail) | `/api/v1/dashboard/{pk}/thumbnail/{digest}/` |
|
||||
| `GET` | [Download multiple dashboards as YAML files](./api/download-multiple-dashboards-as-yaml-files) | `/api/v1/dashboard/export/` |
|
||||
| `GET` | [Check favorited dashboards for current user](./api/check-favorited-dashboards-for-current-user) | `/api/v1/dashboard/favorite_status/` |
|
||||
| `POST` | [Import dashboard(s) with associated charts/datasets/databases](./api/import-dashboard-s-with-associated-charts-datasets-databases) | `/api/v1/dashboard/import/` |
|
||||
| `GET` | [Get related fields data (dashboard-related-column-name)](./api/get-related-fields-data-dashboard-related-column-name) | `/api/v1/dashboard/related/{column_name}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Charts</strong> (20 endpoints) — Create, read, update, and delete charts (slices).</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DELETE` | [Bulk delete charts](./api/bulk-delete-charts) | `/api/v1/chart/` |
|
||||
| `GET` | [Get a list of charts](./api/get-a-list-of-charts) | `/api/v1/chart/` |
|
||||
| `POST` | [Create a new chart](./api/create-a-new-chart) | `/api/v1/chart/` |
|
||||
| `GET` | [Get metadata information about this API resource (chart--info)](./api/get-metadata-information-about-this-api-resource-chart-info) | `/api/v1/chart/_info` |
|
||||
| `DELETE` | [Delete a chart](./api/delete-a-chart) | `/api/v1/chart/{pk}` |
|
||||
| `GET` | [Get a chart detail information](./api/get-a-chart-detail-information) | `/api/v1/chart/{pk}` |
|
||||
| `PUT` | [Update a chart](./api/update-a-chart) | `/api/v1/chart/{pk}` |
|
||||
| `GET` | [Compute and cache a screenshot (chart-pk-cache-screenshot)](./api/compute-and-cache-a-screenshot-chart-pk-cache-screenshot) | `/api/v1/chart/{pk}/cache_screenshot/` |
|
||||
| `GET` | [Return payload data response for a chart](./api/return-payload-data-response-for-a-chart) | `/api/v1/chart/{pk}/data/` |
|
||||
| `DELETE` | [Remove the chart from the user favorite list](./api/remove-the-chart-from-the-user-favorite-list) | `/api/v1/chart/{pk}/favorites/` |
|
||||
| `POST` | [Mark the chart as favorite for the current user](./api/mark-the-chart-as-favorite-for-the-current-user) | `/api/v1/chart/{pk}/favorites/` |
|
||||
| `GET` | [Get a computed screenshot from cache (chart-pk-screenshot-digest)](./api/get-a-computed-screenshot-from-cache-chart-pk-screenshot-digest) | `/api/v1/chart/{pk}/screenshot/{digest}/` |
|
||||
| `GET` | [Get chart thumbnail](./api/get-chart-thumbnail) | `/api/v1/chart/{pk}/thumbnail/{digest}/` |
|
||||
| `POST` | [Return payload data response for the given query (chart-data)](./api/return-payload-data-response-for-the-given-query-chart-data) | `/api/v1/chart/data` |
|
||||
| `GET` | [Return payload data response for the given query (chart-data-cache-key)](./api/return-payload-data-response-for-the-given-query-chart-data-cache-key) | `/api/v1/chart/data/{cache_key}` |
|
||||
| `GET` | [Download multiple charts as YAML files](./api/download-multiple-charts-as-yaml-files) | `/api/v1/chart/export/` |
|
||||
| `GET` | [Check favorited charts for current user](./api/check-favorited-charts-for-current-user) | `/api/v1/chart/favorite_status/` |
|
||||
| `POST` | [Import chart(s) with associated datasets and databases](./api/import-chart-s-with-associated-datasets-and-databases) | `/api/v1/chart/import/` |
|
||||
| `GET` | [Get related fields data (chart-related-column-name)](./api/get-related-fields-data-chart-related-column-name) | `/api/v1/chart/related/{column_name}` |
|
||||
| `PUT` | [Warm up the cache for the chart](./api/warm-up-the-cache-for-the-chart) | `/api/v1/chart/warm_up_cache` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Datasets</strong> (18 endpoints) — Manage datasets (tables) used for building charts.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DELETE` | [Bulk delete datasets](./api/bulk-delete-datasets) | `/api/v1/dataset/` |
|
||||
| `GET` | [Get a list of datasets](./api/get-a-list-of-datasets) | `/api/v1/dataset/` |
|
||||
| `POST` | [Create a new dataset](./api/create-a-new-dataset) | `/api/v1/dataset/` |
|
||||
| `GET` | [Get metadata information about this API resource (dataset--info)](./api/get-metadata-information-about-this-api-resource-dataset-info) | `/api/v1/dataset/_info` |
|
||||
| `DELETE` | [Delete a dataset](./api/delete-a-dataset) | `/api/v1/dataset/{pk}` |
|
||||
| `GET` | [Get a dataset](./api/get-a-dataset) | `/api/v1/dataset/{pk}` |
|
||||
| `PUT` | [Update a dataset](./api/update-a-dataset) | `/api/v1/dataset/{pk}` |
|
||||
| `DELETE` | [Delete a dataset column](./api/delete-a-dataset-column) | `/api/v1/dataset/{pk}/column/{column_id}` |
|
||||
| `DELETE` | [Delete a dataset metric](./api/delete-a-dataset-metric) | `/api/v1/dataset/{pk}/metric/{metric_id}` |
|
||||
| `PUT` | [Refresh and update columns of a dataset](./api/refresh-and-update-columns-of-a-dataset) | `/api/v1/dataset/{pk}/refresh` |
|
||||
| `GET` | [Get charts and dashboards count associated to a dataset](./api/get-charts-and-dashboards-count-associated-to-a-dataset) | `/api/v1/dataset/{pk}/related_objects` |
|
||||
| `GET` | [Get distinct values from field data (dataset-distinct-column-name)](./api/get-distinct-values-from-field-data-dataset-distinct-column-name) | `/api/v1/dataset/distinct/{column_name}` |
|
||||
| `POST` | [Duplicate a dataset](./api/duplicate-a-dataset) | `/api/v1/dataset/duplicate` |
|
||||
| `GET` | [Download multiple datasets as YAML files](./api/download-multiple-datasets-as-yaml-files) | `/api/v1/dataset/export/` |
|
||||
| `POST` | [Retrieve a table by name, or create it if it does not exist](./api/retrieve-a-table-by-name-or-create-it-if-it-does-not-exist) | `/api/v1/dataset/get_or_create/` |
|
||||
| `POST` | [Import dataset(s) with associated databases](./api/import-dataset-s-with-associated-databases) | `/api/v1/dataset/import/` |
|
||||
| `GET` | [Get related fields data (dataset-related-column-name)](./api/get-related-fields-data-dataset-related-column-name) | `/api/v1/dataset/related/{column_name}` |
|
||||
| `PUT` | [Warm up the cache for each chart powered by the given table](./api/warm-up-the-cache-for-each-chart-powered-by-the-given-table) | `/api/v1/dataset/warm_up_cache` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Database</strong> (31 endpoints) — Manage database connections and metadata.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get a list of databases](./api/get-a-list-of-databases) | `/api/v1/database/` |
|
||||
| `POST` | [Create a new database](./api/create-a-new-database) | `/api/v1/database/` |
|
||||
| `GET` | [Get metadata information about this API resource (database--info)](./api/get-metadata-information-about-this-api-resource-database-info) | `/api/v1/database/_info` |
|
||||
| `DELETE` | [Delete a database](./api/delete-a-database) | `/api/v1/database/{pk}` |
|
||||
| `GET` | [Get a database](./api/get-a-database) | `/api/v1/database/{pk}` |
|
||||
| `PUT` | [Change a database](./api/change-a-database) | `/api/v1/database/{pk}` |
|
||||
| `GET` | [Get all catalogs from a database](./api/get-all-catalogs-from-a-database) | `/api/v1/database/{pk}/catalogs/` |
|
||||
| `GET` | [Get a database connection info](./api/get-a-database-connection-info) | `/api/v1/database/{pk}/connection` |
|
||||
| `GET` | [Get function names supported by a database](./api/get-function-names-supported-by-a-database) | `/api/v1/database/{pk}/function_names/` |
|
||||
| `GET` | [Get charts and dashboards count associated to a database](./api/get-charts-and-dashboards-count-associated-to-a-database) | `/api/v1/database/{pk}/related_objects/` |
|
||||
| `GET` | [The list of the database schemas where to upload information](./api/the-list-of-the-database-schemas-where-to-upload-information) | `/api/v1/database/{pk}/schemas_access_for_file_upload/` |
|
||||
| `GET` | [Get all schemas from a database](./api/get-all-schemas-from-a-database) | `/api/v1/database/{pk}/schemas/` |
|
||||
| `GET` | [Get database select star for table (database-pk-select-star-table-name)](./api/get-database-select-star-for-table-database-pk-select-star-table-name) | `/api/v1/database/{pk}/select_star/{table_name}/` |
|
||||
| `GET` | [Get database select star for table (database-pk-select-star-table-name-schema-name)](./api/get-database-select-star-for-table-database-pk-select-star-table-name-schema-name) | `/api/v1/database/{pk}/select_star/{table_name}/{schema_name}/` |
|
||||
| `DELETE` | [Delete a SSH tunnel](./api/delete-a-ssh-tunnel) | `/api/v1/database/{pk}/ssh_tunnel/` |
|
||||
| `POST` | [Re-sync all permissions for a database connection](./api/re-sync-all-permissions-for-a-database-connection) | `/api/v1/database/{pk}/sync_permissions/` |
|
||||
| `GET` | [Get table extra metadata (database-pk-table-extra-table-name-schema-name)](./api/get-table-extra-metadata-database-pk-table-extra-table-name-schema-name) | `/api/v1/database/{pk}/table_extra/{table_name}/{schema_name}/` |
|
||||
| `GET` | [Get table metadata](./api/get-table-metadata) | `/api/v1/database/{pk}/table_metadata/` |
|
||||
| `GET` | [Get table extra metadata (database-pk-table-metadata-extra)](./api/get-table-extra-metadata-database-pk-table-metadata-extra) | `/api/v1/database/{pk}/table_metadata/extra/` |
|
||||
| `GET` | [Get database table metadata](./api/get-database-table-metadata) | `/api/v1/database/{pk}/table/{table_name}/{schema_name}/` |
|
||||
| `GET` | [Get a list of tables for given database](./api/get-a-list-of-tables-for-given-database) | `/api/v1/database/{pk}/tables/` |
|
||||
| `POST` | [Upload a file to a database table](./api/upload-a-file-to-a-database-table) | `/api/v1/database/{pk}/upload/` |
|
||||
| `POST` | [Validate arbitrary SQL](./api/validate-arbitrary-sql) | `/api/v1/database/{pk}/validate_sql/` |
|
||||
| `GET` | [Get names of databases currently available](./api/get-names-of-databases-currently-available) | `/api/v1/database/available/` |
|
||||
| `GET` | [Download database(s) and associated dataset(s) as a zip file](./api/download-database-s-and-associated-dataset-s-as-a-zip-file) | `/api/v1/database/export/` |
|
||||
| `POST` | [Import database(s) with associated datasets](./api/import-database-s-with-associated-datasets) | `/api/v1/database/import/` |
|
||||
| `GET` | [Receive personal access tokens from OAuth2](./api/receive-personal-access-tokens-from-o-auth-2) | `/api/v1/database/oauth2/` |
|
||||
| `GET` | [Get related fields data (database-related-column-name)](./api/get-related-fields-data-database-related-column-name) | `/api/v1/database/related/{column_name}` |
|
||||
| `POST` | [Test a database connection](./api/test-a-database-connection) | `/api/v1/database/test_connection/` |
|
||||
| `POST` | [Upload a file and returns file metadata](./api/upload-a-file-and-returns-file-metadata) | `/api/v1/database/upload_metadata/` |
|
||||
| `POST` | [Validate database connection parameters](./api/validate-database-connection-parameters) | `/api/v1/database/validate_parameters/` |
|
||||
|
||||
</details>
|
||||
|
||||
#### Data Exploration
|
||||
|
||||
<details>
|
||||
<summary><strong>Explore</strong> (1 endpoints) — Chart exploration and data querying endpoints.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Assemble Explore related information in a single endpoint](./api/assemble-explore-related-information-in-a-single-endpoint) | `/api/v1/explore/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>SQL Lab</strong> (6 endpoints) — Execute SQL queries and manage SQL Lab sessions.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get the bootstrap data for SqlLab page](./api/get-the-bootstrap-data-for-sql-lab-page) | `/api/v1/sqllab/` |
|
||||
| `POST` | [Estimate the SQL query execution cost](./api/estimate-the-sql-query-execution-cost) | `/api/v1/sqllab/estimate/` |
|
||||
| `POST` | [Execute a SQL query](./api/execute-a-sql-query) | `/api/v1/sqllab/execute/` |
|
||||
| `GET` | [Export the SQL query results to a CSV](./api/export-the-sql-query-results-to-a-csv) | `/api/v1/sqllab/export/{client_id}/` |
|
||||
| `POST` | [Format SQL code](./api/format-sql-code) | `/api/v1/sqllab/format_sql/` |
|
||||
| `GET` | [Get the result of a SQL query execution](./api/get-the-result-of-a-sql-query-execution) | `/api/v1/sqllab/results/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Queries</strong> (17 endpoints) — View and manage SQL Lab query history.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get a list of queries](./api/get-a-list-of-queries) | `/api/v1/query/` |
|
||||
| `GET` | [Get query detail information](./api/get-query-detail-information) | `/api/v1/query/{pk}` |
|
||||
| `GET` | [Get distinct values from field data (query-distinct-column-name)](./api/get-distinct-values-from-field-data-query-distinct-column-name) | `/api/v1/query/distinct/{column_name}` |
|
||||
| `GET` | [Get related fields data (query-related-column-name)](./api/get-related-fields-data-query-related-column-name) | `/api/v1/query/related/{column_name}` |
|
||||
| `POST` | [Manually stop a query with client_id](./api/manually-stop-a-query-with-client-id) | `/api/v1/query/stop` |
|
||||
| `GET` | [Get a list of queries that changed after last_updated_ms](./api/get-a-list-of-queries-that-changed-after-last-updated-ms) | `/api/v1/query/updated_since` |
|
||||
| `DELETE` | [Bulk delete saved queries](./api/bulk-delete-saved-queries) | `/api/v1/saved_query/` |
|
||||
| `GET` | [Get a list of saved queries](./api/get-a-list-of-saved-queries) | `/api/v1/saved_query/` |
|
||||
| `POST` | [Create a saved query](./api/create-a-saved-query) | `/api/v1/saved_query/` |
|
||||
| `GET` | [Get metadata information about this API resource (saved-query--info)](./api/get-metadata-information-about-this-api-resource-saved-query-info) | `/api/v1/saved_query/_info` |
|
||||
| `DELETE` | [Delete a saved query](./api/delete-a-saved-query) | `/api/v1/saved_query/{pk}` |
|
||||
| `GET` | [Get a saved query](./api/get-a-saved-query) | `/api/v1/saved_query/{pk}` |
|
||||
| `PUT` | [Update a saved query](./api/update-a-saved-query) | `/api/v1/saved_query/{pk}` |
|
||||
| `GET` | [Get distinct values from field data (saved-query-distinct-column-name)](./api/get-distinct-values-from-field-data-saved-query-distinct-column-name) | `/api/v1/saved_query/distinct/{column_name}` |
|
||||
| `GET` | [Download multiple saved queries as YAML files](./api/download-multiple-saved-queries-as-yaml-files) | `/api/v1/saved_query/export/` |
|
||||
| `POST` | [Import saved queries with associated databases](./api/import-saved-queries-with-associated-databases) | `/api/v1/saved_query/import/` |
|
||||
| `GET` | [Get related fields data (saved-query-related-column-name)](./api/get-related-fields-data-saved-query-related-column-name) | `/api/v1/saved_query/related/{column_name}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Datasources</strong> (1 endpoints) — Query datasource metadata and column values.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get possible values for a datasource column](./api/get-possible-values-for-a-datasource-column) | `/api/v1/datasource/{datasource_type}/{datasource_id}/column/{column_name}/values/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Advanced Data Type</strong> (2 endpoints) — Endpoints for advanced data type operations and conversions.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Return an AdvancedDataTypeResponse](./api/return-an-advanced-data-type-response) | `/api/v1/advanced_data_type/convert` |
|
||||
| `GET` | [Return a list of available advanced data types](./api/return-a-list-of-available-advanced-data-types) | `/api/v1/advanced_data_type/types` |
|
||||
|
||||
</details>
|
||||
|
||||
#### Organization & Customization
|
||||
|
||||
<details>
|
||||
<summary><strong>Tags</strong> (15 endpoints) — Organize assets with tags.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DELETE` | [Bulk delete tags](./api/bulk-delete-tags) | `/api/v1/tag/` |
|
||||
| `GET` | [Get a list of tags](./api/get-a-list-of-tags) | `/api/v1/tag/` |
|
||||
| `POST` | [Create a tag](./api/create-a-tag) | `/api/v1/tag/` |
|
||||
| `GET` | [Get metadata information about tag API endpoints](./api/get-metadata-information-about-tag-api-endpoints) | `/api/v1/tag/_info` |
|
||||
| `POST` | [Add tags to an object](./api/add-tags-to-an-object) | `/api/v1/tag/{object_type}/{object_id}/` |
|
||||
| `DELETE` | [Delete a tagged object](./api/delete-a-tagged-object) | `/api/v1/tag/{object_type}/{object_id}/{tag}/` |
|
||||
| `DELETE` | [Delete a tag](./api/delete-a-tag) | `/api/v1/tag/{pk}` |
|
||||
| `GET` | [Get a tag detail information](./api/get-a-tag-detail-information) | `/api/v1/tag/{pk}` |
|
||||
| `PUT` | [Update a tag](./api/update-a-tag) | `/api/v1/tag/{pk}` |
|
||||
| `DELETE` | [Delete tag by pk favorites](./api/delete-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
|
||||
| `POST` | [Create tag by pk favorites](./api/create-tag-by-pk-favorites) | `/api/v1/tag/{pk}/favorites/` |
|
||||
| `POST` | [Bulk create tags and tagged objects](./api/bulk-create-tags-and-tagged-objects) | `/api/v1/tag/bulk_create` |
|
||||
| `GET` | [Get tag favorite status](./api/get-tag-favorite-status) | `/api/v1/tag/favorite_status/` |
|
||||
| `GET` | [Get all objects associated with a tag](./api/get-all-objects-associated-with-a-tag) | `/api/v1/tag/get_objects/` |
|
||||
| `GET` | [Get related fields data (tag-related-column-name)](./api/get-related-fields-data-tag-related-column-name) | `/api/v1/tag/related/{column_name}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Annotation Layers</strong> (14 endpoints) — Manage annotation layers and annotations for charts.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DELETE` | [Delete multiple annotation layers in a bulk operation](./api/delete-multiple-annotation-layers-in-a-bulk-operation) | `/api/v1/annotation_layer/` |
|
||||
| `GET` | [Get a list of annotation layers (annotation-layer)](./api/get-a-list-of-annotation-layers-annotation-layer) | `/api/v1/annotation_layer/` |
|
||||
| `POST` | [Create an annotation layer (annotation-layer)](./api/create-an-annotation-layer-annotation-layer) | `/api/v1/annotation_layer/` |
|
||||
| `GET` | [Get metadata information about this API resource (annotation-layer--info)](./api/get-metadata-information-about-this-api-resource-annotation-layer-info) | `/api/v1/annotation_layer/_info` |
|
||||
| `DELETE` | [Delete annotation layer (annotation-layer-pk)](./api/delete-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
|
||||
| `GET` | [Get an annotation layer (annotation-layer-pk)](./api/get-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
|
||||
| `PUT` | [Update an annotation layer (annotation-layer-pk)](./api/update-an-annotation-layer-annotation-layer-pk) | `/api/v1/annotation_layer/{pk}` |
|
||||
| `DELETE` | [Bulk delete annotation layers](./api/bulk-delete-annotation-layers) | `/api/v1/annotation_layer/{pk}/annotation/` |
|
||||
| `GET` | [Get a list of annotation layers (annotation-layer-pk-annotation)](./api/get-a-list-of-annotation-layers-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
|
||||
| `POST` | [Create an annotation layer (annotation-layer-pk-annotation)](./api/create-an-annotation-layer-annotation-layer-pk-annotation) | `/api/v1/annotation_layer/{pk}/annotation/` |
|
||||
| `DELETE` | [Delete annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/delete-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
|
||||
| `GET` | [Get an annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/get-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
|
||||
| `PUT` | [Update an annotation layer (annotation-layer-pk-annotation-annotation-id)](./api/update-an-annotation-layer-annotation-layer-pk-annotation-annotation-id) | `/api/v1/annotation_layer/{pk}/annotation/{annotation_id}` |
|
||||
| `GET` | [Get related fields data (annotation-layer-related-column-name)](./api/get-related-fields-data-annotation-layer-related-column-name) | `/api/v1/annotation_layer/related/{column_name}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>CSS Templates</strong> (8 endpoints) — Manage CSS templates for custom dashboard styling.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DELETE` | [Bulk delete CSS templates](./api/bulk-delete-css-templates) | `/api/v1/css_template/` |
|
||||
| `GET` | [Get a list of CSS templates](./api/get-a-list-of-css-templates) | `/api/v1/css_template/` |
|
||||
| `POST` | [Create a CSS template](./api/create-a-css-template) | `/api/v1/css_template/` |
|
||||
| `GET` | [Get metadata information about this API resource (css-template--info)](./api/get-metadata-information-about-this-api-resource-css-template-info) | `/api/v1/css_template/_info` |
|
||||
| `DELETE` | [Delete a CSS template](./api/delete-a-css-template) | `/api/v1/css_template/{pk}` |
|
||||
| `GET` | [Get a CSS template](./api/get-a-css-template) | `/api/v1/css_template/{pk}` |
|
||||
| `PUT` | [Update a CSS template](./api/update-a-css-template) | `/api/v1/css_template/{pk}` |
|
||||
| `GET` | [Get related fields data (css-template-related-column-name)](./api/get-related-fields-data-css-template-related-column-name) | `/api/v1/css_template/related/{column_name}` |
|
||||
|
||||
</details>
|
||||
|
||||
#### Sharing & Embedding
|
||||
|
||||
<details>
|
||||
<summary><strong>Dashboard Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to dashboard states.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | [Create a new dashboard's permanent link](./api/create-a-new-dashboards-permanent-link) | `/api/v1/dashboard/{pk}/permalink` |
|
||||
| `GET` | [Get dashboard's permanent link state](./api/get-dashboards-permanent-link-state) | `/api/v1/dashboard/permalink/{key}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Explore Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to chart explore states.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | [Create a new permanent link (explore-permalink)](./api/create-a-new-permanent-link-explore-permalink) | `/api/v1/explore/permalink` |
|
||||
| `GET` | [Get chart's permanent link state](./api/get-charts-permanent-link-state) | `/api/v1/explore/permalink/{key}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>SQL Lab Permanent Link</strong> (2 endpoints) — Create and retrieve permanent links to SQL Lab states.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | [Create a new permanent link (sqllab-permalink)](./api/create-a-new-permanent-link-sqllab-permalink) | `/api/v1/sqllab/permalink` |
|
||||
| `GET` | [Get permanent link state for SQLLab editor.](./api/get-permanent-link-state-for-sql-lab-editor) | `/api/v1/sqllab/permalink/{key}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Embedded Dashboard</strong> (1 endpoints) — Configure embedded dashboard settings.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get a report schedule log (embedded-dashboard-uuid)](./api/get-a-report-schedule-log-embedded-dashboard-uuid) | `/api/v1/embedded_dashboard/{uuid}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Dashboard Filter State</strong> (4 endpoints) — Manage temporary filter state for dashboards.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | [Create a dashboard's filter state](./api/create-a-dashboards-filter-state) | `/api/v1/dashboard/{pk}/filter_state` |
|
||||
| `DELETE` | [Delete a dashboard's filter state value](./api/delete-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
|
||||
| `GET` | [Get a dashboard's filter state value](./api/get-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
|
||||
| `PUT` | [Update a dashboard's filter state value](./api/update-a-dashboards-filter-state-value) | `/api/v1/dashboard/{pk}/filter_state/{key}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Explore Form Data</strong> (4 endpoints) — Manage temporary form data for chart exploration.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | [Create a new form_data](./api/create-a-new-form-data) | `/api/v1/explore/form_data` |
|
||||
| `DELETE` | [Delete a form_data](./api/delete-a-form-data) | `/api/v1/explore/form_data/{key}` |
|
||||
| `GET` | [Get a form_data](./api/get-a-form-data) | `/api/v1/explore/form_data/{key}` |
|
||||
| `PUT` | [Update an existing form_data](./api/update-an-existing-form-data) | `/api/v1/explore/form_data/{key}` |
|
||||
|
||||
</details>
|
||||
|
||||
#### Scheduling & Alerts
|
||||
|
||||
<details>
|
||||
<summary><strong>Report Schedules</strong> (11 endpoints) — Configure scheduled reports and alerts.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DELETE` | [Bulk delete report schedules](./api/bulk-delete-report-schedules) | `/api/v1/report/` |
|
||||
| `GET` | [Get a list of report schedules](./api/get-a-list-of-report-schedules) | `/api/v1/report/` |
|
||||
| `POST` | [Create a report schedule](./api/create-a-report-schedule) | `/api/v1/report/` |
|
||||
| `GET` | [Get metadata information about this API resource (report--info)](./api/get-metadata-information-about-this-api-resource-report-info) | `/api/v1/report/_info` |
|
||||
| `DELETE` | [Delete a report schedule](./api/delete-a-report-schedule) | `/api/v1/report/{pk}` |
|
||||
| `GET` | [Get a report schedule](./api/get-a-report-schedule) | `/api/v1/report/{pk}` |
|
||||
| `PUT` | [Update a report schedule](./api/update-a-report-schedule) | `/api/v1/report/{pk}` |
|
||||
| `GET` | [Get a list of report schedule logs](./api/get-a-list-of-report-schedule-logs) | `/api/v1/report/{pk}/log/` |
|
||||
| `GET` | [Get a report schedule log (report-pk-log-log-id)](./api/get-a-report-schedule-log-report-pk-log-log-id) | `/api/v1/report/{pk}/log/{log_id}` |
|
||||
| `GET` | [Get related fields data (report-related-column-name)](./api/get-related-fields-data-report-related-column-name) | `/api/v1/report/related/{column_name}` |
|
||||
| `GET` | [Get slack channels](./api/get-slack-channels) | `/api/v1/report/slack_channels/` |
|
||||
|
||||
</details>
|
||||
|
||||
#### Security & Access Control
|
||||
|
||||
<details>
|
||||
<summary><strong>Security Roles</strong> (10 endpoints) — Manage security roles and their permissions.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get security roles](./api/get-security-roles) | `/api/v1/security/roles/` |
|
||||
| `POST` | [Create security roles](./api/create-security-roles) | `/api/v1/security/roles/` |
|
||||
| `GET` | [Get security roles info](./api/get-security-roles-info) | `/api/v1/security/roles/_info` |
|
||||
| `DELETE` | [Delete security roles by pk](./api/delete-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
|
||||
| `GET` | [Get security roles by pk](./api/get-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
|
||||
| `PUT` | [Update security roles by pk](./api/update-security-roles-by-pk) | `/api/v1/security/roles/{pk}` |
|
||||
| `POST` | [Create security roles by role_id permissions](./api/create-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions` |
|
||||
| `GET` | [Get security roles by role_id permissions](./api/get-security-roles-by-role-id-permissions) | `/api/v1/security/roles/{role_id}/permissions/` |
|
||||
| `PUT` | [Update security roles by role_id users](./api/update-security-roles-by-role-id-users) | `/api/v1/security/roles/{role_id}/users` |
|
||||
| `GET` | [List roles](./api/list-roles) | `/api/v1/security/roles/search/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Security Users</strong> (6 endpoints) — Manage user accounts.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get security users](./api/get-security-users) | `/api/v1/security/users/` |
|
||||
| `POST` | [Create security users](./api/create-security-users) | `/api/v1/security/users/` |
|
||||
| `GET` | [Get security users info](./api/get-security-users-info) | `/api/v1/security/users/_info` |
|
||||
| `DELETE` | [Delete security users by pk](./api/delete-security-users-by-pk) | `/api/v1/security/users/{pk}` |
|
||||
| `GET` | [Get security users by pk](./api/get-security-users-by-pk) | `/api/v1/security/users/{pk}` |
|
||||
| `PUT` | [Update security users by pk](./api/update-security-users-by-pk) | `/api/v1/security/users/{pk}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Security Permissions</strong> (3 endpoints) — View available permissions.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get security permissions](./api/get-security-permissions) | `/api/v1/security/permissions/` |
|
||||
| `GET` | [Get security permissions info](./api/get-security-permissions-info) | `/api/v1/security/permissions/_info` |
|
||||
| `GET` | [Get security permissions by pk](./api/get-security-permissions-by-pk) | `/api/v1/security/permissions/{pk}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Security Resources (View Menus)</strong> (6 endpoints) — Manage security resources (view menus).</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get security resources](./api/get-security-resources) | `/api/v1/security/resources/` |
|
||||
| `POST` | [Create security resources](./api/create-security-resources) | `/api/v1/security/resources/` |
|
||||
| `GET` | [Get security resources info](./api/get-security-resources-info) | `/api/v1/security/resources/_info` |
|
||||
| `DELETE` | [Delete security resources by pk](./api/delete-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
|
||||
| `GET` | [Get security resources by pk](./api/get-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
|
||||
| `PUT` | [Update security resources by pk](./api/update-security-resources-by-pk) | `/api/v1/security/resources/{pk}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Security Permissions on Resources (View Menus)</strong> (6 endpoints) — Manage permission-resource mappings.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get security permissions resources](./api/get-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
|
||||
| `POST` | [Create security permissions resources](./api/create-security-permissions-resources) | `/api/v1/security/permissions-resources/` |
|
||||
| `GET` | [Get security permissions resources info](./api/get-security-permissions-resources-info) | `/api/v1/security/permissions-resources/_info` |
|
||||
| `DELETE` | [Delete security permissions resources by pk](./api/delete-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
|
||||
| `GET` | [Get security permissions resources by pk](./api/get-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
|
||||
| `PUT` | [Update security permissions resources by pk](./api/update-security-permissions-resources-by-pk) | `/api/v1/security/permissions-resources/{pk}` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Row Level Security</strong> (8 endpoints) — Manage row-level security rules for data access control.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `DELETE` | [Bulk delete RLS rules](./api/bulk-delete-rls-rules) | `/api/v1/rowlevelsecurity/` |
|
||||
| `GET` | [Get a list of RLS](./api/get-a-list-of-rls) | `/api/v1/rowlevelsecurity/` |
|
||||
| `POST` | [Create a new RLS rule](./api/create-a-new-rls-rule) | `/api/v1/rowlevelsecurity/` |
|
||||
| `GET` | [Get metadata information about this API resource (rowlevelsecurity--info)](./api/get-metadata-information-about-this-api-resource-rowlevelsecurity-info) | `/api/v1/rowlevelsecurity/_info` |
|
||||
| `DELETE` | [Delete an RLS](./api/delete-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
|
||||
| `GET` | [Get an RLS](./api/get-an-rls) | `/api/v1/rowlevelsecurity/{pk}` |
|
||||
| `PUT` | [Update an RLS rule](./api/update-an-rls-rule) | `/api/v1/rowlevelsecurity/{pk}` |
|
||||
| `GET` | [Get related fields data (rowlevelsecurity-related-column-name)](./api/get-related-fields-data-rowlevelsecurity-related-column-name) | `/api/v1/rowlevelsecurity/related/{column_name}` |
|
||||
|
||||
</details>
|
||||
|
||||
#### Import/Export & Administration
|
||||
|
||||
<details>
|
||||
<summary><strong>Import/export</strong> (2 endpoints) — Import and export Superset assets (dashboards, charts, databases).</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Export all assets](./api/export-all-assets) | `/api/v1/assets/export/` |
|
||||
| `POST` | [Import multiple assets](./api/import-multiple-assets) | `/api/v1/assets/import/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>CacheRestApi</strong> (1 endpoints) — Cache management and invalidation operations.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | [Invalidate cache records and remove the database records](./api/invalidate-cache-records-and-remove-the-database-records) | `/api/v1/cachekey/invalidate` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>LogRestApi</strong> (4 endpoints) — Access audit logs and activity history.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get a list of logs](./api/get-a-list-of-logs) | `/api/v1/log/` |
|
||||
| `POST` | [Create log](./api/create-log) | `/api/v1/log/` |
|
||||
| `GET` | [Get a log detail information](./api/get-a-log-detail-information) | `/api/v1/log/{pk}` |
|
||||
| `GET` | [Get recent activity data for a user](./api/get-recent-activity-data-for-a-user) | `/api/v1/log/recent_activity/` |
|
||||
|
||||
</details>
|
||||
|
||||
#### User & System
|
||||
|
||||
<details>
|
||||
<summary><strong>Current User</strong> (2 endpoints) — Get information about the currently authenticated user.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get the user object](./api/get-the-user-object) | `/api/v1/me/` |
|
||||
| `GET` | [Get the user roles](./api/get-the-user-roles) | `/api/v1/me/roles/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>User</strong> (1 endpoints) — User profile and preferences.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get the user avatar](./api/get-the-user-avatar) | `/api/v1/user/{user_id}/avatar.png` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Menu</strong> (1 endpoints) — Get the Superset menu structure.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get menu](./api/get-menu) | `/api/v1/menu/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Available Domains</strong> (1 endpoints) — Get available domains for the Superset instance.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get all available domains](./api/get-all-available-domains) | `/api/v1/available_domains/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>AsyncEventsRestApi</strong> (1 endpoints) — Real-time event streaming via Server-Sent Events (SSE).</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Read off of the Redis events stream](./api/read-off-of-the-redis-events-stream) | `/api/v1/async_event/` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>OpenApi</strong> (1 endpoints) — Access the OpenAPI specification.</summary>
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | [Get api by version openapi](./api/get-api-by-version-openapi) | `/api/{version}/_openapi` |
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
### Additional Resources
|
||||
|
||||
- [Superset REST API Blog Post](https://preset.io/blog/2020-10-01-superset-api/)
|
||||
- [Accessing APIs with Superset](https://preset.io/blog/accessing-apis-with-superset/)
|
||||
71
docs/developer_docs/components/TODO.md
Normal file
71
docs/developer_docs/components/TODO.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Components TODO
|
||||
sidebar_class_name: hidden
|
||||
---
|
||||
|
||||
# Components TODO
|
||||
|
||||
These components were found but not yet supported for documentation generation.
|
||||
Future phases will add support for these sources.
|
||||
|
||||
## Summary
|
||||
|
||||
- **Total skipped:** 19 story files
|
||||
- **Reason:** Import path resolution not yet implemented
|
||||
|
||||
## Skipped by Source
|
||||
|
||||
### App Components
|
||||
|
||||
9 components
|
||||
|
||||
- [ ] `superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.stories.tsx`
|
||||
- [ ] `superset-frontend/src/components/Chart/DrillDetail/DrillDetailTableControls.stories.tsx`
|
||||
- [ ] `superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx`
|
||||
- [ ] `superset-frontend/src/components/ErrorMessage/ErrorAlert.stories.tsx`
|
||||
- [ ] `superset-frontend/src/components/FacePile/FacePile.stories.tsx`
|
||||
- [ ] `superset-frontend/src/components/FilterableTable/FilterableTable.stories.tsx`
|
||||
- [ ] `superset-frontend/src/components/RowCountLabel/RowCountLabel.stories.tsx`
|
||||
- [ ] `superset-frontend/src/components/Tag/Tag.stories.tsx`
|
||||
- [ ] `superset-frontend/src/components/TagsList/TagsList.stories.tsx`
|
||||
|
||||
### Dashboard Components
|
||||
|
||||
2 components
|
||||
|
||||
- [ ] `superset-frontend/src/dashboard/components/AnchorLink/AnchorLink.stories.tsx`
|
||||
- [ ] `superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterDivider.stories.tsx`
|
||||
|
||||
### Explore Components
|
||||
|
||||
4 components
|
||||
|
||||
- [ ] `superset-frontend/src/explore/components/ControlHeader.stories.tsx`
|
||||
- [ ] `superset-frontend/src/explore/components/RunQueryButton/RunQueryButton.stories.tsx`
|
||||
- [ ] `superset-frontend/src/explore/components/controls/BoundsControl.stories.tsx`
|
||||
- [ ] `superset-frontend/src/explore/components/controls/SliderControl.stories.tsx`
|
||||
|
||||
### Feature Components
|
||||
|
||||
2 components
|
||||
|
||||
- [ ] `superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.stories.tsx`
|
||||
- [ ] `superset-frontend/src/features/home/LanguagePicker.stories.tsx`
|
||||
|
||||
### Filter Components
|
||||
|
||||
2 components
|
||||
|
||||
- [ ] `superset-frontend/src/filters/components/Range/RangeFilterPlugin.stories.tsx`
|
||||
- [ ] `superset-frontend/src/filters/components/Select/SelectFilterPlugin.stories.tsx`
|
||||
|
||||
## How to Add Support
|
||||
|
||||
1. Determine the correct import path for the source
|
||||
2. Update `generate-superset-components.mjs` to handle the source
|
||||
3. Add source to `SUPPORTED_SOURCES` array
|
||||
4. Re-run the generator
|
||||
|
||||
---
|
||||
|
||||
*Auto-generated by generate-superset-components.mjs*
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
title: DropdownContainer
|
||||
sidebar_label: DropdownContainer
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# DropdownContainer
|
||||
|
||||
DropdownContainer arranges items horizontally and moves overflowing items into a dropdown popover. Resize the container to see the overflow behavior.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="DropdownContainer"
|
||||
props={{
|
||||
style: {
|
||||
maxWidth: 360
|
||||
},
|
||||
items: [
|
||||
{
|
||||
id: "item-0",
|
||||
element: {
|
||||
component: "Tag",
|
||||
props: {
|
||||
children: "Region",
|
||||
color: "blue"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "item-1",
|
||||
element: {
|
||||
component: "Tag",
|
||||
props: {
|
||||
children: "Category",
|
||||
color: "blue"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "item-2",
|
||||
element: {
|
||||
component: "Tag",
|
||||
props: {
|
||||
children: "Date Range",
|
||||
color: "blue"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "item-3",
|
||||
element: {
|
||||
component: "Tag",
|
||||
props: {
|
||||
children: "Status",
|
||||
color: "blue"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "item-4",
|
||||
element: {
|
||||
component: "Tag",
|
||||
props: {
|
||||
children: "Owner",
|
||||
color: "blue"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "item-5",
|
||||
element: {
|
||||
component: "Tag",
|
||||
props: {
|
||||
children: "Priority",
|
||||
color: "blue"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const items = Array.from({ length: 6 }, (_, i) => ({
|
||||
id: 'item-' + i,
|
||||
element: React.createElement('div', {
|
||||
style: {
|
||||
minWidth: 120,
|
||||
padding: '4px 12px',
|
||||
background: '#e6f4ff',
|
||||
border: '1px solid #91caff',
|
||||
borderRadius: 4,
|
||||
},
|
||||
}, 'Filter ' + (i + 1)),
|
||||
}));
|
||||
return (
|
||||
<div style={{ width: 400, resize: 'horizontal', overflow: 'auto', border: '1px solid #e8e8e8', padding: 16 }}>
|
||||
<DropdownContainer items={items} />
|
||||
<div style={{ marginTop: 8, color: '#999', fontSize: 12 }}>
|
||||
Drag the right edge to resize and see items overflow into a dropdown
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## With Select Filters
|
||||
|
||||
```tsx live
|
||||
function SelectFilters() {
|
||||
const items = ['Region', 'Category', 'Date Range', 'Status', 'Owner'].map(
|
||||
(label, i) => ({
|
||||
id: 'filter-' + i,
|
||||
element: React.createElement('div', {
|
||||
style: { minWidth: 150, padding: '4px 12px', background: '#f5f5f5', border: '1px solid #d9d9d9', borderRadius: 4 },
|
||||
}, label + ': All'),
|
||||
})
|
||||
);
|
||||
return (
|
||||
<div style={{ width: 500, resize: 'horizontal', overflow: 'auto', border: '1px solid #e8e8e8', padding: 16 }}>
|
||||
<DropdownContainer items={items} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { DropdownContainer } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.stories.tsx).
|
||||
:::
|
||||
197
docs/developer_docs/components/design-system/flex.mdx
Normal file
197
docs/developer_docs/components/design-system/flex.mdx
Normal file
@@ -0,0 +1,197 @@
|
||||
---
|
||||
title: Flex
|
||||
sidebar_label: Flex
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Flex
|
||||
|
||||
The Flex component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Flex"
|
||||
props={{
|
||||
vertical: false,
|
||||
wrap: "nowrap",
|
||||
justify: "normal",
|
||||
align: "normal",
|
||||
flex: "normal",
|
||||
gap: "small"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "vertical",
|
||||
label: "Vertical",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "wrap",
|
||||
label: "Wrap",
|
||||
type: "select",
|
||||
options: [
|
||||
"nowrap",
|
||||
"wrap",
|
||||
"wrap-reverse"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "justify",
|
||||
label: "Justify",
|
||||
type: "select",
|
||||
options: [
|
||||
"start",
|
||||
"center",
|
||||
"space-between",
|
||||
"space-around",
|
||||
"space-evenly"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "align",
|
||||
label: "Align",
|
||||
type: "select",
|
||||
options: [
|
||||
"start",
|
||||
"center",
|
||||
"end",
|
||||
"stretch"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "flex",
|
||||
label: "Flex",
|
||||
type: "string"
|
||||
},
|
||||
{
|
||||
name: "gap",
|
||||
label: "Gap",
|
||||
type: "select",
|
||||
options: [
|
||||
"small",
|
||||
"medium",
|
||||
"large"
|
||||
]
|
||||
}
|
||||
]}
|
||||
sampleChildren={["Item 1","Item 2","Item 3","Item 4","Item 5"]}
|
||||
sampleChildrenStyle={{padding:"8px 16px",background:"#e6f4ff",border:"1px solid #91caff",borderRadius:"4px"}}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Flex gap="small" wrap="wrap">
|
||||
{['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'].map(item => (
|
||||
<div
|
||||
key={item}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
background: '#e6f4ff',
|
||||
border: '1px solid #91caff',
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Vertical Layout
|
||||
|
||||
```tsx live
|
||||
function VerticalFlex() {
|
||||
return (
|
||||
<Flex vertical gap="small">
|
||||
<Button buttonStyle="primary">Primary</Button>
|
||||
<Button buttonStyle="dashed">Dashed</Button>
|
||||
<Button buttonStyle="link">Link</Button>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Justify and Align
|
||||
|
||||
```tsx live
|
||||
function JustifyAlign() {
|
||||
const boxStyle = {
|
||||
width: '100%',
|
||||
height: 120,
|
||||
borderRadius: 6,
|
||||
border: '1px solid #40a9ff',
|
||||
};
|
||||
const itemStyle = {
|
||||
width: 60,
|
||||
height: 40,
|
||||
backgroundColor: '#1677ff',
|
||||
borderRadius: 4,
|
||||
};
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{['flex-start', 'center', 'flex-end', 'space-between', 'space-around'].map(justify => (
|
||||
<div key={justify}>
|
||||
<span style={{ marginBottom: 4, display: 'block', color: '#666' }}>{justify}</span>
|
||||
<Flex style={boxStyle} justify={justify} align="center">
|
||||
<div style={itemStyle} />
|
||||
<div style={itemStyle} />
|
||||
<div style={itemStyle} />
|
||||
</Flex>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `vertical` | `boolean` | `false` | - |
|
||||
| `wrap` | `string` | `"nowrap"` | - |
|
||||
| `justify` | `string` | `"normal"` | - |
|
||||
| `align` | `string` | `"normal"` | - |
|
||||
| `flex` | `string` | `"normal"` | - |
|
||||
| `gap` | `string` | `"small"` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Flex } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Flex/Flex.stories.tsx).
|
||||
:::
|
||||
192
docs/developer_docs/components/design-system/grid.mdx
Normal file
192
docs/developer_docs/components/design-system/grid.mdx
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: Grid
|
||||
sidebar_label: Grid
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Grid
|
||||
|
||||
The Grid system of Ant Design is based on a 24-grid layout. The `Row` and `Col` components are used to create flexible and responsive grid layouts.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Grid"
|
||||
renderComponent="Row"
|
||||
props={{
|
||||
align: "top",
|
||||
justify: "start",
|
||||
wrap: true,
|
||||
gutter: 16
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "align",
|
||||
label: "Align",
|
||||
type: "select",
|
||||
options: [
|
||||
"top",
|
||||
"middle",
|
||||
"bottom",
|
||||
"stretch"
|
||||
],
|
||||
description: "Vertical alignment of columns within the row."
|
||||
},
|
||||
{
|
||||
name: "justify",
|
||||
label: "Justify",
|
||||
type: "select",
|
||||
options: [
|
||||
"start",
|
||||
"end",
|
||||
"center",
|
||||
"space-around",
|
||||
"space-between",
|
||||
"space-evenly"
|
||||
],
|
||||
description: "Horizontal distribution of columns within the row."
|
||||
},
|
||||
{
|
||||
name: "wrap",
|
||||
label: "Wrap",
|
||||
type: "boolean",
|
||||
description: "Whether columns are allowed to wrap to the next line."
|
||||
},
|
||||
{
|
||||
name: "gutter",
|
||||
label: "Gutter",
|
||||
type: "number",
|
||||
description: "Spacing between columns in pixels."
|
||||
}
|
||||
]}
|
||||
sampleChildren={[{"component":"Col","props":{"span":4,"children":"col-4","style":{"background":"#e6f4ff","padding":"8px","border":"1px solid #91caff","textAlign":"center"}}},{"component":"Col","props":{"span":4,"children":"col-4 (tall)","style":{"background":"#e6f4ff","padding":"24px 8px","border":"1px solid #91caff","textAlign":"center"}}},{"component":"Col","props":{"span":4,"children":"col-4","style":{"background":"#e6f4ff","padding":"8px","border":"1px solid #91caff","textAlign":"center"}}}]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<div style={{ background: '#e6f4ff', padding: '8px', border: '1px solid #91caff' }}>col-12</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div style={{ background: '#e6f4ff', padding: '8px', border: '1px solid #91caff' }}>col-12</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div style={{ background: '#e6f4ff', padding: '8px', border: '1px solid #91caff' }}>col-8</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div style={{ background: '#e6f4ff', padding: '8px', border: '1px solid #91caff' }}>col-8</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div style={{ background: '#e6f4ff', padding: '8px', border: '1px solid #91caff' }}>col-8</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Responsive Grid
|
||||
|
||||
```tsx live
|
||||
function ResponsiveGrid() {
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col xs={24} sm={12} md={8} lg={6}>
|
||||
<div style={{ background: '#e6f4ff', padding: '16px', border: '1px solid #91caff', textAlign: 'center' }}>
|
||||
Responsive
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8} lg={6}>
|
||||
<div style={{ background: '#e6f4ff', padding: '16px', border: '1px solid #91caff', textAlign: 'center' }}>
|
||||
Responsive
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8} lg={6}>
|
||||
<div style={{ background: '#e6f4ff', padding: '16px', border: '1px solid #91caff', textAlign: 'center' }}>
|
||||
Responsive
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8} lg={6}>
|
||||
<div style={{ background: '#e6f4ff', padding: '16px', border: '1px solid #91caff', textAlign: 'center' }}>
|
||||
Responsive
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Alignment
|
||||
|
||||
```tsx live
|
||||
function AlignmentDemo() {
|
||||
const boxStyle = { background: '#e6f4ff', padding: '16px 0', border: '1px solid #91caff', textAlign: 'center' };
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<Row justify="start" gutter={8}>
|
||||
<Col span={4}><div style={boxStyle}>start</div></Col>
|
||||
<Col span={4}><div style={boxStyle}>start</div></Col>
|
||||
</Row>
|
||||
<Row justify="center" gutter={8}>
|
||||
<Col span={4}><div style={boxStyle}>center</div></Col>
|
||||
<Col span={4}><div style={boxStyle}>center</div></Col>
|
||||
</Row>
|
||||
<Row justify="end" gutter={8}>
|
||||
<Col span={4}><div style={boxStyle}>end</div></Col>
|
||||
<Col span={4}><div style={boxStyle}>end</div></Col>
|
||||
</Row>
|
||||
<Row justify="space-between" gutter={8}>
|
||||
<Col span={4}><div style={boxStyle}>between</div></Col>
|
||||
<Col span={4}><div style={boxStyle}>between</div></Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `align` | `string` | `"top"` | Vertical alignment of columns within the row. |
|
||||
| `justify` | `string` | `"start"` | Horizontal distribution of columns within the row. |
|
||||
| `wrap` | `boolean` | `true` | Whether columns are allowed to wrap to the next line. |
|
||||
| `gutter` | `number` | `16` | Spacing between columns in pixels. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import Grid from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Grid/Grid.stories.tsx).
|
||||
:::
|
||||
38
docs/developer_docs/components/design-system/index.mdx
Normal file
38
docs/developer_docs/components/design-system/index.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Layout Components
|
||||
sidebar_label: Layout Components
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Layout Components
|
||||
|
||||
7 components available in this category.
|
||||
|
||||
## Components
|
||||
|
||||
- [DropdownContainer](./dropdowncontainer)
|
||||
- [Flex](./flex)
|
||||
- [Grid](./grid)
|
||||
- [Layout](./layout)
|
||||
- [MetadataBar](./metadatabar)
|
||||
- [Space](./space)
|
||||
- [Table](./table)
|
||||
139
docs/developer_docs/components/design-system/layout.mdx
Normal file
139
docs/developer_docs/components/design-system/layout.mdx
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: Layout
|
||||
sidebar_label: Layout
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Layout
|
||||
|
||||
Ant Design Layout component with configurable Sider, Header, Footer, and Content.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Layout"
|
||||
props={{
|
||||
hasSider: false,
|
||||
style: {
|
||||
minHeight: 200
|
||||
}
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "hasSider",
|
||||
label: "Has Sider",
|
||||
type: "boolean",
|
||||
description: "Whether the layout contains a Sider sub-component."
|
||||
}
|
||||
]}
|
||||
sampleChildren={[{"component":"Layout.Header","props":{"children":"Header","style":{"background":"#001529","color":"#fff","padding":"0 24px","lineHeight":"64px"}}},{"component":"Layout.Content","props":{"children":"Content Area","style":{"padding":"24px","background":"#fff","flex":1}}},{"component":"Layout.Footer","props":{"children":"Footer","style":{"textAlign":"center","background":"#f5f5f5","padding":"12px"}}}]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Layout style={{ minHeight: '300px' }}>
|
||||
<Layout.Sider theme="dark" width={200}>
|
||||
<div style={{ color: '#fff', padding: '16px' }}>Sidebar</div>
|
||||
</Layout.Sider>
|
||||
<Layout>
|
||||
<Layout.Header style={{ background: '#fff', padding: '0 16px' }}>
|
||||
Header
|
||||
</Layout.Header>
|
||||
<Layout.Content style={{ margin: '16px', padding: '24px', background: '#fff' }}>
|
||||
Content
|
||||
</Layout.Content>
|
||||
<Layout.Footer style={{ textAlign: 'center' }}>
|
||||
Footer
|
||||
</Layout.Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Content Only
|
||||
|
||||
```tsx live
|
||||
function ContentOnly() {
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Header style={{ background: '#001529', color: '#fff', padding: '0 24px', lineHeight: '64px' }}>
|
||||
Application Header
|
||||
</Layout.Header>
|
||||
<Layout.Content style={{ padding: '24px', minHeight: '200px', background: '#fff' }}>
|
||||
Main content area without a sidebar
|
||||
</Layout.Content>
|
||||
<Layout.Footer style={{ textAlign: 'center', background: '#f5f5f5' }}>
|
||||
Footer Content
|
||||
</Layout.Footer>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Right Sidebar
|
||||
|
||||
```tsx live
|
||||
function RightSidebar() {
|
||||
return (
|
||||
<Layout style={{ minHeight: '300px' }}>
|
||||
<Layout>
|
||||
<Layout.Header style={{ background: '#fff', padding: '0 24px' }}>
|
||||
Header
|
||||
</Layout.Header>
|
||||
<Layout.Content style={{ padding: '24px', background: '#fff' }}>
|
||||
Content with right sidebar
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
<Layout.Sider theme="light" width={200} style={{ background: '#fafafa' }}>
|
||||
<div style={{ padding: '16px' }}>Right Sidebar</div>
|
||||
</Layout.Sider>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `hasSider` | `boolean` | `false` | Whether the layout contains a Sider sub-component. |
|
||||
| `style` | `any` | `{"minHeight":200}` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Layout } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Layout/Layout.stories.tsx).
|
||||
:::
|
||||
174
docs/developer_docs/components/design-system/metadatabar.mdx
Normal file
174
docs/developer_docs/components/design-system/metadatabar.mdx
Normal file
@@ -0,0 +1,174 @@
|
||||
---
|
||||
title: MetadataBar
|
||||
sidebar_label: MetadataBar
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# MetadataBar
|
||||
|
||||
MetadataBar displays a row of metadata items (SQL info, owners, last modified, tags, dashboards, etc.) that collapse responsively based on available width.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="MetadataBar"
|
||||
props={{
|
||||
title: "Added to 3 dashboards",
|
||||
createdBy: "Jane Smith",
|
||||
modifiedBy: "Jane Smith",
|
||||
description: "To preview the list of dashboards go to More settings.",
|
||||
items: [
|
||||
{
|
||||
type: "sql",
|
||||
title: "Click to view query"
|
||||
},
|
||||
{
|
||||
type: "owner",
|
||||
createdBy: "Jane Smith",
|
||||
owners: [
|
||||
"John Doe",
|
||||
"Mary Wilson"
|
||||
],
|
||||
createdOn: "a week ago"
|
||||
},
|
||||
{
|
||||
type: "lastModified",
|
||||
value: "a week ago",
|
||||
modifiedBy: "Jane Smith"
|
||||
},
|
||||
{
|
||||
type: "tags",
|
||||
values: [
|
||||
"management",
|
||||
"research",
|
||||
"poc"
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "dashboards",
|
||||
title: "Added to 3 dashboards",
|
||||
description: "To preview the list of dashboards go to More settings."
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "createdBy",
|
||||
label: "Created By",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "modifiedBy",
|
||||
label: "Modified By",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "text"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const items = [
|
||||
{ type: 'sql', title: 'Click to view query' },
|
||||
{
|
||||
type: 'owner',
|
||||
createdBy: 'Jane Smith',
|
||||
owners: ['John Doe', 'Mary Wilson'],
|
||||
createdOn: 'a week ago',
|
||||
},
|
||||
{
|
||||
type: 'lastModified',
|
||||
value: 'a week ago',
|
||||
modifiedBy: 'Jane Smith',
|
||||
},
|
||||
{ type: 'tags', values: ['management', 'research', 'poc'] },
|
||||
];
|
||||
return <MetadataBar items={items} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Minimal Metadata
|
||||
|
||||
```tsx live
|
||||
function MinimalMetadata() {
|
||||
const items = [
|
||||
{ type: 'owner', createdBy: 'Admin', owners: ['Admin'], createdOn: 'yesterday' },
|
||||
{ type: 'lastModified', value: '2 hours ago', modifiedBy: 'Admin' },
|
||||
];
|
||||
return <MetadataBar items={items} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Full Metadata
|
||||
|
||||
```tsx live
|
||||
function FullMetadata() {
|
||||
const items = [
|
||||
{ type: 'sql', title: 'SELECT * FROM ...' },
|
||||
{ type: 'owner', createdBy: 'Jane Smith', owners: ['Jane Smith', 'John Doe', 'Bob Wilson'], createdOn: '2 weeks ago' },
|
||||
{ type: 'lastModified', value: '3 days ago', modifiedBy: 'John Doe' },
|
||||
{ type: 'tags', values: ['production', 'finance', 'quarterly'] },
|
||||
{ type: 'dashboards', title: 'Used in 12 dashboards' },
|
||||
{ type: 'description', value: 'This chart shows quarterly revenue breakdown by region and product line.' },
|
||||
{ type: 'rows', title: '1.2M rows' },
|
||||
{ type: 'table', title: 'public.revenue_data' },
|
||||
];
|
||||
return <MetadataBar items={items} />;
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `title` | `string` | `"Added to 3 dashboards"` | - |
|
||||
| `createdBy` | `string` | `"Jane Smith"` | - |
|
||||
| `modifiedBy` | `string` | `"Jane Smith"` | - |
|
||||
| `description` | `string` | `"To preview the list of dashboards go to More settings."` | - |
|
||||
| `items` | `any` | `[{"type":"sql","title":"Click to view query"},{"type":"owner","createdBy":"Jane Smith","owners":["John Doe","Mary Wilson"],"createdOn":"a week ago"},{"type":"lastModified","value":"a week ago","modifiedBy":"Jane Smith"},{"type":"tags","values":["management","research","poc"]},{"type":"dashboards","title":"Added to 3 dashboards","description":"To preview the list of dashboards go to More settings."}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import MetadataBar from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/MetadataBar/MetadataBar.stories.tsx).
|
||||
:::
|
||||
168
docs/developer_docs/components/design-system/space.mdx
Normal file
168
docs/developer_docs/components/design-system/space.mdx
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title: Space
|
||||
sidebar_label: Space
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Space
|
||||
|
||||
The Space component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Space"
|
||||
props={{
|
||||
direction: "horizontal",
|
||||
size: "small",
|
||||
wrap: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "direction",
|
||||
label: "Direction",
|
||||
type: "select",
|
||||
options: [
|
||||
"vertical",
|
||||
"horizontal"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"small",
|
||||
"middle",
|
||||
"large"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "wrap",
|
||||
label: "Wrap",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "align",
|
||||
label: "Align",
|
||||
type: "select",
|
||||
options: [
|
||||
"start",
|
||||
"end",
|
||||
"center",
|
||||
"baseline"
|
||||
]
|
||||
}
|
||||
]}
|
||||
sampleChildren={["Item 1","Item 2","Item 3","Item 4","Item 5"]}
|
||||
sampleChildrenStyle={{padding:"8px 16px",background:"#e6f4ff",border:"1px solid #91caff",borderRadius:"4px"}}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Space size="small">
|
||||
{['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'].map(item => (
|
||||
<div
|
||||
key={item}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
background: '#e6f4ff',
|
||||
border: '1px solid #91caff',
|
||||
borderRadius: 4,
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
))}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Vertical Space
|
||||
|
||||
```tsx live
|
||||
function VerticalSpace() {
|
||||
return (
|
||||
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
|
||||
<Button buttonStyle="primary">Primary</Button>
|
||||
<Button buttonStyle="secondary">Secondary</Button>
|
||||
<Button buttonStyle="dashed">Dashed</Button>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Space Sizes
|
||||
|
||||
```tsx live
|
||||
function SpaceSizes() {
|
||||
const items = ['Item 1', 'Item 2', 'Item 3'];
|
||||
const itemStyle = {
|
||||
padding: '8px 16px',
|
||||
background: '#e6f4ff',
|
||||
border: '1px solid #91caff',
|
||||
borderRadius: 4,
|
||||
};
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
|
||||
{['small', 'middle', 'large'].map(size => (
|
||||
<div key={size}>
|
||||
<h4>{size}</h4>
|
||||
<Space size={size}>
|
||||
{items.map(item => (
|
||||
<div key={item} style={itemStyle}>{item}</div>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `direction` | `string` | `"horizontal"` | - |
|
||||
| `size` | `string` | `"small"` | - |
|
||||
| `wrap` | `boolean` | `false` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Space } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Space/Space.stories.tsx).
|
||||
:::
|
||||
311
docs/developer_docs/components/design-system/table.mdx
Normal file
311
docs/developer_docs/components/design-system/table.mdx
Normal file
@@ -0,0 +1,311 @@
|
||||
---
|
||||
title: Table
|
||||
sidebar_label: Table
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Table
|
||||
|
||||
A data table component with sorting, pagination, row selection, resizable columns, reorderable columns, and virtualization for large datasets.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Table"
|
||||
props={{
|
||||
size: "small",
|
||||
bordered: false,
|
||||
loading: false,
|
||||
sticky: true,
|
||||
resizable: false,
|
||||
reorderable: false,
|
||||
usePagination: false,
|
||||
key: 5,
|
||||
name: "1GB USB Flash Drive",
|
||||
category: "Portable Storage",
|
||||
price: 9.99,
|
||||
height: 350,
|
||||
defaultPageSize: 5,
|
||||
pageSizeOptions: [
|
||||
"5",
|
||||
"10"
|
||||
],
|
||||
data: [
|
||||
{
|
||||
key: 1,
|
||||
name: "Floppy Disk 10 pack",
|
||||
category: "Disk Storage",
|
||||
price: 9.99
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: "DVD 100 pack",
|
||||
category: "Optical Storage",
|
||||
price: 27.99
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: "128 GB SSD",
|
||||
category: "Harddrive",
|
||||
price: 49.99
|
||||
},
|
||||
{
|
||||
key: 4,
|
||||
name: "4GB 144mhz",
|
||||
category: "Memory",
|
||||
price: 19.99
|
||||
},
|
||||
{
|
||||
key: 5,
|
||||
name: "1GB USB Flash Drive",
|
||||
category: "Portable Storage",
|
||||
price: 9.99
|
||||
},
|
||||
{
|
||||
key: 6,
|
||||
name: "256 GB SSD",
|
||||
category: "Harddrive",
|
||||
price: 89.99
|
||||
},
|
||||
{
|
||||
key: 7,
|
||||
name: "1 TB SSD",
|
||||
category: "Harddrive",
|
||||
price: 349.99
|
||||
},
|
||||
{
|
||||
key: 8,
|
||||
name: "16 GB DDR4",
|
||||
category: "Memory",
|
||||
price: 59.99
|
||||
},
|
||||
{
|
||||
key: 9,
|
||||
name: "32 GB DDR5",
|
||||
category: "Memory",
|
||||
price: 129.99
|
||||
},
|
||||
{
|
||||
key: 10,
|
||||
name: "Blu-ray 50 pack",
|
||||
category: "Optical Storage",
|
||||
price: 34.99
|
||||
},
|
||||
{
|
||||
key: 11,
|
||||
name: "64 GB USB Drive",
|
||||
category: "Portable Storage",
|
||||
price: 14.99
|
||||
},
|
||||
{
|
||||
key: 12,
|
||||
name: "2 TB HDD",
|
||||
category: "Harddrive",
|
||||
price: 59.99
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
title: "Name",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: "Category",
|
||||
dataIndex: "category",
|
||||
key: "category",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "Price",
|
||||
dataIndex: "price",
|
||||
key: "price",
|
||||
width: 100
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"small",
|
||||
"middle",
|
||||
"large"
|
||||
],
|
||||
description: "Table size."
|
||||
},
|
||||
{
|
||||
name: "bordered",
|
||||
label: "Bordered",
|
||||
type: "boolean",
|
||||
description: "Whether to show all table borders."
|
||||
},
|
||||
{
|
||||
name: "loading",
|
||||
label: "Loading",
|
||||
type: "boolean",
|
||||
description: "Whether the table is in a loading state."
|
||||
},
|
||||
{
|
||||
name: "sticky",
|
||||
label: "Sticky",
|
||||
type: "boolean",
|
||||
description: "Whether the table header is sticky."
|
||||
},
|
||||
{
|
||||
name: "resizable",
|
||||
label: "Resizable",
|
||||
type: "boolean",
|
||||
description: "Whether columns can be resized by dragging column edges."
|
||||
},
|
||||
{
|
||||
name: "reorderable",
|
||||
label: "Reorderable",
|
||||
type: "boolean",
|
||||
description: "EXPERIMENTAL: Whether columns can be reordered by dragging. May not work in all contexts."
|
||||
},
|
||||
{
|
||||
name: "usePagination",
|
||||
label: "Use Pagination",
|
||||
type: "boolean",
|
||||
description: "Whether to enable pagination. When enabled, the table displays 5 rows per page."
|
||||
},
|
||||
{
|
||||
name: "key",
|
||||
label: "Key",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
label: "Name",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "category",
|
||||
label: "Category",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "price",
|
||||
label: "Price",
|
||||
type: "number"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const data = [
|
||||
{ key: 1, name: 'PostgreSQL', type: 'Database', status: 'Active' },
|
||||
{ key: 2, name: 'MySQL', type: 'Database', status: 'Active' },
|
||||
{ key: 3, name: 'SQLite', type: 'Database', status: 'Inactive' },
|
||||
{ key: 4, name: 'Presto', type: 'Query Engine', status: 'Active' },
|
||||
];
|
||||
const columns = [
|
||||
{ title: 'Name', dataIndex: 'name', key: 'name', width: 150 },
|
||||
{ title: 'Type', dataIndex: 'type', key: 'type' },
|
||||
{ title: 'Status', dataIndex: 'status', key: 'status', width: 100 },
|
||||
];
|
||||
return <Table data={data} columns={columns} size="small" />;
|
||||
}
|
||||
```
|
||||
|
||||
## With Pagination
|
||||
|
||||
```tsx live
|
||||
function PaginatedTable() {
|
||||
const data = Array.from({ length: 20 }, (_, i) => ({
|
||||
key: i,
|
||||
name: 'Record ' + (i + 1),
|
||||
value: Math.round(Math.random() * 1000),
|
||||
category: ['A', 'B', 'C'][i % 3],
|
||||
}));
|
||||
const columns = [
|
||||
{ title: 'Name', dataIndex: 'name', key: 'name' },
|
||||
{ title: 'Value', dataIndex: 'value', key: 'value', width: 100 },
|
||||
{ title: 'Category', dataIndex: 'category', key: 'category', width: 100 },
|
||||
];
|
||||
return (
|
||||
<Table
|
||||
data={data}
|
||||
columns={columns}
|
||||
size="small"
|
||||
pageSizeOptions={['5', '10']}
|
||||
defaultPageSize={5}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Loading State
|
||||
|
||||
```tsx live
|
||||
function LoadingTable() {
|
||||
const columns = [
|
||||
{ title: 'Name', dataIndex: 'name', key: 'name' },
|
||||
{ title: 'Status', dataIndex: 'status', key: 'status' },
|
||||
];
|
||||
return <Table data={[]} columns={columns} size="small" loading />;
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `size` | `string` | `"small"` | Table size. |
|
||||
| `bordered` | `boolean` | `false` | Whether to show all table borders. |
|
||||
| `loading` | `boolean` | `false` | Whether the table is in a loading state. |
|
||||
| `sticky` | `boolean` | `true` | Whether the table header is sticky. |
|
||||
| `resizable` | `boolean` | `false` | Whether columns can be resized by dragging column edges. |
|
||||
| `reorderable` | `boolean` | `false` | EXPERIMENTAL: Whether columns can be reordered by dragging. May not work in all contexts. |
|
||||
| `usePagination` | `boolean` | `false` | Whether to enable pagination. When enabled, the table displays 5 rows per page. |
|
||||
| `key` | `number` | `5` | - |
|
||||
| `name` | `string` | `"1GB USB Flash Drive"` | - |
|
||||
| `category` | `string` | `"Portable Storage"` | - |
|
||||
| `price` | `number` | `9.99` | - |
|
||||
| `height` | `number` | `350` | - |
|
||||
| `defaultPageSize` | `number` | `5` | - |
|
||||
| `pageSizeOptions` | `any` | `["5","10"]` | - |
|
||||
| `data` | `any` | `[{"key":1,"name":"Floppy Disk 10 pack","category":"Disk Storage","price":9.99},{"key":2,"name":"DVD 100 pack","category":"Optical Storage","price":27.99},{"key":3,"name":"128 GB SSD","category":"Harddrive","price":49.99},{"key":4,"name":"4GB 144mhz","category":"Memory","price":19.99},{"key":5,"name":"1GB USB Flash Drive","category":"Portable Storage","price":9.99},{"key":6,"name":"256 GB SSD","category":"Harddrive","price":89.99},{"key":7,"name":"1 TB SSD","category":"Harddrive","price":349.99},{"key":8,"name":"16 GB DDR4","category":"Memory","price":59.99},{"key":9,"name":"32 GB DDR5","category":"Memory","price":129.99},{"key":10,"name":"Blu-ray 50 pack","category":"Optical Storage","price":34.99},{"key":11,"name":"64 GB USB Drive","category":"Portable Storage","price":14.99},{"key":12,"name":"2 TB HDD","category":"Harddrive","price":59.99}]` | - |
|
||||
| `columns` | `any` | `[{"title":"Name","dataIndex":"name","key":"name","width":200},{"title":"Category","dataIndex":"category","key":"category","width":150},{"title":"Price","dataIndex":"price","key":"price","width":100}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Table } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Table/Table.stories.tsx).
|
||||
:::
|
||||
74
docs/developer_docs/components/index.mdx
Normal file
74
docs/developer_docs/components/index.mdx
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: UI Components Overview
|
||||
sidebar_label: Overview
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
# Superset Design System
|
||||
|
||||
A design system is a complete set of standards intended to manage design at scale using reusable components and patterns.
|
||||
|
||||
The Superset Design System uses [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/) principles with adapted terminology:
|
||||
|
||||
| Atomic Design | Atoms | Molecules | Organisms | Templates | Pages / Screens |
|
||||
|---|:---:|:---:|:---:|:---:|:---:|
|
||||
| **Superset Design** | Foundations | Components | Patterns | Templates | Features |
|
||||
|
||||
<img src="/img/atomic-design.png" alt="Atoms = Foundations, Molecules = Components, Organisms = Patterns, Templates = Templates, Pages / Screens = Features" style={{maxWidth: '100%'}} />
|
||||
|
||||
---
|
||||
|
||||
## Component Library
|
||||
|
||||
Interactive documentation for Superset's UI component library. **53 components** documented across 2 categories.
|
||||
|
||||
### [Core Components](./ui/)
|
||||
46 components — Buttons, inputs, modals, selects, and other fundamental UI elements.
|
||||
|
||||
### [Layout Components](./design-system/)
|
||||
7 components — Grid, Layout, Table, Flex, Space, and container components for page structure.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
All components are exported from `@superset-ui/core/components`:
|
||||
|
||||
```tsx
|
||||
import { Button, Modal, Select } from '@superset-ui/core/components';
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
This documentation is auto-generated from Storybook stories. To add or update component documentation:
|
||||
|
||||
1. Create or update the component's `.stories.tsx` file
|
||||
2. Add a descriptive `title` and `description` in the story meta
|
||||
3. Export an interactive story with `args` for configurable props
|
||||
4. Run `yarn generate:superset-components` in the `docs/` directory
|
||||
|
||||
:::info Work in Progress
|
||||
This component library is actively being documented. See the [Components TODO](./TODO) page for a list of components awaiting documentation.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
*Auto-generated from Storybook stories in the [Design System/Introduction](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-ui-core/src/components/DesignSystem.stories.tsx) story.*
|
||||
215
docs/developer_docs/components/ui/autocomplete.mdx
Normal file
215
docs/developer_docs/components/ui/autocomplete.mdx
Normal file
@@ -0,0 +1,215 @@
|
||||
---
|
||||
title: AutoComplete
|
||||
sidebar_label: AutoComplete
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# AutoComplete
|
||||
|
||||
AutoComplete component for search functionality.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="AutoComplete"
|
||||
props={{
|
||||
placeholder: "Type to search...",
|
||||
options: [
|
||||
{
|
||||
value: "Dashboard",
|
||||
label: "Dashboard"
|
||||
},
|
||||
{
|
||||
value: "Chart",
|
||||
label: "Chart"
|
||||
},
|
||||
{
|
||||
value: "Dataset",
|
||||
label: "Dataset"
|
||||
},
|
||||
{
|
||||
value: "Database",
|
||||
label: "Database"
|
||||
},
|
||||
{
|
||||
value: "Query",
|
||||
label: "Query"
|
||||
}
|
||||
],
|
||||
style: {
|
||||
width: 300
|
||||
},
|
||||
filterOption: true
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "placeholder",
|
||||
label: "Placeholder",
|
||||
type: "text",
|
||||
description: "Placeholder text for AutoComplete"
|
||||
},
|
||||
{
|
||||
name: "style",
|
||||
label: "Style",
|
||||
type: "object",
|
||||
description: "Custom styles for AutoComplete"
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
label: "Value",
|
||||
type: "text",
|
||||
description: "Selected option"
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Disable the AutoComplete"
|
||||
},
|
||||
{
|
||||
name: "popupMatchSelectWidth",
|
||||
label: "Popup Match Select Width",
|
||||
type: "number",
|
||||
description: "Width of the dropdown"
|
||||
},
|
||||
{
|
||||
name: "allowClear",
|
||||
label: "Allow Clear",
|
||||
type: "boolean",
|
||||
description: "Show clear button"
|
||||
},
|
||||
{
|
||||
name: "autoFocus",
|
||||
label: "Auto Focus",
|
||||
type: "boolean",
|
||||
description: "If get focus when component mounted"
|
||||
},
|
||||
{
|
||||
name: "backfill",
|
||||
label: "Backfill",
|
||||
type: "boolean",
|
||||
description: "If backfill selected item the input when using keyboard"
|
||||
},
|
||||
{
|
||||
name: "popupClassName",
|
||||
label: "Popup Class Name",
|
||||
type: "text",
|
||||
description: "The className of dropdown menu"
|
||||
},
|
||||
{
|
||||
name: "filterOption",
|
||||
label: "Filter Option",
|
||||
type: "boolean",
|
||||
description: "Enable filtering of options based on input"
|
||||
},
|
||||
{
|
||||
name: "notFoundContent",
|
||||
label: "Not Found Content",
|
||||
type: "text",
|
||||
description: "Specify content to show when no result matches."
|
||||
},
|
||||
{
|
||||
name: "open",
|
||||
label: "Open",
|
||||
type: "boolean",
|
||||
description: "Controlled open state of dropdown"
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
description: "Set validation status"
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"large",
|
||||
"middle",
|
||||
"small"
|
||||
],
|
||||
description: "The size of the input box"
|
||||
},
|
||||
{
|
||||
name: "variant",
|
||||
label: "Variant",
|
||||
type: "select",
|
||||
options: [
|
||||
"outlined",
|
||||
"borderless",
|
||||
"filled"
|
||||
],
|
||||
description: "Variants of input"
|
||||
},
|
||||
{
|
||||
name: "virtual",
|
||||
label: "Virtual",
|
||||
type: "boolean",
|
||||
description: "Disable virtual scroll when set to false"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<AutoComplete
|
||||
placeholder="Type to search..."
|
||||
options={[{"value":"Dashboard","label":"Dashboard"},{"value":"Chart","label":"Chart"},{"value":"Dataset","label":"Dataset"},{"value":"Database","label":"Database"},{"value":"Query","label":"Query"}]}
|
||||
style={{"width":300}}
|
||||
filterOption
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `placeholder` | `string` | `"Type to search..."` | Placeholder text for AutoComplete |
|
||||
| `options` | `any` | `[{"value":"Dashboard","label":"Dashboard"},{"value":"Chart","label":"Chart"},{"value":"Dataset","label":"Dataset"},{"value":"Database","label":"Database"},{"value":"Query","label":"Query"}]` | The dropdown options |
|
||||
| `style` | `any` | `{"width":300}` | Custom styles for AutoComplete |
|
||||
| `filterOption` | `boolean` | `true` | Enable filtering of options based on input |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { AutoComplete } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/AutoComplete/AutoComplete.stories.tsx).
|
||||
:::
|
||||
140
docs/developer_docs/components/ui/avatar.mdx
Normal file
140
docs/developer_docs/components/ui/avatar.mdx
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
title: Avatar
|
||||
sidebar_label: Avatar
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Avatar
|
||||
|
||||
The Avatar component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Avatar"
|
||||
props={{
|
||||
children: "AB",
|
||||
alt: "",
|
||||
gap: 4,
|
||||
shape: "circle",
|
||||
size: "default",
|
||||
src: "",
|
||||
draggable: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "children",
|
||||
label: "Children",
|
||||
type: "text",
|
||||
description: "Text or initials to display inside the avatar."
|
||||
},
|
||||
{
|
||||
name: "alt",
|
||||
label: "Alt",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "gap",
|
||||
label: "Gap",
|
||||
type: "number",
|
||||
description: "Letter spacing inside the avatar."
|
||||
},
|
||||
{
|
||||
name: "shape",
|
||||
label: "Shape",
|
||||
type: "select",
|
||||
options: [
|
||||
"circle",
|
||||
"square"
|
||||
],
|
||||
description: "The shape of the avatar."
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"small",
|
||||
"default",
|
||||
"large"
|
||||
],
|
||||
description: "The size of the avatar."
|
||||
},
|
||||
{
|
||||
name: "src",
|
||||
label: "Src",
|
||||
type: "text",
|
||||
description: "Image URL for the avatar. If provided, overrides children."
|
||||
},
|
||||
{
|
||||
name: "draggable",
|
||||
label: "Draggable",
|
||||
type: "boolean"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Avatar
|
||||
alt=""
|
||||
gap={4}
|
||||
shape="circle"
|
||||
size="default"
|
||||
src=""
|
||||
>
|
||||
AB
|
||||
</Avatar>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `children` | `string` | `"AB"` | Text or initials to display inside the avatar. |
|
||||
| `alt` | `string` | `""` | - |
|
||||
| `gap` | `number` | `4` | Letter spacing inside the avatar. |
|
||||
| `shape` | `string` | `"circle"` | The shape of the avatar. |
|
||||
| `size` | `string` | `"default"` | The size of the avatar. |
|
||||
| `src` | `string` | `""` | Image URL for the avatar. If provided, overrides children. |
|
||||
| `draggable` | `boolean` | `false` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Avatar } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Avatar/Avatar.stories.tsx).
|
||||
:::
|
||||
160
docs/developer_docs/components/ui/badge.mdx
Normal file
160
docs/developer_docs/components/ui/badge.mdx
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
title: Badge
|
||||
sidebar_label: Badge
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Badge
|
||||
|
||||
The Badge component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Badge"
|
||||
props={{
|
||||
count: 5,
|
||||
size: "default",
|
||||
showZero: false,
|
||||
overflowCount: 99
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "count",
|
||||
label: "Count",
|
||||
type: "number",
|
||||
description: "Number to show in the badge."
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"default",
|
||||
"small"
|
||||
],
|
||||
description: "Size of the badge."
|
||||
},
|
||||
{
|
||||
name: "showZero",
|
||||
label: "Show Zero",
|
||||
type: "boolean",
|
||||
description: "Whether to show badge when count is zero."
|
||||
},
|
||||
{
|
||||
name: "overflowCount",
|
||||
label: "Overflow Count",
|
||||
type: "number",
|
||||
description: "Max count to show. Shows count+ when exceeded (e.g., 99+)."
|
||||
},
|
||||
{
|
||||
name: "color",
|
||||
label: "Color",
|
||||
type: "select",
|
||||
options: [
|
||||
"pink",
|
||||
"red",
|
||||
"yellow",
|
||||
"orange",
|
||||
"cyan",
|
||||
"green",
|
||||
"blue",
|
||||
"purple",
|
||||
"geekblue",
|
||||
"magenta",
|
||||
"volcano",
|
||||
"gold",
|
||||
"lime"
|
||||
],
|
||||
description: "Custom background color for the badge."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Badge
|
||||
count={5}
|
||||
size="default"
|
||||
overflowCount={99}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Status Badge
|
||||
|
||||
```tsx live
|
||||
function StatusBadgeDemo() {
|
||||
const statuses = ['default', 'success', 'processing', 'warning', 'error'];
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
{statuses.map(status => (
|
||||
<Badge key={status} status={status} text={`Status: ${status}`} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Color Gallery
|
||||
|
||||
```tsx live
|
||||
function ColorGallery() {
|
||||
const colors = ['pink', 'red', 'orange', 'green', 'cyan', 'blue', 'purple'];
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
||||
{colors.map(color => (
|
||||
<Badge key={color} count={9} color={color} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `count` | `number` | `5` | Number to show in the badge. |
|
||||
| `size` | `string` | `"default"` | Size of the badge. |
|
||||
| `showZero` | `boolean` | `false` | Whether to show badge when count is zero. |
|
||||
| `overflowCount` | `number` | `99` | Max count to show. Shows count+ when exceeded (e.g., 99+). |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Badge } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Badge/Badge.stories.tsx).
|
||||
:::
|
||||
93
docs/developer_docs/components/ui/breadcrumb.mdx
Normal file
93
docs/developer_docs/components/ui/breadcrumb.mdx
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Breadcrumb
|
||||
sidebar_label: Breadcrumb
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Breadcrumb
|
||||
|
||||
Breadcrumb component for displaying navigation paths.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Breadcrumb"
|
||||
props={{
|
||||
items: [
|
||||
{
|
||||
title: "Home",
|
||||
href: "/"
|
||||
},
|
||||
{
|
||||
title: "Library",
|
||||
href: "/library"
|
||||
},
|
||||
{
|
||||
title: "Data"
|
||||
}
|
||||
],
|
||||
separator: "/"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "separator",
|
||||
label: "Separator",
|
||||
type: "text",
|
||||
description: "Custom separator between items"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{ title: 'Home', href: '/' },
|
||||
{ title: 'Library', href: '/library' },
|
||||
{ title: 'Data' },
|
||||
]}
|
||||
separator="/"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Breadcrumb } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Breadcrumb/Breadcrumb.stories.tsx).
|
||||
:::
|
||||
142
docs/developer_docs/components/ui/button.mdx
Normal file
142
docs/developer_docs/components/ui/button.mdx
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Button
|
||||
sidebar_label: Button
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls, ComponentGallery } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Button
|
||||
|
||||
The Button component from Superset's UI library.
|
||||
|
||||
## All Variants
|
||||
|
||||
<ComponentGallery
|
||||
component="Button"
|
||||
sizes={["xsmall","small","default"]}
|
||||
styles={["primary","secondary","dashed","danger","link"]}
|
||||
sizeProp="buttonSize"
|
||||
styleProp="buttonStyle"
|
||||
/>
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Button"
|
||||
props={{
|
||||
buttonStyle: "default",
|
||||
buttonSize: "default",
|
||||
children: "Button!"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "buttonStyle",
|
||||
label: "Button Style",
|
||||
type: "select",
|
||||
options: [
|
||||
"primary",
|
||||
"secondary",
|
||||
"dashed",
|
||||
"danger",
|
||||
"link"
|
||||
],
|
||||
description: "The style variant of the button."
|
||||
},
|
||||
{
|
||||
name: "buttonSize",
|
||||
label: "Button Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"xsmall",
|
||||
"small",
|
||||
"default"
|
||||
],
|
||||
description: "The size of the button."
|
||||
},
|
||||
{
|
||||
name: "children",
|
||||
label: "Children",
|
||||
type: "text",
|
||||
description: "The button text or content."
|
||||
},
|
||||
{
|
||||
name: "target",
|
||||
label: "Target",
|
||||
type: "select"
|
||||
},
|
||||
{
|
||||
name: "href",
|
||||
label: "Href",
|
||||
type: "select"
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Whether the button is disabled."
|
||||
},
|
||||
{
|
||||
name: "loading",
|
||||
label: "Loading",
|
||||
type: "boolean",
|
||||
description: "Whether to show loading spinner."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Button
|
||||
buttonStyle="default"
|
||||
buttonSize="default"
|
||||
>
|
||||
Button!
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `buttonStyle` | `string` | `"default"` | The style variant of the button. |
|
||||
| `buttonSize` | `string` | `"default"` | The size of the button. |
|
||||
| `children` | `string` | `"Button!"` | The button text or content. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Button } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Button/Button.stories.tsx).
|
||||
:::
|
||||
88
docs/developer_docs/components/ui/buttongroup.mdx
Normal file
88
docs/developer_docs/components/ui/buttongroup.mdx
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: ButtonGroup
|
||||
sidebar_label: ButtonGroup
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# ButtonGroup
|
||||
|
||||
ButtonGroup is a container that groups multiple Button components together with consistent spacing and styling.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="ButtonGroup"
|
||||
props={{
|
||||
expand: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "expand",
|
||||
label: "Expand",
|
||||
type: "boolean",
|
||||
description: "When true, buttons expand to fill available width."
|
||||
},
|
||||
{
|
||||
name: "className",
|
||||
label: "Class Name",
|
||||
type: "text",
|
||||
description: "CSS class name for custom styling."
|
||||
}
|
||||
]}
|
||||
sampleChildren={[{"component":"Button","props":{"buttonStyle":"tertiary","children":"Button 1"}},{"component":"Button","props":{"buttonStyle":"tertiary","children":"Button 2"}},{"component":"Button","props":{"buttonStyle":"tertiary","children":"Button 3"}}]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<Button buttonStyle="tertiary">Button 1</Button>
|
||||
<Button buttonStyle="tertiary">Button 2</Button>
|
||||
<Button buttonStyle="tertiary">Button 3</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `expand` | `boolean` | `false` | When true, buttons expand to fill available width. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { ButtonGroup } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/ButtonGroup/ButtonGroup.stories.tsx).
|
||||
:::
|
||||
79
docs/developer_docs/components/ui/cachedlabel.mdx
Normal file
79
docs/developer_docs/components/ui/cachedlabel.mdx
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: CachedLabel
|
||||
sidebar_label: CachedLabel
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# CachedLabel
|
||||
|
||||
The CachedLabel component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="CachedLabel"
|
||||
props={{}}
|
||||
controls={[
|
||||
{
|
||||
name: "cachedTimestamp",
|
||||
label: "Cached Timestamp",
|
||||
type: "text",
|
||||
description: "ISO timestamp of when the data was cached"
|
||||
},
|
||||
{
|
||||
name: "className",
|
||||
label: "Class Name",
|
||||
type: "text",
|
||||
description: "Additional CSS class for the label"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<CachedLabel
|
||||
// Add props here
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { CachedLabel } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/CachedLabel/CachedLabel.stories.tsx).
|
||||
:::
|
||||
142
docs/developer_docs/components/ui/card.mdx
Normal file
142
docs/developer_docs/components/ui/card.mdx
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Card
|
||||
sidebar_label: Card
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Card
|
||||
|
||||
A container component for grouping related content. Supports titles, borders, loading states, and hover effects.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Card"
|
||||
props={{
|
||||
padded: true,
|
||||
title: "Dashboard Overview",
|
||||
children: "This card displays a summary of your dashboard metrics and recent activity.",
|
||||
bordered: true,
|
||||
loading: false,
|
||||
hoverable: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "padded",
|
||||
label: "Padded",
|
||||
type: "boolean",
|
||||
description: "Whether the card content has padding."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "Title text displayed at the top of the card."
|
||||
},
|
||||
{
|
||||
name: "children",
|
||||
label: "Children",
|
||||
type: "text",
|
||||
description: "The content inside the card."
|
||||
},
|
||||
{
|
||||
name: "bordered",
|
||||
label: "Bordered",
|
||||
type: "boolean",
|
||||
description: "Whether to show a border around the card."
|
||||
},
|
||||
{
|
||||
name: "loading",
|
||||
label: "Loading",
|
||||
type: "boolean",
|
||||
description: "Whether to show a loading skeleton."
|
||||
},
|
||||
{
|
||||
name: "hoverable",
|
||||
label: "Hoverable",
|
||||
type: "boolean",
|
||||
description: "Whether the card lifts on hover."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Card title="Dashboard Overview" bordered>
|
||||
This card displays a summary of your dashboard metrics and recent activity.
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Card States
|
||||
|
||||
```tsx live
|
||||
function CardStates() {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
||||
<Card title="Default" bordered style={{ width: 250 }}>
|
||||
Default card content.
|
||||
</Card>
|
||||
<Card title="Hoverable" bordered hoverable style={{ width: 250 }}>
|
||||
Hover over this card.
|
||||
</Card>
|
||||
<Card title="Loading" bordered loading style={{ width: 250 }}>
|
||||
This content is hidden while loading.
|
||||
</Card>
|
||||
<Card title="No Border" style={{ width: 250 }}>
|
||||
Borderless card.
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `padded` | `boolean` | `true` | Whether the card content has padding. |
|
||||
| `title` | `string` | `"Dashboard Overview"` | Title text displayed at the top of the card. |
|
||||
| `children` | `string` | `"This card displays a summary of your dashboard metrics and recent activity."` | The content inside the card. |
|
||||
| `bordered` | `boolean` | `true` | Whether to show a border around the card. |
|
||||
| `loading` | `boolean` | `false` | Whether to show a loading skeleton. |
|
||||
| `hoverable` | `boolean` | `false` | Whether the card lifts on hover. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Card } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Card/Card.stories.tsx).
|
||||
:::
|
||||
141
docs/developer_docs/components/ui/checkbox.mdx
Normal file
141
docs/developer_docs/components/ui/checkbox.mdx
Normal file
@@ -0,0 +1,141 @@
|
||||
---
|
||||
title: Checkbox
|
||||
sidebar_label: Checkbox
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Checkbox
|
||||
|
||||
Checkbox component that supports both regular and indeterminate states, built on top of Ant Design v5 Checkbox.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Checkbox"
|
||||
props={{
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "checked",
|
||||
label: "Checked",
|
||||
type: "boolean",
|
||||
description: "Whether the checkbox is checked."
|
||||
},
|
||||
{
|
||||
name: "indeterminate",
|
||||
label: "Indeterminate",
|
||||
type: "boolean",
|
||||
description: "Whether the checkbox is in indeterminate state (partially selected)."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Checkbox
|
||||
// Add props here
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## All Checkbox States
|
||||
|
||||
```tsx live
|
||||
function AllStates() {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<Checkbox checked={false}>Unchecked</Checkbox>
|
||||
<Checkbox checked={true}>Checked</Checkbox>
|
||||
<Checkbox indeterminate={true}>Indeterminate</Checkbox>
|
||||
<Checkbox disabled>Disabled unchecked</Checkbox>
|
||||
<Checkbox disabled checked>Disabled checked</Checkbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Select All Pattern
|
||||
|
||||
```tsx live
|
||||
function SelectAllDemo() {
|
||||
const [selected, setSelected] = React.useState([]);
|
||||
const options = ['Option A', 'Option B', 'Option C'];
|
||||
|
||||
const allSelected = selected.length === options.length;
|
||||
const indeterminate = selected.length > 0 && !allSelected;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={allSelected}
|
||||
indeterminate={indeterminate}
|
||||
onChange={(e) => setSelected(e.target.checked ? [...options] : [])}
|
||||
>
|
||||
Select All
|
||||
</Checkbox>
|
||||
<div style={{ marginLeft: 24, marginTop: 8 }}>
|
||||
{options.map(opt => (
|
||||
<div key={opt}>
|
||||
<Checkbox
|
||||
checked={selected.includes(opt)}
|
||||
onChange={() => setSelected(prev =>
|
||||
prev.includes(opt) ? prev.filter(x => x !== opt) : [...prev, opt]
|
||||
)}
|
||||
>
|
||||
{opt}
|
||||
</Checkbox>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `checked` | `boolean` | `false` | Whether the checkbox is checked. |
|
||||
| `indeterminate` | `boolean` | `false` | Whether the checkbox is in indeterminate state (partially selected). |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Checkbox } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Checkbox/Checkbox.stories.tsx).
|
||||
:::
|
||||
106
docs/developer_docs/components/ui/collapse.mdx
Normal file
106
docs/developer_docs/components/ui/collapse.mdx
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: Collapse
|
||||
sidebar_label: Collapse
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Collapse
|
||||
|
||||
The Collapse component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Collapse"
|
||||
props={{
|
||||
ghost: false,
|
||||
bordered: true,
|
||||
accordion: false,
|
||||
animateArrows: false,
|
||||
modalMode: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "ghost",
|
||||
label: "Ghost",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "bordered",
|
||||
label: "Bordered",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "accordion",
|
||||
label: "Accordion",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "animateArrows",
|
||||
label: "Animate Arrows",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "modalMode",
|
||||
label: "Modal Mode",
|
||||
type: "boolean"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Collapse
|
||||
bordered
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `ghost` | `boolean` | `false` | - |
|
||||
| `bordered` | `boolean` | `true` | - |
|
||||
| `accordion` | `boolean` | `false` | - |
|
||||
| `animateArrows` | `boolean` | `false` | - |
|
||||
| `modalMode` | `boolean` | `false` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Collapse } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Collapse/Collapse.stories.tsx).
|
||||
:::
|
||||
110
docs/developer_docs/components/ui/datepicker.mdx
Normal file
110
docs/developer_docs/components/ui/datepicker.mdx
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
title: DatePicker
|
||||
sidebar_label: DatePicker
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# DatePicker
|
||||
|
||||
The DatePicker component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="DatePicker"
|
||||
props={{
|
||||
placeholder: "Select date",
|
||||
showNow: true,
|
||||
allowClear: false,
|
||||
autoFocus: true,
|
||||
disabled: false,
|
||||
format: "YYYY-MM-DD hh:mm a",
|
||||
inputReadOnly: false,
|
||||
picker: "date",
|
||||
placement: "bottomLeft",
|
||||
size: "middle",
|
||||
showTime: {
|
||||
format: "hh:mm a",
|
||||
needConfirm: false
|
||||
}
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "placeholder",
|
||||
label: "Placeholder",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "showNow",
|
||||
label: "Show Now",
|
||||
type: "boolean",
|
||||
description: "Show \"Now\" button to select current date and time."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<DatePicker
|
||||
placeholder="Select date"
|
||||
format="YYYY-MM-DD hh:mm a"
|
||||
showNow
|
||||
showTime={{ format: 'hh:mm a', needConfirm: false }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `placeholder` | `string` | `"Select date"` | - |
|
||||
| `showNow` | `boolean` | `true` | Show "Now" button to select current date and time. |
|
||||
| `allowClear` | `boolean` | `false` | - |
|
||||
| `autoFocus` | `boolean` | `true` | - |
|
||||
| `disabled` | `boolean` | `false` | - |
|
||||
| `format` | `string` | `"YYYY-MM-DD hh:mm a"` | - |
|
||||
| `inputReadOnly` | `boolean` | `false` | - |
|
||||
| `picker` | `string` | `"date"` | - |
|
||||
| `placement` | `string` | `"bottomLeft"` | - |
|
||||
| `size` | `string` | `"middle"` | - |
|
||||
| `showTime` | `any` | `{"format":"hh:mm a","needConfirm":false}` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { DatePicker } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/DatePicker/DatePicker.stories.tsx).
|
||||
:::
|
||||
144
docs/developer_docs/components/ui/divider.mdx
Normal file
144
docs/developer_docs/components/ui/divider.mdx
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
title: Divider
|
||||
sidebar_label: Divider
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Divider
|
||||
|
||||
The Divider component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Divider"
|
||||
props={{
|
||||
dashed: false,
|
||||
variant: "solid",
|
||||
orientation: "center",
|
||||
orientationMargin: "",
|
||||
plain: true,
|
||||
type: "horizontal"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "dashed",
|
||||
label: "Dashed",
|
||||
type: "boolean",
|
||||
description: "Whether line is dashed (deprecated, use variant)."
|
||||
},
|
||||
{
|
||||
name: "variant",
|
||||
label: "Variant",
|
||||
type: "select",
|
||||
options: [
|
||||
"dashed",
|
||||
"dotted",
|
||||
"solid"
|
||||
],
|
||||
description: "Line style of the divider."
|
||||
},
|
||||
{
|
||||
name: "orientation",
|
||||
label: "Orientation",
|
||||
type: "select",
|
||||
options: [
|
||||
"left",
|
||||
"right",
|
||||
"center"
|
||||
],
|
||||
description: "Position of title inside divider."
|
||||
},
|
||||
{
|
||||
name: "orientationMargin",
|
||||
label: "Orientation Margin",
|
||||
type: "text",
|
||||
description: "Margin from divider edge to title."
|
||||
},
|
||||
{
|
||||
name: "plain",
|
||||
label: "Plain",
|
||||
type: "boolean",
|
||||
description: "Use plain style without bold title."
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
label: "Type",
|
||||
type: "select",
|
||||
options: [
|
||||
"horizontal",
|
||||
"vertical"
|
||||
],
|
||||
description: "Direction of the divider."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<>
|
||||
<p>Horizontal divider with title (orientationMargin applies here):</p>
|
||||
<Divider orientation="left" orientationMargin={0}>Left Title</Divider>
|
||||
<Divider orientation="right" orientationMargin={50}>Right Title</Divider>
|
||||
<Divider>Center Title</Divider>
|
||||
<p>Vertical divider (use container gap for spacing):</p>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||
<span>Link</span>
|
||||
<Divider type="vertical" />
|
||||
<span>Link</span>
|
||||
<Divider type="vertical" />
|
||||
<span>Link</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `dashed` | `boolean` | `false` | Whether line is dashed (deprecated, use variant). |
|
||||
| `variant` | `string` | `"solid"` | Line style of the divider. |
|
||||
| `orientation` | `string` | `"center"` | Position of title inside divider. |
|
||||
| `orientationMargin` | `string` | `""` | Margin from divider edge to title. |
|
||||
| `plain` | `boolean` | `true` | Use plain style without bold title. |
|
||||
| `type` | `string` | `"horizontal"` | Direction of the divider. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Divider } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Divider/Divider.stories.tsx).
|
||||
:::
|
||||
172
docs/developer_docs/components/ui/editabletitle.mdx
Normal file
172
docs/developer_docs/components/ui/editabletitle.mdx
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
title: EditableTitle
|
||||
sidebar_label: EditableTitle
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# EditableTitle
|
||||
|
||||
The EditableTitle component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="EditableTitle"
|
||||
props={{
|
||||
canEdit: true,
|
||||
editing: false,
|
||||
emptyText: "Empty text",
|
||||
noPermitTooltip: "Not permitted",
|
||||
showTooltip: true,
|
||||
title: "Title",
|
||||
defaultTitle: "Default title",
|
||||
placeholder: "Placeholder",
|
||||
certifiedBy: "",
|
||||
certificationDetails: "",
|
||||
maxWidth: 100,
|
||||
autoSize: true
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "canEdit",
|
||||
label: "Can Edit",
|
||||
type: "boolean",
|
||||
description: "Whether the title can be edited."
|
||||
},
|
||||
{
|
||||
name: "editing",
|
||||
label: "Editing",
|
||||
type: "boolean",
|
||||
description: "Whether the title is currently in edit mode."
|
||||
},
|
||||
{
|
||||
name: "emptyText",
|
||||
label: "Empty Text",
|
||||
type: "text",
|
||||
description: "Text to display when title is empty."
|
||||
},
|
||||
{
|
||||
name: "noPermitTooltip",
|
||||
label: "No Permit Tooltip",
|
||||
type: "text",
|
||||
description: "Tooltip shown when user lacks edit permission."
|
||||
},
|
||||
{
|
||||
name: "showTooltip",
|
||||
label: "Show Tooltip",
|
||||
type: "boolean",
|
||||
description: "Whether to show tooltip on hover."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "The title text to display."
|
||||
},
|
||||
{
|
||||
name: "defaultTitle",
|
||||
label: "Default Title",
|
||||
type: "text",
|
||||
description: "Default title when none is provided."
|
||||
},
|
||||
{
|
||||
name: "placeholder",
|
||||
label: "Placeholder",
|
||||
type: "text",
|
||||
description: "Placeholder text when editing."
|
||||
},
|
||||
{
|
||||
name: "certifiedBy",
|
||||
label: "Certified By",
|
||||
type: "text",
|
||||
description: "Name of person/team who certified this item."
|
||||
},
|
||||
{
|
||||
name: "certificationDetails",
|
||||
label: "Certification Details",
|
||||
type: "text",
|
||||
description: "Additional certification details or description."
|
||||
},
|
||||
{
|
||||
name: "maxWidth",
|
||||
label: "Max Width",
|
||||
type: "number",
|
||||
description: "Maximum width of the title in pixels."
|
||||
},
|
||||
{
|
||||
name: "autoSize",
|
||||
label: "Auto Size",
|
||||
type: "boolean",
|
||||
description: "Whether to auto-size based on content."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<EditableTitle
|
||||
title="My Dashboard"
|
||||
canEdit
|
||||
showTooltip
|
||||
certifiedBy="Data Team"
|
||||
certificationDetails="Verified Q1 2024"
|
||||
onSaveTitle={(newTitle) => console.log('Saved:', newTitle)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `canEdit` | `boolean` | `true` | Whether the title can be edited. |
|
||||
| `editing` | `boolean` | `false` | Whether the title is currently in edit mode. |
|
||||
| `emptyText` | `string` | `"Empty text"` | Text to display when title is empty. |
|
||||
| `noPermitTooltip` | `string` | `"Not permitted"` | Tooltip shown when user lacks edit permission. |
|
||||
| `showTooltip` | `boolean` | `true` | Whether to show tooltip on hover. |
|
||||
| `title` | `string` | `"Title"` | The title text to display. |
|
||||
| `defaultTitle` | `string` | `"Default title"` | Default title when none is provided. |
|
||||
| `placeholder` | `string` | `"Placeholder"` | Placeholder text when editing. |
|
||||
| `certifiedBy` | `string` | `""` | Name of person/team who certified this item. |
|
||||
| `certificationDetails` | `string` | `""` | Additional certification details or description. |
|
||||
| `maxWidth` | `number` | `100` | Maximum width of the title in pixels. |
|
||||
| `autoSize` | `boolean` | `true` | Whether to auto-size based on content. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { EditableTitle } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/EditableTitle/EditableTitle.stories.tsx).
|
||||
:::
|
||||
147
docs/developer_docs/components/ui/emptystate.mdx
Normal file
147
docs/developer_docs/components/ui/emptystate.mdx
Normal file
@@ -0,0 +1,147 @@
|
||||
---
|
||||
title: EmptyState
|
||||
sidebar_label: EmptyState
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls, ComponentGallery } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# EmptyState
|
||||
|
||||
The EmptyState component from Superset's UI library.
|
||||
|
||||
## All Variants
|
||||
|
||||
<ComponentGallery
|
||||
component="EmptyState"
|
||||
sizes={["medium"]}
|
||||
styles={["chart.svg","document.svg","empty-charts.svg","empty-dashboard.svg","empty-dataset.svg","empty-query.svg","empty-table.svg","empty.svg","empty_sql_chart.svg","filter-results.svg","filter.svg","star-circle.svg","union.svg","vector.svg"]}
|
||||
sizeProp="size"
|
||||
styleProp="image"
|
||||
/>
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="EmptyState"
|
||||
props={{
|
||||
size: "medium",
|
||||
title: "No Data Available",
|
||||
description: "There is no data to display at this time.",
|
||||
image: "empty.svg",
|
||||
buttonText: ""
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"small",
|
||||
"medium",
|
||||
"large"
|
||||
],
|
||||
description: "Size of the empty state component."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "Main title text."
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "text",
|
||||
description: "Description text below the title."
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
label: "Image",
|
||||
type: "select",
|
||||
options: [
|
||||
"chart.svg",
|
||||
"document.svg",
|
||||
"empty-charts.svg",
|
||||
"empty-dashboard.svg",
|
||||
"empty-dataset.svg",
|
||||
"empty-query.svg",
|
||||
"empty-table.svg",
|
||||
"empty.svg",
|
||||
"empty_sql_chart.svg",
|
||||
"filter-results.svg",
|
||||
"filter.svg",
|
||||
"star-circle.svg",
|
||||
"union.svg",
|
||||
"vector.svg"
|
||||
],
|
||||
description: "Predefined image to display."
|
||||
},
|
||||
{
|
||||
name: "buttonText",
|
||||
label: "Button Text",
|
||||
type: "text",
|
||||
description: "Text for optional action button."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<EmptyState
|
||||
size="medium"
|
||||
title="No Results Found"
|
||||
description="Try adjusting your filters or search terms."
|
||||
image="filter.svg"
|
||||
buttonText="Clear Filters"
|
||||
buttonAction={() => alert('Filters cleared!')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `size` | `string` | `"medium"` | Size of the empty state component. |
|
||||
| `title` | `string` | `"No Data Available"` | Main title text. |
|
||||
| `description` | `string` | `"There is no data to display at this time."` | Description text below the title. |
|
||||
| `image` | `string` | `"empty.svg"` | Predefined image to display. |
|
||||
| `buttonText` | `string` | `""` | Text for optional action button. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { EmptyState } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/EmptyState/EmptyState.stories.tsx).
|
||||
:::
|
||||
96
docs/developer_docs/components/ui/favestar.mdx
Normal file
96
docs/developer_docs/components/ui/favestar.mdx
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: FaveStar
|
||||
sidebar_label: FaveStar
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# FaveStar
|
||||
|
||||
FaveStar component for marking items as favorites
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="FaveStar"
|
||||
props={{
|
||||
itemId: 1,
|
||||
isStarred: false,
|
||||
showTooltip: true
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "itemId",
|
||||
label: "Item ID",
|
||||
type: "number",
|
||||
description: "Unique identifier for the item"
|
||||
},
|
||||
{
|
||||
name: "isStarred",
|
||||
label: "Is Starred",
|
||||
type: "boolean",
|
||||
description: "Whether the item is currently starred."
|
||||
},
|
||||
{
|
||||
name: "showTooltip",
|
||||
label: "Show Tooltip",
|
||||
type: "boolean",
|
||||
description: "Show tooltip on hover."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<FaveStar
|
||||
itemId={1}
|
||||
showTooltip
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `itemId` | `number` | `1` | Unique identifier for the item |
|
||||
| `isStarred` | `boolean` | `false` | Whether the item is currently starred. |
|
||||
| `showTooltip` | `boolean` | `true` | Show tooltip on hover. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { FaveStar } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/FaveStar/FaveStar.stories.tsx).
|
||||
:::
|
||||
106
docs/developer_docs/components/ui/iconbutton.mdx
Normal file
106
docs/developer_docs/components/ui/iconbutton.mdx
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: IconButton
|
||||
sidebar_label: IconButton
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# IconButton
|
||||
|
||||
The IconButton component is a versatile button that allows you to combine an icon with a text label. It is designed for use in situations where you want to display an icon along with some text in a single clickable element.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="IconButton"
|
||||
props={{
|
||||
buttonText: "IconButton",
|
||||
altText: "Icon button alt text",
|
||||
padded: true,
|
||||
icon: "https://superset.apache.org/img/superset-logo-horiz.svg"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "buttonText",
|
||||
label: "Button Text",
|
||||
type: "text",
|
||||
description: "The text inside the button."
|
||||
},
|
||||
{
|
||||
name: "altText",
|
||||
label: "Alt Text",
|
||||
type: "text",
|
||||
description: "The alt text for the button. If not provided, the button text is used as the alt text by default."
|
||||
},
|
||||
{
|
||||
name: "padded",
|
||||
label: "Padded",
|
||||
type: "boolean",
|
||||
description: "Add padding between icon and button text."
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
label: "Icon",
|
||||
type: "text",
|
||||
description: "Icon inside the button (URL or path)."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<IconButton
|
||||
buttonText="IconButton"
|
||||
altText="Icon button alt text"
|
||||
padded
|
||||
icon="https://superset.apache.org/img/superset-logo-horiz.svg"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `buttonText` | `string` | `"IconButton"` | The text inside the button. |
|
||||
| `altText` | `string` | `"Icon button alt text"` | The alt text for the button. If not provided, the button text is used as the alt text by default. |
|
||||
| `padded` | `boolean` | `true` | Add padding between icon and button text. |
|
||||
| `icon` | `string` | `"https://superset.apache.org/img/superset-logo-horiz.svg"` | Icon inside the button (URL or path). |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { IconButton } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/IconButton/IconButton.stories.tsx).
|
||||
:::
|
||||
252
docs/developer_docs/components/ui/icons.mdx
Normal file
252
docs/developer_docs/components/ui/icons.mdx
Normal file
@@ -0,0 +1,252 @@
|
||||
---
|
||||
title: Icons
|
||||
sidebar_label: Icons
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Icons
|
||||
|
||||
Icon library for Apache Superset. Contains over 200 icons based on Ant Design icons with consistent sizing and theming support.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Icons"
|
||||
renderComponent="Icons.InfoCircleOutlined"
|
||||
props={{
|
||||
iconSize: "xl"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "iconSize",
|
||||
label: "Icon Size",
|
||||
type: "inline-radio",
|
||||
options: [
|
||||
"s",
|
||||
"m",
|
||||
"l",
|
||||
"xl",
|
||||
"xxl"
|
||||
],
|
||||
description: "Size of the icons: s (12px), m (16px), l (20px), xl (24px), xxl (32px)."
|
||||
},
|
||||
{
|
||||
name: "showNames",
|
||||
label: "Show Names",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "iconColor",
|
||||
label: "Icon Color",
|
||||
type: "select"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
|
||||
<Icons.InfoCircleOutlined iconSize="xl" />
|
||||
<Icons.CheckCircleOutlined iconSize="xl" />
|
||||
<Icons.WarningOutlined iconSize="xl" />
|
||||
<Icons.CloseCircleOutlined iconSize="xl" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Icon Sizes
|
||||
|
||||
```tsx live
|
||||
function IconSizes() {
|
||||
const sizes = ['s', 'm', 'l', 'xl', 'xxl'];
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 24, alignItems: 'end' }}>
|
||||
{sizes.map(size => (
|
||||
<div key={size} style={{ textAlign: 'center' }}>
|
||||
<Icons.DatabaseOutlined iconSize={size} />
|
||||
<div style={{ fontSize: 12, marginTop: 8, color: '#666' }}>{size}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Icon Gallery
|
||||
|
||||
```tsx live
|
||||
function IconGallery() {
|
||||
const Section = ({ title, children }) => (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div style={{ fontWeight: 600, marginBottom: 8, color: '#666' }}>{title}</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<Section title="Charts">
|
||||
<Icons.LineChartOutlined iconSize="xl" />
|
||||
<Icons.BarChartOutlined iconSize="xl" />
|
||||
<Icons.PieChartOutlined iconSize="xl" />
|
||||
<Icons.AreaChartOutlined iconSize="xl" />
|
||||
<Icons.DashboardOutlined iconSize="xl" />
|
||||
<Icons.FundProjectionScreenOutlined iconSize="xl" />
|
||||
</Section>
|
||||
<Section title="Data">
|
||||
<Icons.DatabaseOutlined iconSize="xl" />
|
||||
<Icons.TableOutlined iconSize="xl" />
|
||||
<Icons.ConsoleSqlOutlined iconSize="xl" />
|
||||
<Icons.FilterOutlined iconSize="xl" />
|
||||
<Icons.FieldNumberOutlined iconSize="xl" />
|
||||
<Icons.FieldTimeOutlined iconSize="xl" />
|
||||
<Icons.FunctionOutlined iconSize="xl" />
|
||||
<Icons.CalculatorOutlined iconSize="xl" />
|
||||
</Section>
|
||||
<Section title="Actions">
|
||||
<Icons.PlusOutlined iconSize="xl" />
|
||||
<Icons.EditOutlined iconSize="xl" />
|
||||
<Icons.DeleteOutlined iconSize="xl" />
|
||||
<Icons.CopyOutlined iconSize="xl" />
|
||||
<Icons.SaveOutlined iconSize="xl" />
|
||||
<Icons.DownloadOutlined iconSize="xl" />
|
||||
<Icons.UploadOutlined iconSize="xl" />
|
||||
<Icons.ReloadOutlined iconSize="xl" />
|
||||
<Icons.SyncOutlined iconSize="xl" />
|
||||
<Icons.SearchOutlined iconSize="xl" />
|
||||
<Icons.ExpandOutlined iconSize="xl" />
|
||||
<Icons.FullscreenOutlined iconSize="xl" />
|
||||
<Icons.ShareAltOutlined iconSize="xl" />
|
||||
<Icons.ExportOutlined iconSize="xl" />
|
||||
</Section>
|
||||
<Section title="Status">
|
||||
<Icons.CheckOutlined iconSize="xl" />
|
||||
<Icons.CheckCircleOutlined iconSize="xl" />
|
||||
<Icons.CloseOutlined iconSize="xl" />
|
||||
<Icons.CloseCircleOutlined iconSize="xl" />
|
||||
<Icons.InfoCircleOutlined iconSize="xl" />
|
||||
<Icons.WarningOutlined iconSize="xl" />
|
||||
<Icons.ExclamationCircleOutlined iconSize="xl" />
|
||||
<Icons.QuestionCircleOutlined iconSize="xl" />
|
||||
<Icons.LoadingOutlined iconSize="xl" />
|
||||
<Icons.StopOutlined iconSize="xl" />
|
||||
</Section>
|
||||
<Section title="Navigation">
|
||||
<Icons.MenuOutlined iconSize="xl" />
|
||||
<Icons.DownOutlined iconSize="xl" />
|
||||
<Icons.UpOutlined iconSize="xl" />
|
||||
<Icons.RightOutlined iconSize="xl" />
|
||||
<Icons.CaretDownOutlined iconSize="xl" />
|
||||
<Icons.CaretUpOutlined iconSize="xl" />
|
||||
<Icons.ArrowRightOutlined iconSize="xl" />
|
||||
<Icons.MoreOutlined iconSize="xl" />
|
||||
<Icons.EllipsisOutlined iconSize="xl" />
|
||||
</Section>
|
||||
<Section title="Objects">
|
||||
<Icons.FileOutlined iconSize="xl" />
|
||||
<Icons.FileTextOutlined iconSize="xl" />
|
||||
<Icons.FileImageOutlined iconSize="xl" />
|
||||
<Icons.BookOutlined iconSize="xl" />
|
||||
<Icons.TagOutlined iconSize="xl" />
|
||||
<Icons.TagsOutlined iconSize="xl" />
|
||||
<Icons.StarOutlined iconSize="xl" />
|
||||
<Icons.BellOutlined iconSize="xl" />
|
||||
<Icons.CalendarOutlined iconSize="xl" />
|
||||
<Icons.ClockCircleOutlined iconSize="xl" />
|
||||
<Icons.MailOutlined iconSize="xl" />
|
||||
<Icons.LinkOutlined iconSize="xl" />
|
||||
<Icons.LockOutlined iconSize="xl" />
|
||||
<Icons.UnlockOutlined iconSize="xl" />
|
||||
<Icons.KeyOutlined iconSize="xl" />
|
||||
</Section>
|
||||
<Section title="Users">
|
||||
<Icons.UserOutlined iconSize="xl" />
|
||||
<Icons.UserAddOutlined iconSize="xl" />
|
||||
<Icons.UsergroupAddOutlined iconSize="xl" />
|
||||
<Icons.LoginOutlined iconSize="xl" />
|
||||
</Section>
|
||||
<Section title="Settings">
|
||||
<Icons.SettingOutlined iconSize="xl" />
|
||||
<Icons.BgColorsOutlined iconSize="xl" />
|
||||
<Icons.FormatPainterOutlined iconSize="xl" />
|
||||
<Icons.HighlightOutlined iconSize="xl" />
|
||||
<Icons.EyeOutlined iconSize="xl" />
|
||||
<Icons.EyeInvisibleOutlined iconSize="xl" />
|
||||
<Icons.SunOutlined iconSize="xl" />
|
||||
<Icons.MoonOutlined iconSize="xl" />
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Icon with Text
|
||||
|
||||
```tsx live
|
||||
function IconWithText() {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Icons.CheckCircleOutlined iconSize="l" style={{ color: '#52c41a' }} />
|
||||
<span>Success message</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Icons.InfoCircleOutlined iconSize="l" style={{ color: '#1890ff' }} />
|
||||
<span>Information message</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Icons.WarningOutlined iconSize="l" style={{ color: '#faad14' }} />
|
||||
<span>Warning message</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Icons.CloseCircleOutlined iconSize="l" style={{ color: '#ff4d4f' }} />
|
||||
<span>Error message</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `iconSize` | `string` | `"xl"` | Size of the icons: s (12px), m (16px), l (20px), xl (24px), xxl (32px). |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Icons } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Icons/Icons.stories.tsx).
|
||||
:::
|
||||
100
docs/developer_docs/components/ui/icontooltip.mdx
Normal file
100
docs/developer_docs/components/ui/icontooltip.mdx
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: IconTooltip
|
||||
sidebar_label: IconTooltip
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# IconTooltip
|
||||
|
||||
The IconTooltip component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="IconTooltip"
|
||||
props={{
|
||||
tooltip: "Tooltip"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "tooltip",
|
||||
label: "Tooltip",
|
||||
type: "text",
|
||||
description: "Text content to display in the tooltip."
|
||||
},
|
||||
{
|
||||
name: "placement",
|
||||
label: "Placement",
|
||||
type: "select",
|
||||
options: [
|
||||
"bottom",
|
||||
"bottomLeft",
|
||||
"bottomRight",
|
||||
"left",
|
||||
"leftBottom",
|
||||
"leftTop",
|
||||
"right",
|
||||
"rightBottom",
|
||||
"rightTop",
|
||||
"top",
|
||||
"topLeft",
|
||||
"topRight"
|
||||
],
|
||||
description: "Position of the tooltip relative to the icon."
|
||||
}
|
||||
]}
|
||||
sampleChildren={[{"component":"Icons.InfoCircleOutlined","props":{"iconSize":"l"}}]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<IconTooltip tooltip="Helpful information">
|
||||
<Icons.InfoCircleOutlined iconSize="l" />
|
||||
</IconTooltip>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `tooltip` | `string` | `"Tooltip"` | Text content to display in the tooltip. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { IconTooltip } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/IconTooltip/IconTooltip.stories.tsx).
|
||||
:::
|
||||
77
docs/developer_docs/components/ui/index.mdx
Normal file
77
docs/developer_docs/components/ui/index.mdx
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Core Components
|
||||
sidebar_label: Core Components
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Core Components
|
||||
|
||||
46 components available in this category.
|
||||
|
||||
## Components
|
||||
|
||||
- [AutoComplete](./autocomplete)
|
||||
- [Avatar](./avatar)
|
||||
- [Badge](./badge)
|
||||
- [Breadcrumb](./breadcrumb)
|
||||
- [Button](./button)
|
||||
- [ButtonGroup](./buttongroup)
|
||||
- [CachedLabel](./cachedlabel)
|
||||
- [Card](./card)
|
||||
- [Checkbox](./checkbox)
|
||||
- [Collapse](./collapse)
|
||||
- [DatePicker](./datepicker)
|
||||
- [Divider](./divider)
|
||||
- [EditableTitle](./editabletitle)
|
||||
- [EmptyState](./emptystate)
|
||||
- [FaveStar](./favestar)
|
||||
- [IconButton](./iconbutton)
|
||||
- [Icons](./icons)
|
||||
- [IconTooltip](./icontooltip)
|
||||
- [InfoTooltip](./infotooltip)
|
||||
- [Input](./input)
|
||||
- [Label](./label)
|
||||
- [List](./list)
|
||||
- [ListViewCard](./listviewcard)
|
||||
- [Loading](./loading)
|
||||
- [Menu](./menu)
|
||||
- [Modal](./modal)
|
||||
- [ModalTrigger](./modaltrigger)
|
||||
- [Popover](./popover)
|
||||
- [ProgressBar](./progressbar)
|
||||
- [Radio](./radio)
|
||||
- [SafeMarkdown](./safemarkdown)
|
||||
- [Select](./select)
|
||||
- [Skeleton](./skeleton)
|
||||
- [Slider](./slider)
|
||||
- [Steps](./steps)
|
||||
- [Switch](./switch)
|
||||
- [TableCollection](./tablecollection)
|
||||
- [TableView](./tableview)
|
||||
- [Tabs](./tabs)
|
||||
- [Timer](./timer)
|
||||
- [Tooltip](./tooltip)
|
||||
- [Tree](./tree)
|
||||
- [TreeSelect](./treeselect)
|
||||
- [Typography](./typography)
|
||||
- [UnsavedChangesModal](./unsavedchangesmodal)
|
||||
- [Upload](./upload)
|
||||
106
docs/developer_docs/components/ui/infotooltip.mdx
Normal file
106
docs/developer_docs/components/ui/infotooltip.mdx
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: InfoTooltip
|
||||
sidebar_label: InfoTooltip
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# InfoTooltip
|
||||
|
||||
The InfoTooltip component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="InfoTooltip"
|
||||
props={{
|
||||
tooltip: "This is the text that will display!"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "tooltip",
|
||||
label: "Tooltip",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "placement",
|
||||
label: "Placement",
|
||||
type: "select",
|
||||
options: [
|
||||
"bottom",
|
||||
"left",
|
||||
"right",
|
||||
"top",
|
||||
"topLeft",
|
||||
"topRight",
|
||||
"bottomLeft",
|
||||
"bottomRight",
|
||||
"leftTop",
|
||||
"leftBottom",
|
||||
"rightTop",
|
||||
"rightBottom"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "trigger",
|
||||
label: "Trigger",
|
||||
type: "select",
|
||||
options: [
|
||||
"hover",
|
||||
"click"
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<InfoTooltip
|
||||
tooltip="This is the text that will display!"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `tooltip` | `string` | `"This is the text that will display!"` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { InfoTooltip } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/InfoTooltip/InfoTooltip.stories.tsx).
|
||||
:::
|
||||
162
docs/developer_docs/components/ui/input.mdx
Normal file
162
docs/developer_docs/components/ui/input.mdx
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: Input
|
||||
sidebar_label: Input
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Input
|
||||
|
||||
The Input component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Input"
|
||||
props={{
|
||||
allowClear: false,
|
||||
disabled: false,
|
||||
showCount: false,
|
||||
type: "text",
|
||||
variant: "outlined"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "allowClear",
|
||||
label: "Allow Clear",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "showCount",
|
||||
label: "Show Count",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
label: "Type",
|
||||
type: "select",
|
||||
options: [
|
||||
"text",
|
||||
"password",
|
||||
"email",
|
||||
"number",
|
||||
"tel",
|
||||
"url",
|
||||
"search"
|
||||
],
|
||||
description: "HTML input type"
|
||||
},
|
||||
{
|
||||
name: "variant",
|
||||
label: "Variant",
|
||||
type: "select",
|
||||
options: [
|
||||
"outlined",
|
||||
"borderless",
|
||||
"filled"
|
||||
],
|
||||
description: "Input style variant"
|
||||
},
|
||||
{
|
||||
name: "defaultValue",
|
||||
label: "Default Value",
|
||||
type: "text",
|
||||
description: "Default input value"
|
||||
},
|
||||
{
|
||||
name: "id",
|
||||
label: "ID",
|
||||
type: "text",
|
||||
description: "HTML id attribute"
|
||||
},
|
||||
{
|
||||
name: "maxLength",
|
||||
label: "Max Length",
|
||||
type: "number",
|
||||
description: "Maximum length of input"
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
"error",
|
||||
"warning"
|
||||
],
|
||||
description: "Validation status"
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"large",
|
||||
"middle",
|
||||
"small"
|
||||
],
|
||||
description: "Size of the input"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
variant="outlined"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `allowClear` | `boolean` | `false` | - |
|
||||
| `disabled` | `boolean` | `false` | - |
|
||||
| `showCount` | `boolean` | `false` | - |
|
||||
| `type` | `string` | `"text"` | HTML input type |
|
||||
| `variant` | `string` | `"outlined"` | Input style variant |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Input } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Input/Input.stories.tsx).
|
||||
:::
|
||||
105
docs/developer_docs/components/ui/label.mdx
Normal file
105
docs/developer_docs/components/ui/label.mdx
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Label
|
||||
sidebar_label: Label
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Label
|
||||
|
||||
The Label component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Label"
|
||||
props={{
|
||||
type: "default",
|
||||
children: "Label text",
|
||||
monospace: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "type",
|
||||
label: "Type",
|
||||
type: "select",
|
||||
options: [
|
||||
"default",
|
||||
"info",
|
||||
"success",
|
||||
"warning",
|
||||
"error",
|
||||
"primary"
|
||||
],
|
||||
description: "The visual style of the label."
|
||||
},
|
||||
{
|
||||
name: "children",
|
||||
label: "Children",
|
||||
type: "text",
|
||||
description: "The label text content."
|
||||
},
|
||||
{
|
||||
name: "monospace",
|
||||
label: "Monospace",
|
||||
type: "boolean",
|
||||
description: "Use monospace font."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Label
|
||||
type="default"
|
||||
>
|
||||
Label text
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `type` | `string` | `"default"` | The visual style of the label. |
|
||||
| `children` | `string` | `"Label text"` | The label text content. |
|
||||
| `monospace` | `boolean` | `false` | Use monospace font. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Label } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Label/Label.stories.tsx).
|
||||
:::
|
||||
117
docs/developer_docs/components/ui/list.mdx
Normal file
117
docs/developer_docs/components/ui/list.mdx
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
title: List
|
||||
sidebar_label: List
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# List
|
||||
|
||||
The List component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="List"
|
||||
props={{
|
||||
bordered: false,
|
||||
split: true,
|
||||
size: "default",
|
||||
loading: false,
|
||||
dataSource: [
|
||||
"Dashboard Analytics",
|
||||
"User Management",
|
||||
"Data Sources"
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "bordered",
|
||||
label: "Bordered",
|
||||
type: "boolean",
|
||||
description: "Whether to show a border around the list."
|
||||
},
|
||||
{
|
||||
name: "split",
|
||||
label: "Split",
|
||||
type: "boolean",
|
||||
description: "Whether to show a divider between items."
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"default",
|
||||
"small",
|
||||
"large"
|
||||
],
|
||||
description: "Size of the list."
|
||||
},
|
||||
{
|
||||
name: "loading",
|
||||
label: "Loading",
|
||||
type: "boolean",
|
||||
description: "Whether to show a loading indicator."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const data = ['Dashboard Analytics', 'User Management', 'Data Sources'];
|
||||
return (
|
||||
<List
|
||||
bordered
|
||||
dataSource={data}
|
||||
renderItem={(item) => <List.Item>{item}</List.Item>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `bordered` | `boolean` | `false` | Whether to show a border around the list. |
|
||||
| `split` | `boolean` | `true` | Whether to show a divider between items. |
|
||||
| `size` | `string` | `"default"` | Size of the list. |
|
||||
| `loading` | `boolean` | `false` | Whether to show a loading indicator. |
|
||||
| `dataSource` | `any` | `["Dashboard Analytics","User Management","Data Sources"]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { List } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/List/List.stories.tsx).
|
||||
:::
|
||||
132
docs/developer_docs/components/ui/listviewcard.mdx
Normal file
132
docs/developer_docs/components/ui/listviewcard.mdx
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
title: ListViewCard
|
||||
sidebar_label: ListViewCard
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# ListViewCard
|
||||
|
||||
ListViewCard is a card component used to display items in list views with an image, title, description, and optional cover sections.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="ListViewCard"
|
||||
props={{
|
||||
title: "Superset Card Title",
|
||||
loading: false,
|
||||
url: "/superset/dashboard/births/",
|
||||
imgURL: "https://picsum.photos/seed/superset/300/200",
|
||||
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit...",
|
||||
coverLeft: "Left Section",
|
||||
coverRight: "Right Section"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "Title displayed on the card."
|
||||
},
|
||||
{
|
||||
name: "loading",
|
||||
label: "Loading",
|
||||
type: "boolean",
|
||||
description: "Whether the card is in loading state."
|
||||
},
|
||||
{
|
||||
name: "url",
|
||||
label: "URL",
|
||||
type: "text",
|
||||
description: "URL the card links to."
|
||||
},
|
||||
{
|
||||
name: "imgURL",
|
||||
label: "Image URL",
|
||||
type: "text",
|
||||
description: "Primary image URL for the card."
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "text",
|
||||
description: "Description text displayed on the card."
|
||||
},
|
||||
{
|
||||
name: "coverLeft",
|
||||
label: "Cover Left",
|
||||
type: "text",
|
||||
description: "Content for the left section of the cover."
|
||||
},
|
||||
{
|
||||
name: "coverRight",
|
||||
label: "Cover Right",
|
||||
type: "text",
|
||||
description: "Content for the right section of the cover."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<ListViewCard
|
||||
title="Superset Card Title"
|
||||
url="/superset/dashboard/births/"
|
||||
imgURL="https://picsum.photos/seed/superset/300/200"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
|
||||
coverLeft="Left Section"
|
||||
coverRight="Right Section"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `title` | `string` | `"Superset Card Title"` | Title displayed on the card. |
|
||||
| `loading` | `boolean` | `false` | Whether the card is in loading state. |
|
||||
| `url` | `string` | `"/superset/dashboard/births/"` | URL the card links to. |
|
||||
| `imgURL` | `string` | `"https://picsum.photos/seed/superset/300/200"` | Primary image URL for the card. |
|
||||
| `description` | `string` | `"Lorem ipsum dolor sit amet, consectetur adipiscing elit..."` | Description text displayed on the card. |
|
||||
| `coverLeft` | `string` | `"Left Section"` | Content for the left section of the cover. |
|
||||
| `coverRight` | `string` | `"Right Section"` | Content for the right section of the cover. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { ListViewCard } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/ListViewCard/ListViewCard.stories.tsx).
|
||||
:::
|
||||
187
docs/developer_docs/components/ui/loading.mdx
Normal file
187
docs/developer_docs/components/ui/loading.mdx
Normal file
@@ -0,0 +1,187 @@
|
||||
---
|
||||
title: Loading
|
||||
sidebar_label: Loading
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Loading
|
||||
|
||||
The Loading component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Loading"
|
||||
props={{
|
||||
size: "m",
|
||||
position: "normal",
|
||||
muted: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"s",
|
||||
"m",
|
||||
"l"
|
||||
],
|
||||
description: "Size of the spinner: s (40px), m (70px), or l (100px)."
|
||||
},
|
||||
{
|
||||
name: "position",
|
||||
label: "Position",
|
||||
type: "select",
|
||||
options: [
|
||||
"normal",
|
||||
"floating",
|
||||
"inline"
|
||||
],
|
||||
description: "Position style: normal (inline flow), floating (overlay), or inline."
|
||||
},
|
||||
{
|
||||
name: "muted",
|
||||
label: "Muted",
|
||||
type: "boolean",
|
||||
description: "Whether to show a muted/subtle version of the spinner."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<div>
|
||||
{['normal', 'floating', 'inline'].map(position => (
|
||||
<div
|
||||
key={position}
|
||||
style={{
|
||||
marginBottom: 40,
|
||||
padding: 20,
|
||||
border: '1px solid #eee',
|
||||
position: 'relative',
|
||||
minHeight: 80,
|
||||
}}
|
||||
>
|
||||
<h4 style={{ marginTop: 0 }}>{position}</h4>
|
||||
<Loading position={position} size="m" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Size and Opacity Showcase
|
||||
|
||||
```tsx live
|
||||
function SizeShowcase() {
|
||||
const sizes = ['s', 'm', 'l'];
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 20, alignItems: 'center' }}>
|
||||
<div><strong>Size</strong></div>
|
||||
<div><strong>Normal</strong></div>
|
||||
<div><strong>Muted</strong></div>
|
||||
<div><strong>Usage</strong></div>
|
||||
{sizes.map(size => (
|
||||
<React.Fragment key={size}>
|
||||
<div style={{ fontWeight: 'bold' }}>
|
||||
{size.toUpperCase()} ({size === 's' ? '40px' : size === 'm' ? '70px' : '100px'})
|
||||
</div>
|
||||
<div style={{ textAlign: 'center', padding: 10, border: '1px solid #eee' }}>
|
||||
<Loading size={size} position="normal" />
|
||||
</div>
|
||||
<div style={{ textAlign: 'center', padding: 10, border: '1px solid #eee' }}>
|
||||
<Loading size={size} muted position="normal" />
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: '#666' }}>
|
||||
{size === 's' && 'Filter bars, inline'}
|
||||
{size === 'm' && 'Explore pages'}
|
||||
{size === 'l' && 'Full page loading'}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Contextual Examples
|
||||
|
||||
```tsx live
|
||||
function ContextualDemo() {
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<h4>Filter Bar (size="s", muted)</h4>
|
||||
<div style={{ height: 40, backgroundColor: '#f5f5f5', display: 'flex', alignItems: 'center', padding: '0 10px', gap: 10, marginBottom: 30 }}>
|
||||
<span>Filter 1:</span>
|
||||
<Loading size="s" muted position="normal" />
|
||||
<span>Filter 2:</span>
|
||||
<Loading size="s" muted position="normal" />
|
||||
</div>
|
||||
|
||||
<h4>Dashboard Grid (size="s", muted)</h4>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10, marginBottom: 30 }}>
|
||||
{[1, 2, 3].map(i => (
|
||||
<div key={i} style={{ height: 100, backgroundColor: '#fafafa', border: '1px solid #ddd', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Loading size="s" muted position="normal" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h4>Main Loading (size="l")</h4>
|
||||
<div style={{ height: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', border: '2px dashed #ccc' }}>
|
||||
<Loading size="l" position="normal" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `size` | `string` | `"m"` | Size of the spinner: s (40px), m (70px), or l (100px). |
|
||||
| `position` | `string` | `"normal"` | Position style: normal (inline flow), floating (overlay), or inline. |
|
||||
| `muted` | `boolean` | `false` | Whether to show a muted/subtle version of the spinner. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Loading } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Loading/Loading.stories.tsx).
|
||||
:::
|
||||
174
docs/developer_docs/components/ui/menu.mdx
Normal file
174
docs/developer_docs/components/ui/menu.mdx
Normal file
@@ -0,0 +1,174 @@
|
||||
---
|
||||
title: Menu
|
||||
sidebar_label: Menu
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Menu
|
||||
|
||||
Navigation menu component supporting horizontal, vertical, and inline modes. Based on Ant Design Menu with Superset styling.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Menu"
|
||||
props={{
|
||||
mode: "horizontal",
|
||||
selectable: true,
|
||||
items: [
|
||||
{
|
||||
label: "Dashboards",
|
||||
key: "dashboards"
|
||||
},
|
||||
{
|
||||
label: "Charts",
|
||||
key: "charts"
|
||||
},
|
||||
{
|
||||
label: "Datasets",
|
||||
key: "datasets"
|
||||
},
|
||||
{
|
||||
label: "SQL Lab",
|
||||
key: "sqllab"
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "mode",
|
||||
label: "Mode",
|
||||
type: "select",
|
||||
options: [
|
||||
"horizontal",
|
||||
"vertical",
|
||||
"inline"
|
||||
],
|
||||
description: "Menu display mode: horizontal navbar, vertical sidebar, or inline collapsible."
|
||||
},
|
||||
{
|
||||
name: "selectable",
|
||||
label: "Selectable",
|
||||
type: "boolean",
|
||||
description: "Whether menu items can be selected."
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
label: "Multiple",
|
||||
type: "boolean",
|
||||
description: "Allow multiple items to be selected."
|
||||
},
|
||||
{
|
||||
name: "inlineCollapsed",
|
||||
label: "Inline Collapsed",
|
||||
type: "boolean",
|
||||
description: "Whether the inline menu is collapsed (only applies to inline mode)."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
selectable
|
||||
items={[
|
||||
{ label: 'Dashboards', key: 'dashboards' },
|
||||
{ label: 'Charts', key: 'charts' },
|
||||
{ label: 'Datasets', key: 'datasets' },
|
||||
{ label: 'SQL Lab', key: 'sqllab' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Vertical Menu
|
||||
|
||||
```tsx live
|
||||
function VerticalMenu() {
|
||||
return (
|
||||
<Menu
|
||||
mode="vertical"
|
||||
style={{ width: 200 }}
|
||||
items={[
|
||||
{ label: 'Dashboards', key: 'dashboards' },
|
||||
{ label: 'Charts', key: 'charts' },
|
||||
{ label: 'Datasets', key: 'datasets' },
|
||||
{
|
||||
label: 'Settings',
|
||||
key: 'settings',
|
||||
children: [
|
||||
{ label: 'Profile', key: 'profile' },
|
||||
{ label: 'Preferences', key: 'preferences' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Menu with Icons
|
||||
|
||||
```tsx live
|
||||
function MenuWithIcons() {
|
||||
return (
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
items={[
|
||||
{ label: <><Icons.DashboardOutlined /> Dashboards</>, key: 'dashboards' },
|
||||
{ label: <><Icons.LineChartOutlined /> Charts</>, key: 'charts' },
|
||||
{ label: <><Icons.DatabaseOutlined /> Datasets</>, key: 'datasets' },
|
||||
{ label: <><Icons.ConsoleSqlOutlined /> SQL Lab</>, key: 'sqllab' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `mode` | `string` | `"horizontal"` | Menu display mode: horizontal navbar, vertical sidebar, or inline collapsible. |
|
||||
| `selectable` | `boolean` | `true` | Whether menu items can be selected. |
|
||||
| `items` | `any` | `[{"label":"Dashboards","key":"dashboards"},{"label":"Charts","key":"charts"},{"label":"Datasets","key":"datasets"},{"label":"SQL Lab","key":"sqllab"}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Menu } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Menu/Menu.stories.tsx).
|
||||
:::
|
||||
207
docs/developer_docs/components/ui/modal.mdx
Normal file
207
docs/developer_docs/components/ui/modal.mdx
Normal file
@@ -0,0 +1,207 @@
|
||||
---
|
||||
title: Modal
|
||||
sidebar_label: Modal
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Modal
|
||||
|
||||
Modal dialog component for displaying content that requires user attention or interaction. Supports customizable buttons, drag/resize, and confirmation dialogs.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Modal"
|
||||
props={{
|
||||
disablePrimaryButton: false,
|
||||
primaryButtonName: "Submit",
|
||||
primaryButtonStyle: "primary",
|
||||
show: false,
|
||||
title: "I'm a modal!",
|
||||
resizable: false,
|
||||
draggable: false,
|
||||
width: 500
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "disablePrimaryButton",
|
||||
label: "Disable Primary Button",
|
||||
type: "boolean",
|
||||
description: "Whether the primary button is disabled."
|
||||
},
|
||||
{
|
||||
name: "primaryButtonName",
|
||||
label: "Primary Button Name",
|
||||
type: "text",
|
||||
description: "Text for the primary action button."
|
||||
},
|
||||
{
|
||||
name: "primaryButtonStyle",
|
||||
label: "Primary Button Style",
|
||||
type: "select",
|
||||
options: [
|
||||
"primary",
|
||||
"secondary",
|
||||
"dashed",
|
||||
"danger",
|
||||
"link"
|
||||
],
|
||||
description: "The style of the primary action button."
|
||||
},
|
||||
{
|
||||
name: "show",
|
||||
label: "Show",
|
||||
type: "boolean",
|
||||
description: "Whether the modal is visible. Use the \"Try It\" example below for a working demo."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "Title displayed in the modal header."
|
||||
},
|
||||
{
|
||||
name: "resizable",
|
||||
label: "Resizable",
|
||||
type: "boolean",
|
||||
description: "Whether the modal can be resized by dragging corners."
|
||||
},
|
||||
{
|
||||
name: "draggable",
|
||||
label: "Draggable",
|
||||
type: "boolean",
|
||||
description: "Whether the modal can be dragged by its header."
|
||||
},
|
||||
{
|
||||
name: "width",
|
||||
label: "Width",
|
||||
type: "number",
|
||||
description: "Width of the modal in pixels."
|
||||
}
|
||||
]}
|
||||
triggerProp="show"
|
||||
onHideProp="onHide"
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function ModalDemo() {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setIsOpen(true)}>Open Modal</Button>
|
||||
<Modal
|
||||
show={isOpen}
|
||||
onHide={() => setIsOpen(false)}
|
||||
title="Example Modal"
|
||||
primaryButtonName="Submit"
|
||||
onHandledPrimaryAction={() => {
|
||||
alert('Submitted!');
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<p>This is the modal content. Click Submit or close the modal.</p>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Danger Modal
|
||||
|
||||
```tsx live
|
||||
function DangerModal() {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
return (
|
||||
<>
|
||||
<Button buttonStyle="danger" onClick={() => setIsOpen(true)}>Delete Item</Button>
|
||||
<Modal
|
||||
show={isOpen}
|
||||
onHide={() => setIsOpen(false)}
|
||||
title="Confirm Delete"
|
||||
primaryButtonName="Delete"
|
||||
primaryButtonStyle="danger"
|
||||
onHandledPrimaryAction={() => {
|
||||
alert('Deleted!');
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Confirmation Dialogs
|
||||
|
||||
```tsx live
|
||||
function ConfirmationDialogs() {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<Button onClick={() => Modal.confirm({
|
||||
title: 'Confirm Action',
|
||||
content: 'Are you sure you want to proceed?',
|
||||
okText: 'Yes',
|
||||
})}>Confirm</Button>
|
||||
<Button onClick={() => Modal.warning({
|
||||
title: 'Warning',
|
||||
content: 'This action may have consequences.',
|
||||
})}>Warning</Button>
|
||||
<Button onClick={() => Modal.error({
|
||||
title: 'Error',
|
||||
content: 'Something went wrong.',
|
||||
})}>Error</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `disablePrimaryButton` | `boolean` | `false` | Whether the primary button is disabled. |
|
||||
| `primaryButtonName` | `string` | `"Submit"` | Text for the primary action button. |
|
||||
| `primaryButtonStyle` | `string` | `"primary"` | The style of the primary action button. |
|
||||
| `show` | `boolean` | `false` | Whether the modal is visible. Use the "Try It" example below for a working demo. |
|
||||
| `title` | `string` | `"I'm a modal!"` | Title displayed in the modal header. |
|
||||
| `resizable` | `boolean` | `false` | Whether the modal can be resized by dragging corners. |
|
||||
| `draggable` | `boolean` | `false` | Whether the modal can be dragged by its header. |
|
||||
| `width` | `number` | `500` | Width of the modal in pixels. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Modal } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.stories.tsx).
|
||||
:::
|
||||
192
docs/developer_docs/components/ui/modaltrigger.mdx
Normal file
192
docs/developer_docs/components/ui/modaltrigger.mdx
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: ModalTrigger
|
||||
sidebar_label: ModalTrigger
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# ModalTrigger
|
||||
|
||||
A component that renders a trigger element which opens a modal when clicked. Useful for actions that need confirmation or additional input.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="ModalTrigger"
|
||||
props={{
|
||||
isButton: true,
|
||||
modalTitle: "Modal Title",
|
||||
modalBody: "This is the modal body content.",
|
||||
tooltip: "Click to open modal",
|
||||
width: "600px",
|
||||
maxWidth: "1000px",
|
||||
responsive: true,
|
||||
draggable: false,
|
||||
resizable: false,
|
||||
triggerNode: "Click to Open Modal"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "isButton",
|
||||
label: "Is Button",
|
||||
type: "boolean",
|
||||
description: "Whether to wrap the trigger in a button element."
|
||||
},
|
||||
{
|
||||
name: "modalTitle",
|
||||
label: "Modal Title",
|
||||
type: "text",
|
||||
description: "Title displayed in the modal header."
|
||||
},
|
||||
{
|
||||
name: "modalBody",
|
||||
label: "Modal Body",
|
||||
type: "text",
|
||||
description: "Content displayed in the modal body."
|
||||
},
|
||||
{
|
||||
name: "tooltip",
|
||||
label: "Tooltip",
|
||||
type: "text",
|
||||
description: "Tooltip text shown on hover over the trigger."
|
||||
},
|
||||
{
|
||||
name: "width",
|
||||
label: "Width",
|
||||
type: "text",
|
||||
description: "Width of the modal (e.g., \"600px\", \"80%\")."
|
||||
},
|
||||
{
|
||||
name: "maxWidth",
|
||||
label: "Max Width",
|
||||
type: "text",
|
||||
description: "Maximum width of the modal."
|
||||
},
|
||||
{
|
||||
name: "responsive",
|
||||
label: "Responsive",
|
||||
type: "boolean",
|
||||
description: "Whether the modal should be responsive."
|
||||
},
|
||||
{
|
||||
name: "draggable",
|
||||
label: "Draggable",
|
||||
type: "boolean",
|
||||
description: "Whether the modal can be dragged by its header."
|
||||
},
|
||||
{
|
||||
name: "resizable",
|
||||
label: "Resizable",
|
||||
type: "boolean",
|
||||
description: "Whether the modal can be resized by dragging corners."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<ModalTrigger
|
||||
isButton
|
||||
triggerNode={<span>Click to Open</span>}
|
||||
modalTitle="Example Modal"
|
||||
modalBody={<p>This is the modal content. You can put any React elements here.</p>}
|
||||
width="500px"
|
||||
responsive
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## With Custom Trigger
|
||||
|
||||
```tsx live
|
||||
function CustomTrigger() {
|
||||
return (
|
||||
<ModalTrigger
|
||||
triggerNode={
|
||||
<Button buttonStyle="primary">
|
||||
<Icons.PlusOutlined /> Add New Item
|
||||
</Button>
|
||||
}
|
||||
modalTitle="Add New Item"
|
||||
modalBody={
|
||||
<div>
|
||||
<p>Fill out the form to add a new item.</p>
|
||||
<Input placeholder="Item name" />
|
||||
</div>
|
||||
}
|
||||
width="400px"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Draggable & Resizable
|
||||
|
||||
```tsx live
|
||||
function DraggableModal() {
|
||||
return (
|
||||
<ModalTrigger
|
||||
isButton
|
||||
triggerNode={<span>Open Draggable Modal</span>}
|
||||
modalTitle="Draggable & Resizable"
|
||||
modalBody={<p>Try dragging the header or resizing from the corners!</p>}
|
||||
draggable
|
||||
resizable
|
||||
width="500px"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `isButton` | `boolean` | `true` | Whether to wrap the trigger in a button element. |
|
||||
| `modalTitle` | `string` | `"Modal Title"` | Title displayed in the modal header. |
|
||||
| `modalBody` | `string` | `"This is the modal body content."` | Content displayed in the modal body. |
|
||||
| `tooltip` | `string` | `"Click to open modal"` | Tooltip text shown on hover over the trigger. |
|
||||
| `width` | `string` | `"600px"` | Width of the modal (e.g., "600px", "80%"). |
|
||||
| `maxWidth` | `string` | `"1000px"` | Maximum width of the modal. |
|
||||
| `responsive` | `boolean` | `true` | Whether the modal should be responsive. |
|
||||
| `draggable` | `boolean` | `false` | Whether the modal can be dragged by its header. |
|
||||
| `resizable` | `boolean` | `false` | Whether the modal can be resized by dragging corners. |
|
||||
| `triggerNode` | `string` | `"Click to Open Modal"` | The clickable element that opens the modal when clicked. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { ModalTrigger } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/ModalTrigger/ModalTrigger.stories.tsx).
|
||||
:::
|
||||
199
docs/developer_docs/components/ui/popover.mdx
Normal file
199
docs/developer_docs/components/ui/popover.mdx
Normal file
@@ -0,0 +1,199 @@
|
||||
---
|
||||
title: Popover
|
||||
sidebar_label: Popover
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Popover
|
||||
|
||||
A floating card that appears when hovering or clicking a trigger element. Supports configurable placement, trigger behavior, and custom content.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Popover"
|
||||
props={{
|
||||
content: "Popover sample content",
|
||||
title: "Popover title",
|
||||
arrow: true,
|
||||
color: "#fff"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "content",
|
||||
label: "Content",
|
||||
type: "text",
|
||||
description: "Content displayed inside the popover body."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "Title displayed in the popover header."
|
||||
},
|
||||
{
|
||||
name: "arrow",
|
||||
label: "Arrow",
|
||||
type: "boolean",
|
||||
description: "Whether to show the popover's arrow pointing to the trigger."
|
||||
},
|
||||
{
|
||||
name: "color",
|
||||
label: "Color",
|
||||
type: "color",
|
||||
description: "The background color of the popover."
|
||||
},
|
||||
{
|
||||
name: "placement",
|
||||
label: "Placement",
|
||||
type: "select",
|
||||
options: [
|
||||
"topLeft",
|
||||
"top",
|
||||
"topRight",
|
||||
"leftTop",
|
||||
"left",
|
||||
"leftBottom",
|
||||
"rightTop",
|
||||
"right",
|
||||
"rightBottom",
|
||||
"bottomLeft",
|
||||
"bottom",
|
||||
"bottomRight"
|
||||
],
|
||||
description: "Position of the popover relative to the trigger element."
|
||||
},
|
||||
{
|
||||
name: "trigger",
|
||||
label: "Trigger",
|
||||
type: "select",
|
||||
options: [
|
||||
"hover",
|
||||
"click",
|
||||
"focus"
|
||||
],
|
||||
description: "Event that triggers the popover to appear."
|
||||
}
|
||||
]}
|
||||
sampleChildren={[{"component":"Button","props":{"children":"Hover me"}}]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Popover
|
||||
content="Popover sample content"
|
||||
title="Popover title"
|
||||
arrow
|
||||
>
|
||||
<Button>Hover me</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Click Trigger
|
||||
|
||||
```tsx live
|
||||
function ClickPopover() {
|
||||
return (
|
||||
<Popover
|
||||
content="This popover appears on click."
|
||||
title="Click Popover"
|
||||
trigger="click"
|
||||
>
|
||||
<Button>Click me</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Placements
|
||||
|
||||
```tsx live
|
||||
function PlacementsDemo() {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', justifyContent: 'center', padding: '60px 0' }}>
|
||||
{['top', 'right', 'bottom', 'left'].map(placement => (
|
||||
<Popover
|
||||
key={placement}
|
||||
content={`This popover is placed on the ${placement}`}
|
||||
title={placement}
|
||||
placement={placement}
|
||||
>
|
||||
<Button>{placement}</Button>
|
||||
</Popover>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Rich Content
|
||||
|
||||
```tsx live
|
||||
function RichPopover() {
|
||||
return (
|
||||
<Popover
|
||||
title="Dashboard Info"
|
||||
content={
|
||||
<div>
|
||||
<p><strong>Created by:</strong> Admin</p>
|
||||
<p><strong>Last modified:</strong> Jan 2025</p>
|
||||
<p><strong>Charts:</strong> 12</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button buttonStyle="primary">
|
||||
<Icons.InfoCircleOutlined /> View Details
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `content` | `string` | `"Popover sample content"` | Content displayed inside the popover body. |
|
||||
| `title` | `string` | `"Popover title"` | Title displayed in the popover header. |
|
||||
| `arrow` | `boolean` | `true` | Whether to show the popover's arrow pointing to the trigger. |
|
||||
| `color` | `string` | `"#fff"` | The background color of the popover. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Popover } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Popover/Popover.stories.tsx).
|
||||
:::
|
||||
206
docs/developer_docs/components/ui/progressbar.mdx
Normal file
206
docs/developer_docs/components/ui/progressbar.mdx
Normal file
@@ -0,0 +1,206 @@
|
||||
---
|
||||
title: ProgressBar
|
||||
sidebar_label: ProgressBar
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# ProgressBar
|
||||
|
||||
Progress bar component for displaying completion status. Supports line, circle, and dashboard display types.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="ProgressBar"
|
||||
props={{
|
||||
percent: 75,
|
||||
status: "normal",
|
||||
type: "line",
|
||||
striped: false,
|
||||
showInfo: true,
|
||||
strokeLinecap: "round"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "percent",
|
||||
label: "Percent",
|
||||
type: "number",
|
||||
description: "Completion percentage (0-100)."
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
"normal",
|
||||
"success",
|
||||
"exception",
|
||||
"active"
|
||||
],
|
||||
description: "Current status of the progress bar."
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
label: "Type",
|
||||
type: "select",
|
||||
options: [
|
||||
"line",
|
||||
"circle",
|
||||
"dashboard"
|
||||
],
|
||||
description: "Display type: line, circle, or dashboard gauge."
|
||||
},
|
||||
{
|
||||
name: "striped",
|
||||
label: "Striped",
|
||||
type: "boolean",
|
||||
description: "Whether to show striped animation on the bar."
|
||||
},
|
||||
{
|
||||
name: "showInfo",
|
||||
label: "Show Info",
|
||||
type: "boolean",
|
||||
description: "Whether to show the percentage text."
|
||||
},
|
||||
{
|
||||
name: "strokeLinecap",
|
||||
label: "Stroke Linecap",
|
||||
type: "select",
|
||||
options: [
|
||||
"round",
|
||||
"butt",
|
||||
"square"
|
||||
],
|
||||
description: "Shape of the progress bar endpoints."
|
||||
},
|
||||
{
|
||||
name: "strokeColor",
|
||||
label: "Stroke Color",
|
||||
type: "color",
|
||||
description: "Color of the progress bar fill."
|
||||
},
|
||||
{
|
||||
name: "trailColor",
|
||||
label: "Trail Color",
|
||||
type: "color",
|
||||
description: "Color of the unfilled portion."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<ProgressBar
|
||||
percent={75}
|
||||
status="normal"
|
||||
type="line"
|
||||
showInfo
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## All Progress Types
|
||||
|
||||
```tsx live
|
||||
function AllTypesDemo() {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 40, alignItems: 'center' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<h4>Line</h4>
|
||||
<ProgressBar percent={75} type="line" />
|
||||
</div>
|
||||
<div>
|
||||
<h4>Circle</h4>
|
||||
<ProgressBar percent={75} type="circle" />
|
||||
</div>
|
||||
<div>
|
||||
<h4>Dashboard</h4>
|
||||
<ProgressBar percent={75} type="dashboard" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Status Variants
|
||||
|
||||
```tsx live
|
||||
function StatusDemo() {
|
||||
const statuses = ['normal', 'success', 'exception', 'active'];
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{statuses.map(status => (
|
||||
<div key={status} style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<span style={{ width: 80 }}>{status}</span>
|
||||
<ProgressBar percent={75} status={status} type="line" style={{ flex: 1 }} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Colors
|
||||
|
||||
```tsx live
|
||||
function CustomColors() {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<ProgressBar percent={50} strokeColor="#1890ff" />
|
||||
<ProgressBar percent={70} strokeColor="#52c41a" />
|
||||
<ProgressBar percent={30} strokeColor="#faad14" trailColor="#f0f0f0" />
|
||||
<ProgressBar percent={90} strokeColor="#ff4d4f" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `percent` | `number` | `75` | Completion percentage (0-100). |
|
||||
| `status` | `string` | `"normal"` | Current status of the progress bar. |
|
||||
| `type` | `string` | `"line"` | Display type: line, circle, or dashboard gauge. |
|
||||
| `striped` | `boolean` | `false` | Whether to show striped animation on the bar. |
|
||||
| `showInfo` | `boolean` | `true` | Whether to show the percentage text. |
|
||||
| `strokeLinecap` | `string` | `"round"` | Shape of the progress bar endpoints. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { ProgressBar } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/ProgressBar/ProgressBar.stories.tsx).
|
||||
:::
|
||||
137
docs/developer_docs/components/ui/radio.mdx
Normal file
137
docs/developer_docs/components/ui/radio.mdx
Normal file
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: Radio
|
||||
sidebar_label: Radio
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Radio
|
||||
|
||||
Radio button component for selecting one option from a set. Supports standalone radio buttons, radio buttons styled as buttons, and grouped radio buttons with layout configuration.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Radio"
|
||||
props={{
|
||||
value: "radio1",
|
||||
disabled: false,
|
||||
checked: false,
|
||||
children: "Radio"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "value",
|
||||
label: "Value",
|
||||
type: "text",
|
||||
description: "The value associated with this radio button."
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Whether the radio button is disabled."
|
||||
},
|
||||
{
|
||||
name: "checked",
|
||||
label: "Checked",
|
||||
type: "boolean",
|
||||
description: "Whether the radio button is checked (controlled mode)."
|
||||
},
|
||||
{
|
||||
name: "children",
|
||||
label: "Children",
|
||||
type: "text",
|
||||
description: "Label text displayed next to the radio button."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Radio
|
||||
value="radio1"
|
||||
>
|
||||
Radio
|
||||
</Radio>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Radio Button Variants
|
||||
|
||||
```tsx live
|
||||
function RadioButtonDemo() {
|
||||
const [value, setValue] = React.useState('line');
|
||||
return (
|
||||
<Radio.Group value={value} onChange={e => setValue(e.target.value)}>
|
||||
<Radio.Button value="line">Line Chart</Radio.Button>
|
||||
<Radio.Button value="bar">Bar Chart</Radio.Button>
|
||||
<Radio.Button value="pie">Pie Chart</Radio.Button>
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Vertical Radio Group
|
||||
|
||||
```tsx live
|
||||
function VerticalDemo() {
|
||||
const [value, setValue] = React.useState('option1');
|
||||
return (
|
||||
<Radio.Group value={value} onChange={e => setValue(e.target.value)}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<Radio value="option1">First option</Radio>
|
||||
<Radio value="option2">Second option</Radio>
|
||||
<Radio value="option3">Third option</Radio>
|
||||
</div>
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `value` | `string` | `"radio1"` | The value associated with this radio button. |
|
||||
| `disabled` | `boolean` | `false` | Whether the radio button is disabled. |
|
||||
| `checked` | `boolean` | `false` | Whether the radio button is checked (controlled mode). |
|
||||
| `children` | `string` | `"Radio"` | Label text displayed next to the radio button. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Radio } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Radio/Radio.stories.tsx).
|
||||
:::
|
||||
85
docs/developer_docs/components/ui/safemarkdown.mdx
Normal file
85
docs/developer_docs/components/ui/safemarkdown.mdx
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: SafeMarkdown
|
||||
sidebar_label: SafeMarkdown
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# SafeMarkdown
|
||||
|
||||
The SafeMarkdown component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="SafeMarkdown"
|
||||
props={{
|
||||
htmlSanitization: true
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "htmlSanitization",
|
||||
label: "Html Sanitization",
|
||||
type: "boolean",
|
||||
description: "Enable HTML sanitization (recommended for user input)"
|
||||
},
|
||||
{
|
||||
name: "source",
|
||||
label: "Source",
|
||||
type: "text",
|
||||
description: "Markdown source string to render"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<SafeMarkdown
|
||||
htmlSanitization
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `htmlSanitization` | `boolean` | `true` | Enable HTML sanitization (recommended for user input) |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { SafeMarkdown } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown/SafeMarkdown.stories.tsx).
|
||||
:::
|
||||
308
docs/developer_docs/components/ui/select.mdx
Normal file
308
docs/developer_docs/components/ui/select.mdx
Normal file
@@ -0,0 +1,308 @@
|
||||
---
|
||||
title: Select
|
||||
sidebar_label: Select
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Select
|
||||
|
||||
A versatile select component supporting single and multi-select modes, search filtering, option creation, and both synchronous and asynchronous data sources.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Select"
|
||||
props={{
|
||||
mode: "single",
|
||||
placeholder: "Select ...",
|
||||
showSearch: true,
|
||||
allowNewOptions: false,
|
||||
allowClear: false,
|
||||
allowSelectAll: true,
|
||||
disabled: false,
|
||||
invertSelection: false,
|
||||
oneLine: false,
|
||||
maxTagCount: 4,
|
||||
options: [
|
||||
{
|
||||
label: "Such an incredibly awesome long long label",
|
||||
value: "long-label-1"
|
||||
},
|
||||
{
|
||||
label: "Another incredibly awesome long long label",
|
||||
value: "long-label-2"
|
||||
},
|
||||
{
|
||||
label: "Option A",
|
||||
value: "A"
|
||||
},
|
||||
{
|
||||
label: "Option B",
|
||||
value: "B"
|
||||
},
|
||||
{
|
||||
label: "Option C",
|
||||
value: "C"
|
||||
},
|
||||
{
|
||||
label: "Option D",
|
||||
value: "D"
|
||||
},
|
||||
{
|
||||
label: "Option E",
|
||||
value: "E"
|
||||
},
|
||||
{
|
||||
label: "Option F",
|
||||
value: "F"
|
||||
},
|
||||
{
|
||||
label: "Option G",
|
||||
value: "G"
|
||||
},
|
||||
{
|
||||
label: "Option H",
|
||||
value: "H"
|
||||
},
|
||||
{
|
||||
label: "Option I",
|
||||
value: "I"
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "mode",
|
||||
label: "Mode",
|
||||
type: "inline-radio",
|
||||
options: [
|
||||
"single",
|
||||
"multiple"
|
||||
],
|
||||
description: "Whether to allow selection of a single option or multiple."
|
||||
},
|
||||
{
|
||||
name: "placeholder",
|
||||
label: "Placeholder",
|
||||
type: "text",
|
||||
description: "Placeholder text when no option is selected."
|
||||
},
|
||||
{
|
||||
name: "showSearch",
|
||||
label: "Show Search",
|
||||
type: "boolean",
|
||||
description: "Whether to show a search input for filtering."
|
||||
},
|
||||
{
|
||||
name: "allowNewOptions",
|
||||
label: "Allow New Options",
|
||||
type: "boolean",
|
||||
description: "Whether users can create new options by typing a value not in the list."
|
||||
},
|
||||
{
|
||||
name: "allowClear",
|
||||
label: "Allow Clear",
|
||||
type: "boolean",
|
||||
description: "Whether to show a clear button to reset the selection."
|
||||
},
|
||||
{
|
||||
name: "allowSelectAll",
|
||||
label: "Allow Select All",
|
||||
type: "boolean",
|
||||
description: "Whether to show a \"Select All\" option in multiple mode."
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Whether the select is disabled."
|
||||
},
|
||||
{
|
||||
name: "invertSelection",
|
||||
label: "Invert Selection",
|
||||
type: "boolean",
|
||||
description: "Shows a stop icon instead of a checkmark on selected options, indicating deselection on click."
|
||||
},
|
||||
{
|
||||
name: "oneLine",
|
||||
label: "One Line",
|
||||
type: "boolean",
|
||||
description: "Forces tags onto one line with overflow count. Requires multiple mode."
|
||||
},
|
||||
{
|
||||
name: "maxTagCount",
|
||||
label: "Max Tag Count",
|
||||
type: "number",
|
||||
description: "Maximum number of tags to display in multiple mode before showing an overflow count."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<div style={{ width: 300 }}>
|
||||
<Select
|
||||
ariaLabel="demo-select"
|
||||
options={[
|
||||
{ label: 'Dashboards', value: 'dashboards' },
|
||||
{ label: 'Charts', value: 'charts' },
|
||||
{ label: 'Datasets', value: 'datasets' },
|
||||
{ label: 'SQL Lab', value: 'sqllab' },
|
||||
{ label: 'Settings', value: 'settings' },
|
||||
]}
|
||||
placeholder="Select ..."
|
||||
showSearch
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Multi Select
|
||||
|
||||
```tsx live
|
||||
function MultiSelectDemo() {
|
||||
return (
|
||||
<div style={{ width: 400 }}>
|
||||
<Select
|
||||
ariaLabel="multi-select"
|
||||
mode="multiple"
|
||||
options={[
|
||||
{ label: 'Dashboards', value: 'dashboards' },
|
||||
{ label: 'Charts', value: 'charts' },
|
||||
{ label: 'Datasets', value: 'datasets' },
|
||||
{ label: 'SQL Lab', value: 'sqllab' },
|
||||
{ label: 'Settings', value: 'settings' },
|
||||
]}
|
||||
placeholder="Select items..."
|
||||
allowSelectAll
|
||||
maxTagCount={3}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Allow New Options
|
||||
|
||||
```tsx live
|
||||
function AllowNewDemo() {
|
||||
return (
|
||||
<div style={{ width: 300 }}>
|
||||
<Select
|
||||
ariaLabel="allow-new-select"
|
||||
mode="multiple"
|
||||
options={[
|
||||
{ label: 'Red', value: 'red' },
|
||||
{ label: 'Green', value: 'green' },
|
||||
{ label: 'Blue', value: 'blue' },
|
||||
]}
|
||||
placeholder="Type to add tags..."
|
||||
allowNewOptions
|
||||
showSearch
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Inverted Selection
|
||||
|
||||
```tsx live
|
||||
function InvertedDemo() {
|
||||
return (
|
||||
<div style={{ width: 400 }}>
|
||||
<Select
|
||||
ariaLabel="inverted-select"
|
||||
mode="multiple"
|
||||
options={[
|
||||
{ label: 'Admin', value: 'admin' },
|
||||
{ label: 'Editor', value: 'editor' },
|
||||
{ label: 'Viewer', value: 'viewer' },
|
||||
{ label: 'Public', value: 'public' },
|
||||
]}
|
||||
placeholder="Exclude roles..."
|
||||
invertSelection
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## One Line Mode
|
||||
|
||||
```tsx live
|
||||
function OneLineDemo() {
|
||||
return (
|
||||
<div style={{ width: 300 }}>
|
||||
<Select
|
||||
ariaLabel="oneline-select"
|
||||
mode="multiple"
|
||||
options={[
|
||||
{ label: 'Dashboard 1', value: 'd1' },
|
||||
{ label: 'Dashboard 2', value: 'd2' },
|
||||
{ label: 'Dashboard 3', value: 'd3' },
|
||||
{ label: 'Dashboard 4', value: 'd4' },
|
||||
{ label: 'Dashboard 5', value: 'd5' },
|
||||
]}
|
||||
placeholder="Select dashboards..."
|
||||
oneLine
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `mode` | `string` | `"single"` | Whether to allow selection of a single option or multiple. |
|
||||
| `placeholder` | `string` | `"Select ..."` | Placeholder text when no option is selected. |
|
||||
| `showSearch` | `boolean` | `true` | Whether to show a search input for filtering. |
|
||||
| `allowNewOptions` | `boolean` | `false` | Whether users can create new options by typing a value not in the list. |
|
||||
| `allowClear` | `boolean` | `false` | Whether to show a clear button to reset the selection. |
|
||||
| `allowSelectAll` | `boolean` | `true` | Whether to show a "Select All" option in multiple mode. |
|
||||
| `disabled` | `boolean` | `false` | Whether the select is disabled. |
|
||||
| `invertSelection` | `boolean` | `false` | Shows a stop icon instead of a checkmark on selected options, indicating deselection on click. |
|
||||
| `oneLine` | `boolean` | `false` | Forces tags onto one line with overflow count. Requires multiple mode. |
|
||||
| `maxTagCount` | `number` | `4` | Maximum number of tags to display in multiple mode before showing an overflow count. |
|
||||
| `options` | `any` | `[{"label":"Such an incredibly awesome long long label","value":"long-label-1"},{"label":"Another incredibly awesome long long label","value":"long-label-2"},{"label":"Option A","value":"A"},{"label":"Option B","value":"B"},{"label":"Option C","value":"C"},{"label":"Option D","value":"D"},{"label":"Option E","value":"E"},{"label":"Option F","value":"F"},{"label":"Option G","value":"G"},{"label":"Option H","value":"H"},{"label":"Option I","value":"I"}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Select } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Select/Select.stories.tsx).
|
||||
:::
|
||||
140
docs/developer_docs/components/ui/skeleton.mdx
Normal file
140
docs/developer_docs/components/ui/skeleton.mdx
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
title: Skeleton
|
||||
sidebar_label: Skeleton
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Skeleton
|
||||
|
||||
Skeleton loading component with support for avatar, title, paragraph, button, and input placeholders.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Skeleton"
|
||||
props={{
|
||||
active: true,
|
||||
avatar: false,
|
||||
loading: true,
|
||||
title: true,
|
||||
shape: "circle",
|
||||
size: "default",
|
||||
block: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "active",
|
||||
label: "Active",
|
||||
type: "boolean",
|
||||
description: "Show animation effect."
|
||||
},
|
||||
{
|
||||
name: "avatar",
|
||||
label: "Avatar",
|
||||
type: "boolean",
|
||||
description: "Show avatar placeholder."
|
||||
},
|
||||
{
|
||||
name: "loading",
|
||||
label: "Loading",
|
||||
type: "boolean",
|
||||
description: "Display the skeleton when true."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "boolean",
|
||||
description: "Show title placeholder."
|
||||
},
|
||||
{
|
||||
name: "shape",
|
||||
label: "Shape",
|
||||
type: "select",
|
||||
options: [
|
||||
"circle",
|
||||
"square"
|
||||
],
|
||||
description: "Shape of the avatar/button skeleton."
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"large",
|
||||
"small",
|
||||
"default"
|
||||
],
|
||||
description: "Size of the skeleton elements."
|
||||
},
|
||||
{
|
||||
name: "block",
|
||||
label: "Block",
|
||||
type: "boolean",
|
||||
description: "Option to fit button width to its parent width."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Skeleton
|
||||
active
|
||||
loading
|
||||
title
|
||||
shape="circle"
|
||||
size="default"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `active` | `boolean` | `true` | Show animation effect. |
|
||||
| `avatar` | `boolean` | `false` | Show avatar placeholder. |
|
||||
| `loading` | `boolean` | `true` | Display the skeleton when true. |
|
||||
| `title` | `boolean` | `true` | Show title placeholder. |
|
||||
| `shape` | `string` | `"circle"` | Shape of the avatar/button skeleton. |
|
||||
| `size` | `string` | `"default"` | Size of the skeleton elements. |
|
||||
| `block` | `boolean` | `false` | Option to fit button width to its parent width. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Skeleton } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Skeleton/Skeleton.stories.tsx).
|
||||
:::
|
||||
253
docs/developer_docs/components/ui/slider.mdx
Normal file
253
docs/developer_docs/components/ui/slider.mdx
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: Slider
|
||||
sidebar_label: Slider
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Slider
|
||||
|
||||
A slider input for selecting a value or range from a continuous or stepped interval. Supports single value, range, vertical orientation, marks, and tooltip display.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Slider"
|
||||
props={{
|
||||
min: 0,
|
||||
max: 100,
|
||||
defaultValue: 70,
|
||||
step: 1,
|
||||
disabled: false,
|
||||
reverse: false,
|
||||
vertical: false,
|
||||
keyboard: true,
|
||||
dots: false,
|
||||
included: true
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "min",
|
||||
label: "Min",
|
||||
type: "number",
|
||||
description: "Minimum value of the slider."
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
label: "Max",
|
||||
type: "number",
|
||||
description: "Maximum value of the slider."
|
||||
},
|
||||
{
|
||||
name: "defaultValue",
|
||||
label: "Default Value",
|
||||
type: "number",
|
||||
description: "Initial value of the slider."
|
||||
},
|
||||
{
|
||||
name: "step",
|
||||
label: "Step",
|
||||
type: "number",
|
||||
description: "Step increment between values. Use null for marks-only mode."
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Whether the slider is disabled."
|
||||
},
|
||||
{
|
||||
name: "reverse",
|
||||
label: "Reverse",
|
||||
type: "boolean",
|
||||
description: "Whether to reverse the slider direction."
|
||||
},
|
||||
{
|
||||
name: "vertical",
|
||||
label: "Vertical",
|
||||
type: "boolean",
|
||||
description: "Whether to display the slider vertically."
|
||||
},
|
||||
{
|
||||
name: "keyboard",
|
||||
label: "Keyboard",
|
||||
type: "boolean",
|
||||
description: "Whether keyboard arrow keys can control the slider."
|
||||
},
|
||||
{
|
||||
name: "dots",
|
||||
label: "Dots",
|
||||
type: "boolean",
|
||||
description: "Whether to show dots at each step mark."
|
||||
},
|
||||
{
|
||||
name: "included",
|
||||
label: "Included",
|
||||
type: "boolean",
|
||||
description: "Whether to highlight the filled portion of the track."
|
||||
},
|
||||
{
|
||||
name: "tooltipOpen",
|
||||
label: "Tooltip Open",
|
||||
type: "boolean",
|
||||
description: "Whether the value tooltip is always visible."
|
||||
},
|
||||
{
|
||||
name: "tooltipPosition",
|
||||
label: "Tooltip Position",
|
||||
type: "select",
|
||||
options: [
|
||||
"top",
|
||||
"left",
|
||||
"bottom",
|
||||
"right",
|
||||
"topLeft",
|
||||
"topRight",
|
||||
"bottomLeft",
|
||||
"bottomRight",
|
||||
"leftTop",
|
||||
"leftBottom",
|
||||
"rightTop",
|
||||
"rightBottom"
|
||||
],
|
||||
description: "Position of the value tooltip relative to the handle."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<div style={{ width: 400, padding: '20px 0' }}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={100}
|
||||
defaultValue={70}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Range Slider
|
||||
|
||||
```tsx live
|
||||
function RangeSliderDemo() {
|
||||
return (
|
||||
<div style={{ width: 400, padding: '20px 0' }}>
|
||||
<h4>Basic Range</h4>
|
||||
<Slider range defaultValue={[20, 70]} min={0} max={100} />
|
||||
<br />
|
||||
<h4>Draggable Track</h4>
|
||||
<Slider range={{ draggableTrack: true }} defaultValue={[30, 60]} min={0} max={100} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## With Marks
|
||||
|
||||
```tsx live
|
||||
function MarksDemo() {
|
||||
return (
|
||||
<div style={{ width: 400, padding: '20px 0' }}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={100}
|
||||
defaultValue={37}
|
||||
marks={{
|
||||
0: '0°C',
|
||||
25: '25°C',
|
||||
50: '50°C',
|
||||
75: '75°C',
|
||||
100: '100°C',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Stepped and Dots
|
||||
|
||||
```tsx live
|
||||
function SteppedDemo() {
|
||||
return (
|
||||
<div style={{ width: 400, padding: '20px 0' }}>
|
||||
<h4>Step = 10 with Dots</h4>
|
||||
<Slider min={0} max={100} defaultValue={30} step={10} dots />
|
||||
<br />
|
||||
<h4>Step = 25</h4>
|
||||
<Slider min={0} max={100} defaultValue={50} step={25} dots
|
||||
marks={{ 0: '0', 25: '25', 50: '50', 75: '75', 100: '100' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Vertical Slider
|
||||
|
||||
```tsx live
|
||||
function VerticalDemo() {
|
||||
return (
|
||||
<div style={{ height: 300, display: 'flex', gap: 40, padding: '0 40px' }}>
|
||||
<Slider vertical defaultValue={30} />
|
||||
<Slider vertical range defaultValue={[20, 60]} />
|
||||
<Slider vertical defaultValue={50} dots step={10}
|
||||
marks={{ 0: '0', 50: '50', 100: '100' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `min` | `number` | `0` | Minimum value of the slider. |
|
||||
| `max` | `number` | `100` | Maximum value of the slider. |
|
||||
| `defaultValue` | `number` | `70` | Initial value of the slider. |
|
||||
| `step` | `number` | `1` | Step increment between values. Use null for marks-only mode. |
|
||||
| `disabled` | `boolean` | `false` | Whether the slider is disabled. |
|
||||
| `reverse` | `boolean` | `false` | Whether to reverse the slider direction. |
|
||||
| `vertical` | `boolean` | `false` | Whether to display the slider vertically. |
|
||||
| `keyboard` | `boolean` | `true` | Whether keyboard arrow keys can control the slider. |
|
||||
| `dots` | `boolean` | `false` | Whether to show dots at each step mark. |
|
||||
| `included` | `boolean` | `true` | Whether to highlight the filled portion of the track. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Slider } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Slider/Slider.stories.tsx).
|
||||
:::
|
||||
272
docs/developer_docs/components/ui/steps.mdx
Normal file
272
docs/developer_docs/components/ui/steps.mdx
Normal file
@@ -0,0 +1,272 @@
|
||||
---
|
||||
title: Steps
|
||||
sidebar_label: Steps
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Steps
|
||||
|
||||
A navigation component for guiding users through multi-step workflows. Supports horizontal, vertical, and inline layouts with progress tracking.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Steps"
|
||||
props={{
|
||||
direction: "horizontal",
|
||||
current: 1,
|
||||
labelPlacement: "horizontal",
|
||||
progressDot: false,
|
||||
size: "default",
|
||||
status: "process",
|
||||
type: "default",
|
||||
title: "Step 3",
|
||||
description: "Description 3",
|
||||
items: [
|
||||
{
|
||||
title: "Connect Database",
|
||||
description: "Configure the connection"
|
||||
},
|
||||
{
|
||||
title: "Create Dataset",
|
||||
description: "Select tables and columns"
|
||||
},
|
||||
{
|
||||
title: "Build Chart",
|
||||
description: "Choose visualization type"
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "direction",
|
||||
label: "Direction",
|
||||
type: "select",
|
||||
options: [
|
||||
"horizontal",
|
||||
"vertical"
|
||||
],
|
||||
description: "Layout direction of the steps."
|
||||
},
|
||||
{
|
||||
name: "current",
|
||||
label: "Current",
|
||||
type: "number",
|
||||
description: "Index of the current step (zero-based)."
|
||||
},
|
||||
{
|
||||
name: "labelPlacement",
|
||||
label: "Label Placement",
|
||||
type: "select",
|
||||
options: [
|
||||
"horizontal",
|
||||
"vertical"
|
||||
],
|
||||
description: "Position of step labels relative to the step icon."
|
||||
},
|
||||
{
|
||||
name: "progressDot",
|
||||
label: "Progress Dot",
|
||||
type: "boolean",
|
||||
description: "Whether to use a dot style instead of numbered icons."
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"default",
|
||||
"small"
|
||||
],
|
||||
description: "Size of the step icons and text."
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
"wait",
|
||||
"process",
|
||||
"finish",
|
||||
"error"
|
||||
],
|
||||
description: "Status of the current step."
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
label: "Type",
|
||||
type: "select",
|
||||
options: [
|
||||
"default",
|
||||
"navigation",
|
||||
"inline"
|
||||
],
|
||||
description: "Visual style: default numbered, navigation breadcrumb, or inline compact."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "Description",
|
||||
type: "text"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Steps
|
||||
current={1}
|
||||
items={[
|
||||
{ title: 'Connect Database', description: 'Configure the connection' },
|
||||
{ title: 'Create Dataset', description: 'Select tables and columns' },
|
||||
{ title: 'Build Chart', description: 'Choose visualization type' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Vertical Steps
|
||||
|
||||
```tsx live
|
||||
function VerticalSteps() {
|
||||
return (
|
||||
<Steps
|
||||
direction="vertical"
|
||||
current={1}
|
||||
items={[
|
||||
{ title: 'Upload CSV', description: 'Select a file from your computer' },
|
||||
{ title: 'Configure Columns', description: 'Set data types and names' },
|
||||
{ title: 'Review', description: 'Verify the data looks correct' },
|
||||
{ title: 'Import', description: 'Save the dataset' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Status Indicators
|
||||
|
||||
```tsx live
|
||||
function StatusSteps() {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||
<div>
|
||||
<h4>Error on Step 2</h4>
|
||||
<Steps
|
||||
current={1}
|
||||
status="error"
|
||||
items={[
|
||||
{ title: 'Connection', description: 'Configured' },
|
||||
{ title: 'Validation', description: 'Failed to validate' },
|
||||
{ title: 'Complete' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4>All Complete</h4>
|
||||
<Steps
|
||||
current={3}
|
||||
items={[
|
||||
{ title: 'Step 1' },
|
||||
{ title: 'Step 2' },
|
||||
{ title: 'Step 3' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Dot Style and Small Size
|
||||
|
||||
```tsx live
|
||||
function DotAndSmall() {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||
<div>
|
||||
<h4>Progress Dots</h4>
|
||||
<Steps
|
||||
progressDot
|
||||
current={1}
|
||||
items={[
|
||||
{ title: 'Create', description: 'Define the resource' },
|
||||
{ title: 'Configure', description: 'Set parameters' },
|
||||
{ title: 'Deploy', description: 'Go live' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Small Size</h4>
|
||||
<Steps
|
||||
size="small"
|
||||
current={2}
|
||||
items={[
|
||||
{ title: 'Login' },
|
||||
{ title: 'Verify' },
|
||||
{ title: 'Done' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `direction` | `string` | `"horizontal"` | Layout direction of the steps. |
|
||||
| `current` | `number` | `1` | Index of the current step (zero-based). |
|
||||
| `labelPlacement` | `string` | `"horizontal"` | Position of step labels relative to the step icon. |
|
||||
| `progressDot` | `boolean` | `false` | Whether to use a dot style instead of numbered icons. |
|
||||
| `size` | `string` | `"default"` | Size of the step icons and text. |
|
||||
| `status` | `string` | `"process"` | Status of the current step. |
|
||||
| `type` | `string` | `"default"` | Visual style: default numbered, navigation breadcrumb, or inline compact. |
|
||||
| `title` | `string` | `"Step 3"` | - |
|
||||
| `description` | `string` | `"Description 3"` | - |
|
||||
| `items` | `any` | `[{"title":"Connect Database","description":"Configure the connection"},{"title":"Create Dataset","description":"Select tables and columns"},{"title":"Build Chart","description":"Choose visualization type"}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Steps } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Steps/Steps.stories.tsx).
|
||||
:::
|
||||
193
docs/developer_docs/components/ui/switch.mdx
Normal file
193
docs/developer_docs/components/ui/switch.mdx
Normal file
@@ -0,0 +1,193 @@
|
||||
---
|
||||
title: Switch
|
||||
sidebar_label: Switch
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Switch
|
||||
|
||||
A toggle switch for boolean on/off states. Supports loading indicators, sizing, and an HTML title attribute for accessibility tooltips.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Switch"
|
||||
props={{
|
||||
disabled: false,
|
||||
loading: false,
|
||||
title: "Toggle feature"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Whether the switch is disabled."
|
||||
},
|
||||
{
|
||||
name: "loading",
|
||||
label: "Loading",
|
||||
type: "boolean",
|
||||
description: "Whether to show a loading spinner inside the switch."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "HTML title attribute shown as a browser tooltip on hover. Useful for accessibility."
|
||||
},
|
||||
{
|
||||
name: "checked",
|
||||
label: "Checked",
|
||||
type: "boolean",
|
||||
description: "Whether the switch is on."
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "radio",
|
||||
options: [
|
||||
"small",
|
||||
"default"
|
||||
],
|
||||
description: "Size of the switch."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const [checked, setChecked] = React.useState(true);
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Switch
|
||||
checked={checked}
|
||||
onChange={setChecked}
|
||||
title="Toggle feature"
|
||||
/>
|
||||
<span>{checked ? 'On' : 'Off'}</span>
|
||||
<span style={{ color: '#999', fontSize: 12 }}>(hover the switch to see the title tooltip)</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Switch States
|
||||
|
||||
```tsx live
|
||||
function SwitchStates() {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Switch defaultChecked title="Enabled switch" />
|
||||
<span>Checked</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Switch title="Unchecked switch" />
|
||||
<span>Unchecked</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Switch disabled defaultChecked title="Disabled on" />
|
||||
<span>Disabled (on)</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Switch disabled title="Disabled off" />
|
||||
<span>Disabled (off)</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Switch loading defaultChecked title="Loading switch" />
|
||||
<span>Loading</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Sizes
|
||||
|
||||
```tsx live
|
||||
function SizesDemo() {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 24 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Switch size="small" defaultChecked title="Small switch" />
|
||||
<span>Small</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Switch size="default" defaultChecked title="Default switch" />
|
||||
<span>Default</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Settings Panel
|
||||
|
||||
```tsx live
|
||||
function SettingsPanel() {
|
||||
const [notifications, setNotifications] = React.useState(true);
|
||||
const [darkMode, setDarkMode] = React.useState(false);
|
||||
const [autoRefresh, setAutoRefresh] = React.useState(true);
|
||||
return (
|
||||
<div style={{ maxWidth: 320, border: '1px solid #e8e8e8', borderRadius: 8, padding: 16 }}>
|
||||
<h4 style={{ marginTop: 0 }}>Dashboard Settings</h4>
|
||||
{[
|
||||
{ label: 'Email notifications', checked: notifications, onChange: setNotifications, title: 'Toggle email notifications' },
|
||||
{ label: 'Dark mode', checked: darkMode, onChange: setDarkMode, title: 'Toggle dark mode' },
|
||||
{ label: 'Auto-refresh data', checked: autoRefresh, onChange: setAutoRefresh, title: 'Toggle auto-refresh' },
|
||||
].map(({ label, checked, onChange, title }) => (
|
||||
<div key={label} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 0', borderBottom: '1px solid #f0f0f0' }}>
|
||||
<span>{label}</span>
|
||||
<Switch checked={checked} onChange={onChange} title={title} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `disabled` | `boolean` | `false` | Whether the switch is disabled. |
|
||||
| `loading` | `boolean` | `false` | Whether to show a loading spinner inside the switch. |
|
||||
| `title` | `string` | `"Toggle feature"` | HTML title attribute shown as a browser tooltip on hover. Useful for accessibility. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Switch } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Switch/Switch.stories.tsx).
|
||||
:::
|
||||
66
docs/developer_docs/components/ui/tablecollection.mdx
Normal file
66
docs/developer_docs/components/ui/tablecollection.mdx
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: TableCollection
|
||||
sidebar_label: TableCollection
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# TableCollection
|
||||
|
||||
The TableCollection component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="TableCollection"
|
||||
props={{}}
|
||||
controls={[]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<TableCollection
|
||||
// Add props here
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { TableCollection } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/TableCollection/TableCollection.stories.tsx).
|
||||
:::
|
||||
294
docs/developer_docs/components/ui/tableview.mdx
Normal file
294
docs/developer_docs/components/ui/tableview.mdx
Normal file
@@ -0,0 +1,294 @@
|
||||
---
|
||||
title: TableView
|
||||
sidebar_label: TableView
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# TableView
|
||||
|
||||
A data table component with sorting, pagination, text wrapping, and empty state support. Built on react-table.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="TableView"
|
||||
props={{
|
||||
accessor: "summary",
|
||||
Header: "Summary",
|
||||
sortable: true,
|
||||
id: 456,
|
||||
age: 10,
|
||||
name: "John Smith",
|
||||
summary: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam id porta neque, a vehicula orci. Maecenas rhoncus elit sit amet purus convallis placerat in at nunc. Nulla nec viverra augue.",
|
||||
noDataText: "No data here",
|
||||
pageSize: 2,
|
||||
showRowCount: true,
|
||||
withPagination: true,
|
||||
scrollTopOnPagination: false,
|
||||
columns: [
|
||||
{
|
||||
accessor: "id",
|
||||
Header: "ID",
|
||||
sortable: true,
|
||||
id: "id"
|
||||
},
|
||||
{
|
||||
accessor: "age",
|
||||
Header: "Age",
|
||||
id: "age"
|
||||
},
|
||||
{
|
||||
accessor: "name",
|
||||
Header: "Name",
|
||||
id: "name"
|
||||
},
|
||||
{
|
||||
accessor: "summary",
|
||||
Header: "Summary",
|
||||
id: "summary"
|
||||
}
|
||||
],
|
||||
data: [
|
||||
{
|
||||
id: 123,
|
||||
age: 27,
|
||||
name: "Emily",
|
||||
summary: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
id: 321,
|
||||
age: 10,
|
||||
name: "Kate",
|
||||
summary: "Nam id porta neque, a vehicula orci."
|
||||
},
|
||||
{
|
||||
id: 456,
|
||||
age: 10,
|
||||
name: "John Smith",
|
||||
summary: "Maecenas rhoncus elit sit amet purus convallis placerat."
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "accessor",
|
||||
label: "Accessor",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "Header",
|
||||
label: "Header",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "sortable",
|
||||
label: "Sortable",
|
||||
type: "boolean"
|
||||
},
|
||||
{
|
||||
name: "id",
|
||||
label: "ID",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "age",
|
||||
label: "Age",
|
||||
type: "number"
|
||||
},
|
||||
{
|
||||
name: "name",
|
||||
label: "Name",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "summary",
|
||||
label: "Summary",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "noDataText",
|
||||
label: "No Data Text",
|
||||
type: "text",
|
||||
description: "Text displayed when the table has no data."
|
||||
},
|
||||
{
|
||||
name: "pageSize",
|
||||
label: "Page Size",
|
||||
type: "number",
|
||||
description: "Number of rows displayed per page."
|
||||
},
|
||||
{
|
||||
name: "showRowCount",
|
||||
label: "Show Row Count",
|
||||
type: "boolean",
|
||||
description: "Whether to display the total row count alongside pagination."
|
||||
},
|
||||
{
|
||||
name: "withPagination",
|
||||
label: "With Pagination",
|
||||
type: "boolean",
|
||||
description: "Whether to show pagination controls below the table."
|
||||
},
|
||||
{
|
||||
name: "scrollTopOnPagination",
|
||||
label: "Scroll Top On Pagination",
|
||||
type: "boolean",
|
||||
description: "Whether to scroll to the top of the table when changing pages."
|
||||
},
|
||||
{
|
||||
name: "emptyWrapperType",
|
||||
label: "Empty Wrapper Type",
|
||||
type: "select",
|
||||
description: "Style of the empty state wrapper."
|
||||
},
|
||||
{
|
||||
name: "initialPageIndex",
|
||||
label: "Initial Page Index",
|
||||
type: "number",
|
||||
description: "Initial page to display (zero-based)."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<TableView
|
||||
columns={[
|
||||
{ accessor: 'id', Header: 'ID', sortable: true, id: 'id' },
|
||||
{ accessor: 'age', Header: 'Age', id: 'age' },
|
||||
{ accessor: 'name', Header: 'Name', id: 'name' },
|
||||
{ accessor: 'summary', Header: 'Summary', id: 'summary' },
|
||||
]}
|
||||
data={[
|
||||
{ id: 123, age: 27, name: 'Emily', summary: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' },
|
||||
{ id: 321, age: 10, name: 'Kate', summary: 'Nam id porta neque, a vehicula orci.' },
|
||||
{ id: 456, age: 10, name: 'John Smith', summary: 'Maecenas rhoncus elit sit amet purus convallis placerat.' },
|
||||
]}
|
||||
initialSortBy={[{ id: 'name', desc: true }]}
|
||||
pageSize={2}
|
||||
withPagination
|
||||
showRowCount
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Without Pagination
|
||||
|
||||
```tsx live
|
||||
function NoPaginationDemo() {
|
||||
return (
|
||||
<TableView
|
||||
columns={[
|
||||
{ accessor: 'name', Header: 'Name', id: 'name' },
|
||||
{ accessor: 'email', Header: 'Email', id: 'email' },
|
||||
{ accessor: 'status', Header: 'Status', id: 'status' },
|
||||
]}
|
||||
data={[
|
||||
{ name: 'Alice', email: 'alice@example.com', status: 'Active' },
|
||||
{ name: 'Bob', email: 'bob@example.com', status: 'Inactive' },
|
||||
{ name: 'Charlie', email: 'charlie@example.com', status: 'Active' },
|
||||
]}
|
||||
withPagination={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Empty State
|
||||
|
||||
```tsx live
|
||||
function EmptyDemo() {
|
||||
return (
|
||||
<TableView
|
||||
columns={[
|
||||
{ accessor: 'name', Header: 'Name', id: 'name' },
|
||||
{ accessor: 'value', Header: 'Value', id: 'value' },
|
||||
]}
|
||||
data={[]}
|
||||
noDataText="No results found"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## With Sorting
|
||||
|
||||
```tsx live
|
||||
function SortingDemo() {
|
||||
return (
|
||||
<TableView
|
||||
columns={[
|
||||
{ accessor: 'id', Header: 'ID', id: 'id', sortable: true },
|
||||
{ accessor: 'name', Header: 'Name', id: 'name', sortable: true },
|
||||
{ accessor: 'score', Header: 'Score', id: 'score', sortable: true },
|
||||
]}
|
||||
data={[
|
||||
{ id: 1, name: 'Dashboard A', score: 95 },
|
||||
{ id: 2, name: 'Dashboard B', score: 72 },
|
||||
{ id: 3, name: 'Dashboard C', score: 88 },
|
||||
{ id: 4, name: 'Dashboard D', score: 64 },
|
||||
]}
|
||||
initialSortBy={[{ id: 'score', desc: true }]}
|
||||
withPagination={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `accessor` | `string` | `"summary"` | - |
|
||||
| `Header` | `string` | `"Summary"` | - |
|
||||
| `sortable` | `boolean` | `true` | - |
|
||||
| `id` | `number` | `456` | - |
|
||||
| `age` | `number` | `10` | - |
|
||||
| `name` | `string` | `"John Smith"` | - |
|
||||
| `summary` | `string` | `"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam id porta neque, a vehicula orci. Maecenas rhoncus elit sit amet purus convallis placerat in at nunc. Nulla nec viverra augue."` | - |
|
||||
| `noDataText` | `string` | `"No data here"` | Text displayed when the table has no data. |
|
||||
| `pageSize` | `number` | `2` | Number of rows displayed per page. |
|
||||
| `showRowCount` | `boolean` | `true` | Whether to display the total row count alongside pagination. |
|
||||
| `withPagination` | `boolean` | `true` | Whether to show pagination controls below the table. |
|
||||
| `scrollTopOnPagination` | `boolean` | `false` | Whether to scroll to the top of the table when changing pages. |
|
||||
| `columns` | `any` | `[{"accessor":"id","Header":"ID","sortable":true,"id":"id"},{"accessor":"age","Header":"Age","id":"age"},{"accessor":"name","Header":"Name","id":"name"},{"accessor":"summary","Header":"Summary","id":"summary"}]` | - |
|
||||
| `data` | `any` | `[{"id":123,"age":27,"name":"Emily","summary":"Lorem ipsum dolor sit amet, consectetur adipiscing elit."},{"id":321,"age":10,"name":"Kate","summary":"Nam id porta neque, a vehicula orci."},{"id":456,"age":10,"name":"John Smith","summary":"Maecenas rhoncus elit sit amet purus convallis placerat."}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { TableView } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/TableView/TableView.stories.tsx).
|
||||
:::
|
||||
223
docs/developer_docs/components/ui/tabs.mdx
Normal file
223
docs/developer_docs/components/ui/tabs.mdx
Normal file
@@ -0,0 +1,223 @@
|
||||
---
|
||||
title: Tabs
|
||||
sidebar_label: Tabs
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Tabs
|
||||
|
||||
A tabs component for switching between different views or content sections. Supports multiple tab styles, positions, and sizes.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Tabs"
|
||||
props={{
|
||||
defaultActiveKey: "1",
|
||||
type: "line",
|
||||
tabPosition: "top",
|
||||
size: "middle",
|
||||
animated: true,
|
||||
centered: false,
|
||||
tabBarGutter: 8,
|
||||
items: [
|
||||
{
|
||||
key: "1",
|
||||
label: "Tab 1",
|
||||
children: "Content of Tab Pane 1"
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: "Tab 2",
|
||||
children: "Content of Tab Pane 2"
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
label: "Tab 3",
|
||||
children: "Content of Tab Pane 3"
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "defaultActiveKey",
|
||||
label: "Default Active Key",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
label: "Type",
|
||||
type: "inline-radio",
|
||||
options: [
|
||||
"line",
|
||||
"card",
|
||||
"editable-card"
|
||||
],
|
||||
description: "The style of tabs. Options: line, card, editable-card."
|
||||
},
|
||||
{
|
||||
name: "tabPosition",
|
||||
label: "Tab Position",
|
||||
type: "inline-radio",
|
||||
options: [
|
||||
"top",
|
||||
"bottom",
|
||||
"left",
|
||||
"right"
|
||||
],
|
||||
description: "Position of tabs. Options: top, bottom, left, right."
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "inline-radio",
|
||||
options: [
|
||||
"small",
|
||||
"middle",
|
||||
"large"
|
||||
],
|
||||
description: "Size of the tabs."
|
||||
},
|
||||
{
|
||||
name: "animated",
|
||||
label: "Animated",
|
||||
type: "boolean",
|
||||
description: "Whether to animate tab transitions."
|
||||
},
|
||||
{
|
||||
name: "centered",
|
||||
label: "Centered",
|
||||
type: "boolean",
|
||||
description: "Whether to center the tabs."
|
||||
},
|
||||
{
|
||||
name: "tabBarGutter",
|
||||
label: "Tab Bar Gutter",
|
||||
type: "number",
|
||||
description: "The gap between tabs."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
items={[
|
||||
{ key: '1', label: 'Tab 1', children: 'Content of Tab Pane 1' },
|
||||
{ key: '2', label: 'Tab 2', children: 'Content of Tab Pane 2' },
|
||||
{ key: '3', label: 'Tab 3', children: 'Content of Tab Pane 3' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Card Style
|
||||
|
||||
```tsx live
|
||||
function CardTabs() {
|
||||
return (
|
||||
<Tabs
|
||||
type="card"
|
||||
defaultActiveKey="1"
|
||||
items={[
|
||||
{ key: '1', label: 'Dashboards', children: 'View and manage your dashboards.' },
|
||||
{ key: '2', label: 'Charts', children: 'Browse all saved charts.' },
|
||||
{ key: '3', label: 'Datasets', children: 'Explore available datasets.' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Tab Positions
|
||||
|
||||
```tsx live
|
||||
function TabPositions() {
|
||||
const items = [
|
||||
{ key: '1', label: 'Tab 1', children: 'Content 1' },
|
||||
{ key: '2', label: 'Tab 2', children: 'Content 2' },
|
||||
{ key: '3', label: 'Tab 3', children: 'Content 3' },
|
||||
];
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
|
||||
{['top', 'bottom', 'left', 'right'].map(pos => (
|
||||
<div key={pos}>
|
||||
<h4>{pos}</h4>
|
||||
<Tabs tabPosition={pos} defaultActiveKey="1" items={items} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## With Icons
|
||||
|
||||
```tsx live
|
||||
function IconTabs() {
|
||||
return (
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
items={[
|
||||
{ key: '1', label: <><Icons.DashboardOutlined /> Dashboards</>, children: 'Dashboard content here.' },
|
||||
{ key: '2', label: <><Icons.LineChartOutlined /> Charts</>, children: 'Chart content here.' },
|
||||
{ key: '3', label: <><Icons.DatabaseOutlined /> Datasets</>, children: 'Dataset content here.' },
|
||||
{ key: '4', label: <><Icons.ConsoleSqlOutlined /> SQL Lab</>, children: 'SQL Lab content here.' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `defaultActiveKey` | `string` | `"1"` | - |
|
||||
| `type` | `string` | `"line"` | The style of tabs. Options: line, card, editable-card. |
|
||||
| `tabPosition` | `string` | `"top"` | Position of tabs. Options: top, bottom, left, right. |
|
||||
| `size` | `string` | `"middle"` | Size of the tabs. |
|
||||
| `animated` | `boolean` | `true` | Whether to animate tab transitions. |
|
||||
| `centered` | `boolean` | `false` | Whether to center the tabs. |
|
||||
| `tabBarGutter` | `number` | `8` | The gap between tabs. |
|
||||
| `items` | `any` | `[{"key":"1","label":"Tab 1","children":"Content of Tab Pane 1"},{"key":"2","label":"Tab 2","children":"Content of Tab Pane 2"},{"key":"3","label":"Tab 3","children":"Content of Tab Pane 3"}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Tabs } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Tabs/Tabs.stories.tsx).
|
||||
:::
|
||||
172
docs/developer_docs/components/ui/timer.mdx
Normal file
172
docs/developer_docs/components/ui/timer.mdx
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
title: Timer
|
||||
sidebar_label: Timer
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Timer
|
||||
|
||||
A live elapsed-time display that counts up from a given start time. Used to show query and dashboard load durations. Requires a startTime timestamp to function.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Timer"
|
||||
props={{
|
||||
isRunning: true,
|
||||
status: "success",
|
||||
startTime: 1737936000000
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "isRunning",
|
||||
label: "Is Running",
|
||||
type: "boolean",
|
||||
description: "Whether the timer is actively counting. Toggle to start/stop."
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
label: "Status",
|
||||
type: "select",
|
||||
options: [
|
||||
"success",
|
||||
"warning",
|
||||
"danger",
|
||||
"info",
|
||||
"default",
|
||||
"primary",
|
||||
"secondary"
|
||||
],
|
||||
description: "Visual status of the timer badge."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const [isRunning, setIsRunning] = React.useState(true);
|
||||
const [startTime] = React.useState(Date.now());
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<Timer
|
||||
startTime={startTime}
|
||||
isRunning={isRunning}
|
||||
status="success"
|
||||
/>
|
||||
<Button onClick={() => setIsRunning(r => !r)}>
|
||||
{isRunning ? 'Stop' : 'Start'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Status Variants
|
||||
|
||||
```tsx live
|
||||
function StatusVariants() {
|
||||
const [startTime] = React.useState(Date.now());
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
{['success', 'warning', 'danger', 'info', 'default', 'primary', 'secondary'].map(status => (
|
||||
<div key={status} style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<span style={{ width: 80 }}>{status}</span>
|
||||
<Timer startTime={startTime} isRunning status={status} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Completed Timer
|
||||
|
||||
```tsx live
|
||||
function CompletedTimer() {
|
||||
const start = Date.now() - 5230;
|
||||
const end = Date.now();
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Timer
|
||||
startTime={start}
|
||||
endTime={end}
|
||||
isRunning={false}
|
||||
status="success"
|
||||
/>
|
||||
<span style={{ color: '#999' }}>Query completed in ~5.2 seconds</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Start and Stop
|
||||
|
||||
```tsx live
|
||||
function StartStop() {
|
||||
const [isRunning, setIsRunning] = React.useState(false);
|
||||
const [startTime, setStartTime] = React.useState(null);
|
||||
const handleToggle = () => {
|
||||
if (!isRunning && !startTime) {
|
||||
setStartTime(Date.now());
|
||||
}
|
||||
setIsRunning(r => !r);
|
||||
};
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<Timer
|
||||
startTime={startTime}
|
||||
isRunning={isRunning}
|
||||
status={isRunning ? 'warning' : 'success'}
|
||||
/>
|
||||
<Button onClick={handleToggle}>
|
||||
{isRunning ? 'Pause' : startTime ? 'Resume' : 'Start'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `isRunning` | `boolean` | `true` | Whether the timer is actively counting. Toggle to start/stop. |
|
||||
| `status` | `string` | `"success"` | Visual status of the timer badge. |
|
||||
| `startTime` | `number` | `1737936000000` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Timer } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Timer/Timer.stories.tsx).
|
||||
:::
|
||||
171
docs/developer_docs/components/ui/tooltip.mdx
Normal file
171
docs/developer_docs/components/ui/tooltip.mdx
Normal file
@@ -0,0 +1,171 @@
|
||||
---
|
||||
title: Tooltip
|
||||
sidebar_label: Tooltip
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Tooltip
|
||||
|
||||
The Tooltip component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Tooltip"
|
||||
props={{
|
||||
title: "Simple tooltip text",
|
||||
mouseEnterDelay: 0.1,
|
||||
mouseLeaveDelay: 0.1
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "Text or content shown in the tooltip."
|
||||
},
|
||||
{
|
||||
name: "mouseEnterDelay",
|
||||
label: "Mouse Enter Delay",
|
||||
type: "number",
|
||||
description: "Delay in seconds before showing the tooltip on hover."
|
||||
},
|
||||
{
|
||||
name: "mouseLeaveDelay",
|
||||
label: "Mouse Leave Delay",
|
||||
type: "number",
|
||||
description: "Delay in seconds before hiding the tooltip after mouse leave."
|
||||
},
|
||||
{
|
||||
name: "placement",
|
||||
label: "Placement",
|
||||
type: "select",
|
||||
options: [
|
||||
"bottom",
|
||||
"bottomLeft",
|
||||
"bottomRight",
|
||||
"left",
|
||||
"leftBottom",
|
||||
"leftTop",
|
||||
"right",
|
||||
"rightBottom",
|
||||
"rightTop",
|
||||
"top",
|
||||
"topLeft",
|
||||
"topRight"
|
||||
],
|
||||
description: "Position of the tooltip relative to the trigger element."
|
||||
},
|
||||
{
|
||||
name: "trigger",
|
||||
label: "Trigger",
|
||||
type: "select",
|
||||
options: [
|
||||
"hover",
|
||||
"focus",
|
||||
"click",
|
||||
"contextMenu"
|
||||
],
|
||||
description: "How the tooltip is triggered."
|
||||
},
|
||||
{
|
||||
name: "color",
|
||||
label: "Color",
|
||||
type: "color",
|
||||
description: "Custom background color for the tooltip."
|
||||
}
|
||||
]}
|
||||
sampleChildren={[{"component":"Button","props":{"children":"Hover me"}}]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Tooltip title="This is a helpful tooltip">
|
||||
<Button>Hover me</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Placements
|
||||
|
||||
```tsx live
|
||||
function Placements() {
|
||||
const placements = ['top', 'bottom', 'left', 'right', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'];
|
||||
return (
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||
{placements.map(placement => (
|
||||
<Tooltip key={placement} title={placement} placement={placement}>
|
||||
<Button>{placement}</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Trigger Types
|
||||
|
||||
```tsx live
|
||||
function Triggers() {
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<Tooltip title="Hover trigger" trigger="hover">
|
||||
<Button>Hover</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Click trigger" trigger="click">
|
||||
<Button>Click</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Focus trigger" trigger="focus">
|
||||
<Button>Focus</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `title` | `string` | `"Simple tooltip text"` | Text or content shown in the tooltip. |
|
||||
| `mouseEnterDelay` | `number` | `0.1` | Delay in seconds before showing the tooltip on hover. |
|
||||
| `mouseLeaveDelay` | `number` | `0.1` | Delay in seconds before hiding the tooltip after mouse leave. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Tooltip } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Tooltip/Tooltip.stories.tsx).
|
||||
:::
|
||||
268
docs/developer_docs/components/ui/tree.mdx
Normal file
268
docs/developer_docs/components/ui/tree.mdx
Normal file
@@ -0,0 +1,268 @@
|
||||
---
|
||||
title: Tree
|
||||
sidebar_label: Tree
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Tree
|
||||
|
||||
The Tree component is used to display hierarchical data in a tree structure. It allows for features such as selection, expansion, and drag-and-drop functionality.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Tree"
|
||||
props={{
|
||||
checkable: false,
|
||||
defaultExpandAll: false,
|
||||
disabled: false,
|
||||
draggable: false,
|
||||
multiple: false,
|
||||
selectable: true,
|
||||
showIcon: false,
|
||||
showLine: false,
|
||||
treeData: [
|
||||
{
|
||||
title: "parent 1",
|
||||
key: "0-0",
|
||||
children: [
|
||||
{
|
||||
title: "parent 1-0",
|
||||
key: "0-0-0",
|
||||
children: [
|
||||
{
|
||||
title: "leaf",
|
||||
key: "0-0-0-0"
|
||||
},
|
||||
{
|
||||
title: "leaf",
|
||||
key: "0-0-0-1"
|
||||
},
|
||||
{
|
||||
title: "leaf",
|
||||
key: "0-0-0-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "parent 1-1",
|
||||
key: "0-0-1",
|
||||
children: [
|
||||
{
|
||||
title: "leaf",
|
||||
key: "0-0-1-0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "parent 1-2",
|
||||
key: "0-0-2",
|
||||
children: [
|
||||
{
|
||||
title: "leaf",
|
||||
key: "0-0-2-0"
|
||||
},
|
||||
{
|
||||
title: "leaf",
|
||||
key: "0-0-2-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
defaultExpandedKeys: [
|
||||
"0-0",
|
||||
"0-0-0"
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "checkable",
|
||||
label: "Checkable",
|
||||
type: "boolean",
|
||||
description: "Add a Checkbox before the treeNodes"
|
||||
},
|
||||
{
|
||||
name: "defaultExpandAll",
|
||||
label: "Default Expand All",
|
||||
type: "boolean",
|
||||
description: "Whether to expand all treeNodes by default"
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Whether disabled the tree"
|
||||
},
|
||||
{
|
||||
name: "draggable",
|
||||
label: "Draggable",
|
||||
type: "boolean",
|
||||
description: "Specifies whether this Tree or the node is draggable"
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
label: "Multiple",
|
||||
type: "boolean",
|
||||
description: "Allows selecting multiple treeNodes"
|
||||
},
|
||||
{
|
||||
name: "selectable",
|
||||
label: "Selectable",
|
||||
type: "boolean",
|
||||
description: "Whether can be selected"
|
||||
},
|
||||
{
|
||||
name: "showIcon",
|
||||
label: "Show Icon",
|
||||
type: "boolean",
|
||||
description: "Controls whether to display the icon node"
|
||||
},
|
||||
{
|
||||
name: "showLine",
|
||||
label: "Show Line",
|
||||
type: "boolean",
|
||||
description: "Shows a connecting line"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const treeData = [
|
||||
{
|
||||
title: 'Databases',
|
||||
key: 'databases',
|
||||
children: [
|
||||
{ title: 'PostgreSQL', key: 'postgres' },
|
||||
{ title: 'MySQL', key: 'mysql' },
|
||||
{ title: 'SQLite', key: 'sqlite' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Charts',
|
||||
key: 'charts',
|
||||
children: [
|
||||
{ title: 'Bar Chart', key: 'bar' },
|
||||
{ title: 'Line Chart', key: 'line' },
|
||||
{ title: 'Pie Chart', key: 'pie' },
|
||||
],
|
||||
},
|
||||
];
|
||||
return <Tree treeData={treeData} defaultExpandAll />;
|
||||
}
|
||||
```
|
||||
|
||||
## Checkable Tree
|
||||
|
||||
```tsx live
|
||||
function CheckableTree() {
|
||||
const [checkedKeys, setCheckedKeys] = React.useState(['postgres']);
|
||||
const treeData = [
|
||||
{
|
||||
title: 'Databases',
|
||||
key: 'databases',
|
||||
children: [
|
||||
{ title: 'PostgreSQL', key: 'postgres' },
|
||||
{ title: 'MySQL', key: 'mysql' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Charts',
|
||||
key: 'charts',
|
||||
children: [
|
||||
{ title: 'Bar Chart', key: 'bar' },
|
||||
{ title: 'Line Chart', key: 'line' },
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Tree
|
||||
treeData={treeData}
|
||||
checkable
|
||||
defaultExpandAll
|
||||
checkedKeys={checkedKeys}
|
||||
onCheck={setCheckedKeys}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## With Lines and Icons
|
||||
|
||||
```tsx live
|
||||
function LinesAndIcons() {
|
||||
const treeData = [
|
||||
{
|
||||
title: 'Dashboards',
|
||||
key: 'dashboards',
|
||||
children: [
|
||||
{ title: 'Sales Dashboard', key: 'sales' },
|
||||
{ title: 'Marketing Dashboard', key: 'marketing' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Reports',
|
||||
key: 'reports',
|
||||
children: [
|
||||
{ title: 'Weekly Report', key: 'weekly' },
|
||||
{ title: 'Monthly Report', key: 'monthly' },
|
||||
],
|
||||
},
|
||||
];
|
||||
return <Tree treeData={treeData} showLine showIcon defaultExpandAll />;
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `checkable` | `boolean` | `false` | Add a Checkbox before the treeNodes |
|
||||
| `defaultExpandAll` | `boolean` | `false` | Whether to expand all treeNodes by default |
|
||||
| `disabled` | `boolean` | `false` | Whether disabled the tree |
|
||||
| `draggable` | `boolean` | `false` | Specifies whether this Tree or the node is draggable |
|
||||
| `multiple` | `boolean` | `false` | Allows selecting multiple treeNodes |
|
||||
| `selectable` | `boolean` | `true` | Whether can be selected |
|
||||
| `showIcon` | `boolean` | `false` | Controls whether to display the icon node |
|
||||
| `showLine` | `boolean` | `false` | Shows a connecting line |
|
||||
| `treeData` | `any` | `[{"title":"parent 1","key":"0-0","children":[{"title":"parent 1-0","key":"0-0-0","children":[{"title":"leaf","key":"0-0-0-0"},{"title":"leaf","key":"0-0-0-1"},{"title":"leaf","key":"0-0-0-2"}]},{"title":"parent 1-1","key":"0-0-1","children":[{"title":"leaf","key":"0-0-1-0"}]},{"title":"parent 1-2","key":"0-0-2","children":[{"title":"leaf","key":"0-0-2-0"},{"title":"leaf","key":"0-0-2-1"}]}]}]` | - |
|
||||
| `defaultExpandedKeys` | `any` | `["0-0","0-0-0"]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Tree } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Tree/Tree.stories.tsx).
|
||||
:::
|
||||
286
docs/developer_docs/components/ui/treeselect.mdx
Normal file
286
docs/developer_docs/components/ui/treeselect.mdx
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
title: TreeSelect
|
||||
sidebar_label: TreeSelect
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# TreeSelect
|
||||
|
||||
TreeSelect is a select component that allows users to select from a tree structure.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="TreeSelect"
|
||||
props={{
|
||||
allowClear: true,
|
||||
disabled: false,
|
||||
multiple: false,
|
||||
placeholder: "Please select",
|
||||
showSearch: true,
|
||||
size: "middle",
|
||||
treeCheckable: false,
|
||||
treeDefaultExpandAll: true,
|
||||
treeLine: false,
|
||||
variant: "outlined",
|
||||
treeData: [
|
||||
{
|
||||
title: "Node1",
|
||||
value: "0-0",
|
||||
children: [
|
||||
{
|
||||
title: "Child Node1",
|
||||
value: "0-0-0"
|
||||
},
|
||||
{
|
||||
title: "Child Node2",
|
||||
value: "0-0-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Node2",
|
||||
value: "0-1",
|
||||
children: [
|
||||
{
|
||||
title: "Child Node3",
|
||||
value: "0-1-0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "allowClear",
|
||||
label: "Allow Clear",
|
||||
type: "boolean",
|
||||
description: "Whether to allow clearing the selected value."
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Whether the component is disabled."
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
label: "Multiple",
|
||||
type: "boolean",
|
||||
description: "Whether to allow multiple selections."
|
||||
},
|
||||
{
|
||||
name: "placeholder",
|
||||
label: "Placeholder",
|
||||
type: "text",
|
||||
description: "Placeholder text for the input field."
|
||||
},
|
||||
{
|
||||
name: "showSearch",
|
||||
label: "Show Search",
|
||||
type: "boolean",
|
||||
description: "Whether to show the search input."
|
||||
},
|
||||
{
|
||||
name: "size",
|
||||
label: "Size",
|
||||
type: "select",
|
||||
options: [
|
||||
"large",
|
||||
"middle",
|
||||
"small"
|
||||
],
|
||||
description: "Size of the component."
|
||||
},
|
||||
{
|
||||
name: "treeCheckable",
|
||||
label: "Tree Checkable",
|
||||
type: "boolean",
|
||||
description: "Whether to show checkable tree nodes."
|
||||
},
|
||||
{
|
||||
name: "treeDefaultExpandAll",
|
||||
label: "Tree Default Expand All",
|
||||
type: "boolean",
|
||||
description: "Whether to expand all tree nodes by default."
|
||||
},
|
||||
{
|
||||
name: "treeLine",
|
||||
label: "Tree Line",
|
||||
type: "boolean",
|
||||
description: "Whether to show tree lines."
|
||||
},
|
||||
{
|
||||
name: "variant",
|
||||
label: "Variant",
|
||||
type: "select",
|
||||
options: [
|
||||
"outlined",
|
||||
"borderless",
|
||||
"filled"
|
||||
],
|
||||
description: "Variant of the component."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const [value, setValue] = React.useState(undefined);
|
||||
const treeData = [
|
||||
{
|
||||
title: 'Databases',
|
||||
value: 'databases',
|
||||
children: [
|
||||
{ title: 'PostgreSQL', value: 'postgres' },
|
||||
{ title: 'MySQL', value: 'mysql' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Charts',
|
||||
value: 'charts',
|
||||
children: [
|
||||
{ title: 'Bar Chart', value: 'bar' },
|
||||
{ title: 'Line Chart', value: 'line' },
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<TreeSelect
|
||||
style={{ width: 300 }}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
treeData={treeData}
|
||||
placeholder="Select an item"
|
||||
allowClear
|
||||
treeDefaultExpandAll
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple Selection with Checkboxes
|
||||
|
||||
```tsx live
|
||||
function MultiSelectTree() {
|
||||
const [value, setValue] = React.useState([]);
|
||||
const treeData = [
|
||||
{
|
||||
title: 'Databases',
|
||||
value: 'databases',
|
||||
children: [
|
||||
{ title: 'PostgreSQL', value: 'postgres' },
|
||||
{ title: 'MySQL', value: 'mysql' },
|
||||
{ title: 'SQLite', value: 'sqlite' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'File Formats',
|
||||
value: 'formats',
|
||||
children: [
|
||||
{ title: 'CSV', value: 'csv' },
|
||||
{ title: 'Excel', value: 'excel' },
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<TreeSelect
|
||||
style={{ width: 300 }}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
treeData={treeData}
|
||||
treeCheckable
|
||||
placeholder="Select data sources"
|
||||
treeDefaultExpandAll
|
||||
allowClear
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## With Tree Lines
|
||||
|
||||
```tsx live
|
||||
function TreeLinesDemo() {
|
||||
const treeData = [
|
||||
{
|
||||
title: 'Dashboards',
|
||||
value: 'dashboards',
|
||||
children: [
|
||||
{ title: 'Sales Dashboard', value: 'sales' },
|
||||
{ title: 'Marketing Dashboard', value: 'marketing' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Charts',
|
||||
value: 'charts',
|
||||
children: [
|
||||
{ title: 'Revenue Chart', value: 'revenue' },
|
||||
{ title: 'User Growth', value: 'growth' },
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<TreeSelect
|
||||
style={{ width: 300 }}
|
||||
treeData={treeData}
|
||||
treeLine
|
||||
treeDefaultExpandAll
|
||||
placeholder="Browse items"
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `allowClear` | `boolean` | `true` | Whether to allow clearing the selected value. |
|
||||
| `disabled` | `boolean` | `false` | Whether the component is disabled. |
|
||||
| `multiple` | `boolean` | `false` | Whether to allow multiple selections. |
|
||||
| `placeholder` | `string` | `"Please select"` | Placeholder text for the input field. |
|
||||
| `showSearch` | `boolean` | `true` | Whether to show the search input. |
|
||||
| `size` | `string` | `"middle"` | Size of the component. |
|
||||
| `treeCheckable` | `boolean` | `false` | Whether to show checkable tree nodes. |
|
||||
| `treeDefaultExpandAll` | `boolean` | `true` | Whether to expand all tree nodes by default. |
|
||||
| `treeLine` | `boolean` | `false` | Whether to show tree lines. |
|
||||
| `variant` | `string` | `"outlined"` | Variant of the component. |
|
||||
| `treeData` | `any` | `[{"title":"Node1","value":"0-0","children":[{"title":"Child Node1","value":"0-0-0"},{"title":"Child Node2","value":"0-0-1"}]},{"title":"Node2","value":"0-1","children":[{"title":"Child Node3","value":"0-1-0"}]}]` | - |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { TreeSelect } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/TreeSelect/TreeSelect.stories.tsx).
|
||||
:::
|
||||
236
docs/developer_docs/components/ui/typography.mdx
Normal file
236
docs/developer_docs/components/ui/typography.mdx
Normal file
@@ -0,0 +1,236 @@
|
||||
---
|
||||
title: Typography
|
||||
sidebar_label: Typography
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Typography
|
||||
|
||||
Typography is a component for displaying text with various styles and formats. It includes subcomponents like Title, Paragraph, and Link.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Typography"
|
||||
renderComponent="Typography.Text"
|
||||
props={{
|
||||
children: "Sample Text",
|
||||
code: false,
|
||||
copyable: false,
|
||||
delete: false,
|
||||
disabled: false,
|
||||
ellipsis: false,
|
||||
keyboard: false,
|
||||
mark: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
strong: false
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "children",
|
||||
label: "Children",
|
||||
type: "text",
|
||||
description: "The text content."
|
||||
},
|
||||
{
|
||||
name: "code",
|
||||
label: "Code",
|
||||
type: "boolean",
|
||||
description: "Code style."
|
||||
},
|
||||
{
|
||||
name: "copyable",
|
||||
label: "Copyable",
|
||||
type: "boolean",
|
||||
description: "Whether the text is copyable."
|
||||
},
|
||||
{
|
||||
name: "delete",
|
||||
label: "Delete",
|
||||
type: "boolean",
|
||||
description: "Deleted line style."
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Disabled content."
|
||||
},
|
||||
{
|
||||
name: "ellipsis",
|
||||
label: "Ellipsis",
|
||||
type: "boolean",
|
||||
description: "Display ellipsis when text overflows."
|
||||
},
|
||||
{
|
||||
name: "keyboard",
|
||||
label: "Keyboard",
|
||||
type: "boolean",
|
||||
description: "Keyboard style."
|
||||
},
|
||||
{
|
||||
name: "mark",
|
||||
label: "Mark",
|
||||
type: "boolean",
|
||||
description: "Marked/highlighted style."
|
||||
},
|
||||
{
|
||||
name: "italic",
|
||||
label: "Italic",
|
||||
type: "boolean",
|
||||
description: "Italic style."
|
||||
},
|
||||
{
|
||||
name: "underline",
|
||||
label: "Underline",
|
||||
type: "boolean",
|
||||
description: "Underlined style."
|
||||
},
|
||||
{
|
||||
name: "strong",
|
||||
label: "Strong",
|
||||
type: "boolean",
|
||||
description: "Bold style."
|
||||
},
|
||||
{
|
||||
name: "type",
|
||||
label: "Type",
|
||||
type: "select",
|
||||
options: [
|
||||
"secondary",
|
||||
"success",
|
||||
"warning",
|
||||
"danger"
|
||||
],
|
||||
description: "Text type for semantic coloring."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<div>
|
||||
<Typography.Text>Default Text</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text type="secondary">Secondary</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text type="success">Success</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text type="warning">Warning</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text type="danger">Danger</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text code>Code</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text keyboard>Keyboard</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text mark>Marked</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text underline>Underline</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text delete>Deleted</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text strong>Strong</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text italic>Italic</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## All Subcomponents
|
||||
|
||||
```tsx live
|
||||
function AllSubcomponents() {
|
||||
return (
|
||||
<div>
|
||||
<Typography.Title level={2}>Typography Components</Typography.Title>
|
||||
<Typography.Paragraph>
|
||||
The Typography component includes several subcomponents for different text needs.
|
||||
Use <Typography.Text strong>Title</Typography.Text> for headings,
|
||||
<Typography.Text code>Text</Typography.Text> for inline text styling,
|
||||
and <Typography.Text mark>Paragraph</Typography.Text> for block content.
|
||||
</Typography.Paragraph>
|
||||
<Typography.Link href="https://superset.apache.org" target="_blank">
|
||||
Learn more about Apache Superset
|
||||
</Typography.Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Text Styling Options
|
||||
|
||||
```tsx live
|
||||
function TextStyles() {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<Typography.Text code>Code style</Typography.Text>
|
||||
<Typography.Text keyboard>Keyboard style</Typography.Text>
|
||||
<Typography.Text mark>Highlighted text</Typography.Text>
|
||||
<Typography.Text underline>Underlined text</Typography.Text>
|
||||
<Typography.Text delete>Deleted text</Typography.Text>
|
||||
<Typography.Text strong>Bold text</Typography.Text>
|
||||
<Typography.Text italic>Italic text</Typography.Text>
|
||||
<Typography.Text type="success">Success type</Typography.Text>
|
||||
<Typography.Text type="warning">Warning type</Typography.Text>
|
||||
<Typography.Text type="danger">Danger type</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `children` | `string` | `"Sample Text"` | The text content. |
|
||||
| `code` | `boolean` | `false` | Code style. |
|
||||
| `copyable` | `boolean` | `false` | Whether the text is copyable. |
|
||||
| `delete` | `boolean` | `false` | Deleted line style. |
|
||||
| `disabled` | `boolean` | `false` | Disabled content. |
|
||||
| `ellipsis` | `boolean` | `false` | Display ellipsis when text overflows. |
|
||||
| `keyboard` | `boolean` | `false` | Keyboard style. |
|
||||
| `mark` | `boolean` | `false` | Marked/highlighted style. |
|
||||
| `italic` | `boolean` | `false` | Italic style. |
|
||||
| `underline` | `boolean` | `false` | Underlined style. |
|
||||
| `strong` | `boolean` | `false` | Bold style. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Typography } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Typography/Typography.stories.tsx).
|
||||
:::
|
||||
126
docs/developer_docs/components/ui/unsavedchangesmodal.mdx
Normal file
126
docs/developer_docs/components/ui/unsavedchangesmodal.mdx
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: UnsavedChangesModal
|
||||
sidebar_label: UnsavedChangesModal
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# UnsavedChangesModal
|
||||
|
||||
The UnsavedChangesModal component from Superset's UI library.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="UnsavedChangesModal"
|
||||
props={{
|
||||
showModal: false,
|
||||
title: "Unsaved Changes"
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "showModal",
|
||||
label: "Show Modal",
|
||||
type: "boolean",
|
||||
description: "Whether the modal is visible."
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
label: "Title",
|
||||
type: "text",
|
||||
description: "Title text displayed in the modal header."
|
||||
}
|
||||
]}
|
||||
triggerProp="showModal"
|
||||
onHideProp="onHide,handleSave,onConfirmNavigation"
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
const [show, setShow] = React.useState(false);
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => setShow(true)}>
|
||||
Navigate Away (Unsaved Changes)
|
||||
</Button>
|
||||
<UnsavedChangesModal
|
||||
showModal={show}
|
||||
onHide={() => setShow(false)}
|
||||
handleSave={() => { alert('Saved!'); setShow(false); }}
|
||||
onConfirmNavigation={() => { alert('Discarded changes'); setShow(false); }}
|
||||
title="Unsaved Changes"
|
||||
>
|
||||
If you don't save, changes will be lost.
|
||||
</UnsavedChangesModal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Title
|
||||
|
||||
```tsx live
|
||||
function CustomTitle() {
|
||||
const [show, setShow] = React.useState(false);
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => setShow(true)}>
|
||||
Close Without Saving
|
||||
</Button>
|
||||
<UnsavedChangesModal
|
||||
showModal={show}
|
||||
onHide={() => setShow(false)}
|
||||
handleSave={() => setShow(false)}
|
||||
onConfirmNavigation={() => setShow(false)}
|
||||
title="You have unsaved dashboard changes"
|
||||
>
|
||||
Your dashboard layout and filter changes have not been saved.
|
||||
Do you want to save before leaving?
|
||||
</UnsavedChangesModal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `showModal` | `boolean` | `false` | Whether the modal is visible. |
|
||||
| `title` | `string` | `"Unsaved Changes"` | Title text displayed in the modal header. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { UnsavedChangesModal } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/UnsavedChangesModal/UnsavedChangesModal.stories.tsx).
|
||||
:::
|
||||
136
docs/developer_docs/components/ui/upload.mdx
Normal file
136
docs/developer_docs/components/ui/upload.mdx
Normal file
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: Upload
|
||||
sidebar_label: Upload
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
|
||||
# Upload
|
||||
|
||||
Upload component for file selection and uploading. Supports drag-and-drop, multiple files, and different list display styles.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component="Upload"
|
||||
props={{
|
||||
multiple: false,
|
||||
disabled: false,
|
||||
listType: "text",
|
||||
showUploadList: true
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: "multiple",
|
||||
label: "Multiple",
|
||||
type: "boolean",
|
||||
description: "Support multiple file selection."
|
||||
},
|
||||
{
|
||||
name: "disabled",
|
||||
label: "Disabled",
|
||||
type: "boolean",
|
||||
description: "Disable the upload button."
|
||||
},
|
||||
{
|
||||
name: "listType",
|
||||
label: "List Type",
|
||||
type: "select",
|
||||
options: [
|
||||
"text",
|
||||
"picture",
|
||||
"picture-card",
|
||||
"picture-circle"
|
||||
],
|
||||
description: "Built-in style for the file list display."
|
||||
},
|
||||
{
|
||||
name: "showUploadList",
|
||||
label: "Show Upload List",
|
||||
type: "boolean",
|
||||
description: "Whether to show the upload file list."
|
||||
}
|
||||
]}
|
||||
sampleChildren={[{"component":"Button","props":{"children":"Click to Upload"}}]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Upload>
|
||||
<Button>Click to Upload</Button>
|
||||
</Upload>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Picture Card Style
|
||||
|
||||
```tsx live
|
||||
function PictureCard() {
|
||||
return (
|
||||
<Upload listType="picture-card">
|
||||
+ Upload
|
||||
</Upload>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Drag and Drop
|
||||
|
||||
```tsx live
|
||||
function DragDrop() {
|
||||
return (
|
||||
<Upload.Dragger>
|
||||
<p style={{ fontSize: 48, color: '#999', margin: 0 }}>+</p>
|
||||
<p>Click or drag file to this area to upload</p>
|
||||
<p style={{ color: '#999' }}>Support for single or bulk upload.</p>
|
||||
</Upload.Dragger>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `multiple` | `boolean` | `false` | Support multiple file selection. |
|
||||
| `disabled` | `boolean` | `false` | Disable the upload button. |
|
||||
| `listType` | `string` | `"text"` | Built-in style for the file list display. |
|
||||
| `showUploadList` | `boolean` | `true` | Whether to show the upload file list. |
|
||||
|
||||
## Import
|
||||
|
||||
```tsx
|
||||
import { Upload } from '@superset/components';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
:::tip[Improve this page]
|
||||
This documentation is auto-generated from the component's Storybook story.
|
||||
Help improve it by [editing the story file](https://github.com/apache/superset/edit/master/superset-frontend/packages/superset-ui-core/src/components/Upload/Upload.stories.tsx).
|
||||
:::
|
||||
339
docs/developer_docs/contributing/code-review.md
Normal file
339
docs/developer_docs/contributing/code-review.md
Normal file
@@ -0,0 +1,339 @@
|
||||
---
|
||||
title: Code Review Process
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Code Review Process
|
||||
|
||||
Understand how code reviews work in Apache Superset and how to participate effectively.
|
||||
|
||||
## Overview
|
||||
|
||||
Code review is a critical part of maintaining code quality and sharing knowledge across the team. Every change to Superset goes through peer review before merging.
|
||||
|
||||
## For Authors
|
||||
|
||||
### Preparing for Review
|
||||
|
||||
#### Before Requesting Review
|
||||
- [ ] Self-review your changes
|
||||
- [ ] Ensure CI checks pass
|
||||
- [ ] Add comprehensive tests
|
||||
- [ ] Update documentation
|
||||
- [ ] Fill out PR template completely
|
||||
- [ ] Add screenshots for UI changes
|
||||
|
||||
#### Self-Review Checklist
|
||||
```bash
|
||||
# View your changes
|
||||
git diff upstream/master
|
||||
|
||||
# Check for common issues:
|
||||
# - Commented out code
|
||||
# - Debug statements (console.log, print)
|
||||
# - TODO comments that should be addressed
|
||||
# - Hardcoded values that should be configurable
|
||||
# - Missing error handling
|
||||
# - Performance implications
|
||||
```
|
||||
|
||||
### Requesting Review
|
||||
|
||||
#### Auto-Assignment
|
||||
GitHub will automatically request reviews based on CODEOWNERS file.
|
||||
|
||||
#### Manual Assignment
|
||||
For specific expertise, request additional reviewers:
|
||||
- Frontend changes: Tag frontend experts
|
||||
- Backend changes: Tag backend experts
|
||||
- Security changes: Tag security team
|
||||
- Database changes: Tag database experts
|
||||
|
||||
#### Review Request Message
|
||||
```markdown
|
||||
@reviewer This PR implements [feature]. Could you please review:
|
||||
1. The approach taken in [file]
|
||||
2. Performance implications of [change]
|
||||
3. Security considerations for [feature]
|
||||
|
||||
Thanks!
|
||||
```
|
||||
|
||||
### Responding to Feedback
|
||||
|
||||
#### Best Practices
|
||||
- **Be receptive**: Reviews improve code quality
|
||||
- **Ask questions**: Clarify if feedback is unclear
|
||||
- **Explain decisions**: Share context for your choices
|
||||
- **Update promptly**: Address feedback in timely manner
|
||||
|
||||
#### Comment Responses
|
||||
```markdown
|
||||
# Acknowledging
|
||||
"Good catch! Fixed in [commit hash]"
|
||||
|
||||
# Explaining
|
||||
"I chose this approach because [reason]. Would you prefer [alternative]?"
|
||||
|
||||
# Questioning
|
||||
"Could you elaborate on [concern]? I'm not sure I understand the issue."
|
||||
|
||||
# Disagreeing respectfully
|
||||
"I see your point, but I think [current approach] because [reason]. What do you think?"
|
||||
```
|
||||
|
||||
## For Reviewers
|
||||
|
||||
### Review Responsibilities
|
||||
|
||||
#### What to Review
|
||||
1. **Correctness**: Does the code do what it claims?
|
||||
2. **Design**: Is the approach appropriate?
|
||||
3. **Clarity**: Is the code readable and maintainable?
|
||||
4. **Testing**: Are tests comprehensive?
|
||||
5. **Performance**: Any performance concerns?
|
||||
6. **Security**: Any security issues?
|
||||
7. **Documentation**: Is it well documented?
|
||||
|
||||
### Review Checklist
|
||||
|
||||
#### Functionality
|
||||
- [ ] Feature works as described
|
||||
- [ ] Edge cases are handled
|
||||
- [ ] Error handling is appropriate
|
||||
- [ ] Backwards compatibility maintained
|
||||
|
||||
#### Code Quality
|
||||
- [ ] Follows project conventions
|
||||
- [ ] No code duplication
|
||||
- [ ] Clear variable/function names
|
||||
- [ ] Appropriate abstraction levels
|
||||
- [ ] SOLID principles followed
|
||||
|
||||
#### Testing
|
||||
- [ ] Unit tests for business logic
|
||||
- [ ] Integration tests for APIs
|
||||
- [ ] E2E tests for critical paths
|
||||
- [ ] Tests are maintainable
|
||||
- [ ] Good test coverage
|
||||
|
||||
#### Security
|
||||
- [ ] Input validation
|
||||
- [ ] SQL injection prevention
|
||||
- [ ] XSS prevention
|
||||
- [ ] CSRF protection
|
||||
- [ ] Authentication/authorization checks
|
||||
- [ ] No sensitive data in logs
|
||||
|
||||
#### Performance
|
||||
- [ ] Database queries optimized
|
||||
- [ ] No N+1 queries
|
||||
- [ ] Appropriate caching
|
||||
- [ ] Frontend bundle size impact
|
||||
- [ ] Memory usage considerations
|
||||
|
||||
### Providing Feedback
|
||||
|
||||
#### Effective Comments
|
||||
|
||||
```python
|
||||
# ✅ Good: Specific and actionable
|
||||
"This query could cause N+1 problems. Consider using
|
||||
`select_related('user')` to fetch users in a single query."
|
||||
|
||||
# ❌ Bad: Vague
|
||||
"This doesn't look right."
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ✅ Good: Suggests improvement
|
||||
"Consider using useMemo here to prevent unnecessary
|
||||
re-renders when dependencies haven't changed."
|
||||
|
||||
// ❌ Bad: Just criticism
|
||||
"This is inefficient."
|
||||
```
|
||||
|
||||
#### Comment Types
|
||||
|
||||
**Use GitHub's comment types:**
|
||||
- **Comment**: General feedback or questions
|
||||
- **Approve**: Changes look good
|
||||
- **Request Changes**: Must be addressed before merge
|
||||
|
||||
**Prefix conventions:**
|
||||
- `nit:` Minor issue (non-blocking)
|
||||
- `suggestion:` Recommended improvement
|
||||
- `question:` Seeking clarification
|
||||
- `blocker:` Must be fixed
|
||||
- `praise:` Highlighting good work
|
||||
|
||||
#### Examples
|
||||
|
||||
```markdown
|
||||
nit: Consider renaming `getData` to `fetchUserData` for clarity
|
||||
|
||||
suggestion: This could be simplified using Array.reduce()
|
||||
|
||||
question: Is this intentionally not handling the error case?
|
||||
|
||||
blocker: This SQL is vulnerable to injection. Please use parameterized queries.
|
||||
|
||||
praise: Excellent test coverage! 👍
|
||||
```
|
||||
|
||||
## Review Process
|
||||
|
||||
### Timeline
|
||||
|
||||
#### Expected Response Times
|
||||
- **Initial review**: Within 2-3 business days
|
||||
- **Follow-up review**: Within 1-2 business days
|
||||
- **Critical fixes**: ASAP (tag in Slack)
|
||||
|
||||
#### Escalation
|
||||
If no response after 3 days:
|
||||
1. Ping reviewer in PR comments
|
||||
2. Ask in #development Slack channel
|
||||
3. Tag @apache/superset-committers
|
||||
|
||||
### Approval Requirements
|
||||
|
||||
#### Minimum Requirements
|
||||
- **1 approval** from a committer for minor changes
|
||||
- **2 approvals** for significant features
|
||||
- **3 approvals** for breaking changes
|
||||
|
||||
#### Special Cases
|
||||
- **Security changes**: Require security team review
|
||||
- **API changes**: Require API team review
|
||||
- **Database migrations**: Require database expert review
|
||||
- **UI/UX changes**: Require design review
|
||||
|
||||
### Merge Process
|
||||
|
||||
#### Who Can Merge
|
||||
- Committers with write access
|
||||
- After all requirements met
|
||||
- CI checks must pass
|
||||
|
||||
#### Merge Methods
|
||||
- **Squash and merge**: Default for feature PRs
|
||||
- **Rebase and merge**: For clean history
|
||||
- **Create merge commit**: Rarely used
|
||||
|
||||
#### Merge Checklist
|
||||
- [ ] All CI checks green
|
||||
- [ ] Required approvals obtained
|
||||
- [ ] No unresolved conversations
|
||||
- [ ] PR title follows conventions
|
||||
- [ ] Milestone set (if applicable)
|
||||
|
||||
## Review Etiquette
|
||||
|
||||
### Do's
|
||||
- ✅ Be kind and constructive
|
||||
- ✅ Acknowledge time and effort
|
||||
- ✅ Provide specific examples
|
||||
- ✅ Suggest solutions
|
||||
- ✅ Praise good work
|
||||
- ✅ Consider cultural differences
|
||||
- ✅ Focus on the code, not the person
|
||||
|
||||
### Don'ts
|
||||
- ❌ Use harsh or dismissive language
|
||||
- ❌ Bikeshed on minor preferences
|
||||
- ❌ Review when tired or frustrated
|
||||
- ❌ Make personal attacks
|
||||
- ❌ Ignore the PR description
|
||||
- ❌ Demand perfection
|
||||
|
||||
## Becoming a Reviewer
|
||||
|
||||
### Path to Reviewer
|
||||
1. **Contribute regularly**: Submit quality PRs
|
||||
2. **Participate in discussions**: Share knowledge
|
||||
3. **Review others' code**: Start with comments
|
||||
4. **Build expertise**: Focus on specific areas
|
||||
5. **Get nominated**: By existing committers
|
||||
|
||||
### Reviewer Expectations
|
||||
- Review PRs in your area of expertise
|
||||
- Respond within reasonable time
|
||||
- Mentor new contributors
|
||||
- Maintain high standards
|
||||
- Stay current with best practices
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Reviewing Large PRs
|
||||
|
||||
#### Strategy
|
||||
1. **Request splitting**: Ask to break into smaller PRs
|
||||
2. **Review in phases**:
|
||||
- Architecture/approach first
|
||||
- Implementation details second
|
||||
- Tests and docs last
|
||||
3. **Use draft reviews**: Save comments and submit together
|
||||
|
||||
### Cross-Team Reviews
|
||||
|
||||
#### When Needed
|
||||
- Changes affecting multiple teams
|
||||
- Shared components/libraries
|
||||
- API contract changes
|
||||
- Database schema changes
|
||||
|
||||
### Performance Reviews
|
||||
|
||||
#### Tools
|
||||
```python
|
||||
# Backend performance
|
||||
import cProfile
|
||||
import pstats
|
||||
|
||||
# Profile the code
|
||||
cProfile.run('function_to_profile()', 'stats.prof')
|
||||
stats = pstats.Stats('stats.prof')
|
||||
stats.sort_stats('cumulative').print_stats(10)
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Frontend performance
|
||||
// Use React DevTools Profiler
|
||||
// Chrome DevTools Performance tab
|
||||
// Lighthouse audits
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### Internal
|
||||
- [Coding Guidelines](../guidelines/design-guidelines)
|
||||
- [Testing Guide](../testing/overview)
|
||||
- [Extension Architecture](../extensions/architecture)
|
||||
|
||||
### External
|
||||
- [Google's Code Review Guide](https://google.github.io/eng-practices/review/)
|
||||
- [Best Practices for Code Review](https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/)
|
||||
- [The Art of Readable Code](https://www.oreilly.com/library/view/the-art-of/9781449318482/)
|
||||
|
||||
Next: [Reporting issues effectively](./issue-reporting)
|
||||
1143
docs/developer_docs/contributing/development-setup.md
Normal file
1143
docs/developer_docs/contributing/development-setup.md
Normal file
File diff suppressed because it is too large
Load Diff
415
docs/developer_docs/contributing/guidelines.md
Normal file
415
docs/developer_docs/contributing/guidelines.md
Normal file
@@ -0,0 +1,415 @@
|
||||
---
|
||||
title: Contribution Guidelines
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Contribution Guidelines
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
A philosophy we would like to strongly encourage is
|
||||
|
||||
> Before creating a PR, create an issue.
|
||||
|
||||
The purpose is to separate problem from possible solutions.
|
||||
|
||||
**Bug fixes:** If you're only fixing a small bug, it's fine to submit a pull request right away but we highly recommend filing an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. Please keep in mind that the project maintainers reserve the rights to accept or reject incoming PRs, so it is better to separate the issue and the code to fix it from each other. In some cases, project maintainers may request you to create a separate issue from PR before proceeding.
|
||||
|
||||
**Refactor:** For small refactors, it can be a standalone PR itself detailing what you are refactoring and why. If there are concerns, project maintainers may request you to create a `#SIP` for the PR before proceeding.
|
||||
|
||||
**Feature/Large changes:** If you intend to change the public API, or make any non-trivial changes to the implementation, we require you to file a new issue as `#SIP` (Superset Improvement Proposal). This lets us reach an agreement on your proposal before you put significant effort into it. You are welcome to submit a PR along with the SIP (sometimes necessary for demonstration), but we will not review/merge the code until the SIP is approved.
|
||||
|
||||
In general, small PRs are always easier to review than large PRs. The best practice is to break your work into smaller independent PRs and refer to the same issue. This will greatly reduce turnaround time.
|
||||
|
||||
If you wish to share your work which is not ready to merge yet, create a [Draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/). This will enable maintainers and the CI runner to prioritize mature PR's.
|
||||
|
||||
Finally, never submit a PR that will put master branch in broken state. If the PR is part of multiple PRs to complete a large feature and cannot work on its own, you can create a feature branch and merge all related PRs into the feature branch before creating a PR from feature branch to master.
|
||||
|
||||
### Protocol
|
||||
|
||||
#### Authoring
|
||||
|
||||
- Fill in all sections of the PR template.
|
||||
- Title the PR with one of the following semantic prefixes (inspired by [Karma](http://karma-runner.github.io/0.10/dev/git-commit-msg.html])):
|
||||
|
||||
- `feat` (new feature)
|
||||
- `fix` (bug fix)
|
||||
- `docs` (changes to the documentation)
|
||||
- `style` (formatting, missing semi colons, etc; no application logic change)
|
||||
- `refactor` (refactoring code)
|
||||
- `test` (adding missing tests, refactoring tests; no application logic change)
|
||||
- `chore` (updating tasks etc; no application logic change)
|
||||
- `perf` (performance-related change)
|
||||
- `build` (build tooling, Docker configuration change)
|
||||
- `ci` (test runner, GitHub Actions workflow changes)
|
||||
- `other` (changes that don't correspond to the above -- should be rare!)
|
||||
- Examples:
|
||||
- `feat: export charts as ZIP files`
|
||||
- `perf(api): improve API info performance`
|
||||
- `fix(chart-api): cached-indicator always shows value is cached`
|
||||
|
||||
- Add prefix `[WIP]` to title if not ready for review (WIP = work-in-progress). We recommend creating a PR with `[WIP]` first and remove it once you have passed CI test and read through your code changes at least once.
|
||||
- If you believe your PR contributes a potentially breaking change, put a `!` after the semantic prefix but before the colon in the PR title, like so: `feat!: Added foo functionality to bar`
|
||||
- **Screenshots/GIFs:** Changes to user interface require before/after screenshots, or GIF for interactions
|
||||
- Recommended capture tools ([Kap](https://getkap.co/), [LICEcap](https://www.cockos.com/licecap/), [Skitch](https://download.cnet.com/Skitch/3000-13455_4-189876.html))
|
||||
- If no screenshot is provided, the committers will mark the PR with `need:screenshot` label and will not review until screenshot is provided.
|
||||
- **Dependencies:** Be careful about adding new dependency and avoid unnecessary dependencies.
|
||||
- For Python, include it in `pyproject.toml` denoting any specific restrictions and
|
||||
in `requirements.txt` pinned to a specific version which ensures that the application
|
||||
build is deterministic.
|
||||
- For TypeScript/JavaScript, include new libraries in `package.json`
|
||||
- **Tests:** The pull request should include tests, either as doctests, unit tests, or both. Make sure to resolve all errors and test failures. See [Testing](./howtos#testing) for how to run tests.
|
||||
- **Documentation:** If the pull request adds functionality, the docs should be updated as part of the same PR.
|
||||
- **CI:** Reviewers will not review the code until all CI tests are passed. Sometimes there can be flaky tests. You can close and open PR to re-run CI test. Please report if the issue persists. After the CI fix has been deployed to `master`, please rebase your PR.
|
||||
- **Code coverage:** Please ensure that code coverage does not decrease.
|
||||
- Remove `[WIP]` when ready for review. Please note that it may be merged soon after approved so please make sure the PR is ready to merge and do not expect more time for post-approval edits.
|
||||
- If the PR was not ready for review and inactive for > 30 days, we will close it due to inactivity. The author is welcome to re-open and update.
|
||||
|
||||
#### Reviewing
|
||||
|
||||
- Use constructive tone when writing reviews.
|
||||
- If there are changes required, state clearly what needs to be done before the PR can be approved.
|
||||
- If you are asked to update your pull request with some changes there's no need to create a new one. Push your changes to the same branch.
|
||||
- The committers reserve the right to reject any PR and in some cases may request the author to file an issue.
|
||||
|
||||
#### Test Environments
|
||||
|
||||
- Members of the Apache GitHub org can launch an ephemeral test environment directly on a pull request by creating a comment containing (only) the command `/testenv up`.
|
||||
- Note that org membership must be public in order for this validation to function properly.
|
||||
- Feature flags may be set for a test environment by specifying the flag name (prefixed with `FEATURE_`) and value after the command.
|
||||
- Format: `/testenv up FEATURE_<feature flag name>=true|false`
|
||||
- Example: `/testenv up FEATURE_DASHBOARD_NATIVE_FILTERS=true`
|
||||
- Multiple feature flags may be set in single command, separated by whitespace
|
||||
- A comment will be created by the workflow script with the address and login information for the ephemeral environment.
|
||||
- Test environments may be created once the Docker build CI workflow for the PR has completed successfully.
|
||||
- Test environments do not currently update automatically when new commits are added to a pull request.
|
||||
- Test environments do not currently support async workers, though this is planned.
|
||||
- Running test environments will be shutdown upon closing the pull request.
|
||||
|
||||
You can also access per-PR ephemeral environment directly using the following URL pattern:
|
||||
`https://pr-{PR_NUMBER}.superset.apache.org`
|
||||
|
||||
#### Merging
|
||||
|
||||
- At least one approval is required for merging a PR.
|
||||
- PR is usually left open for at least 24 hours before merging.
|
||||
- After the PR is merged, [close the corresponding issue(s)](https://help.github.com/articles/closing-issues-using-keywords/).
|
||||
|
||||
#### Post-merge Responsibility
|
||||
|
||||
- Project maintainers may contact the PR author if new issues are introduced by the PR.
|
||||
- Project maintainers may revert your changes if a critical issue is found, such as breaking master branch CI.
|
||||
|
||||
## Managing Issues and PRs
|
||||
|
||||
To handle issues and PRs that are coming in, committers read issues/PRs and flag them with labels to categorize and help contributors spot where to take actions, as contributors usually have different expertises.
|
||||
|
||||
Triaging goals
|
||||
|
||||
- **For issues:** Categorize, screen issues, flag required actions from authors.
|
||||
- **For PRs:** Categorize, flag required actions from authors. If PR is ready for review, flag required actions from reviewers.
|
||||
|
||||
First, add **Category labels (a.k.a. hash labels)**. Every issue/PR must have one hash label (except spam entry). Labels that begin with `#` defines issue/PR type:
|
||||
|
||||
| Label | for Issue | for PR |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `#bug` | Bug report | Bug fix |
|
||||
| `#code-quality` | Describe problem with code, architecture or productivity | Refactor, tests, tooling |
|
||||
| `#feature` | New feature request | New feature implementation |
|
||||
| `#refine` | Propose improvement such as adjusting padding or refining UI style, excluding new features, bug fixes, and refactoring. | Implementation of improvement such as adjusting padding or refining UI style, excluding new features, bug fixes, and refactoring. |
|
||||
| `#doc` | Documentation | Documentation |
|
||||
| `#question` | Troubleshooting: Installation, Running locally, Ask how to do something. Can be changed to `#bug` later. | N/A |
|
||||
| `#SIP` | Superset Improvement Proposal | N/A |
|
||||
| `#ASF` | Tasks related to Apache Software Foundation policy | Tasks related to Apache Software Foundation policy |
|
||||
|
||||
Then add other types of labels as appropriate.
|
||||
|
||||
- **Descriptive labels (a.k.a. dot labels):** These labels that begin with `.` describe the details of the issue/PR, such as `.ui`, `.js`, `.install`, `.backend`, etc. Each issue/PR can have zero or more dot labels.
|
||||
- **Need labels:** These labels have pattern `need:xxx`, which describe the work required to progress, such as `need:rebase`, `need:update`, `need:screenshot`.
|
||||
- **Risk labels:** These labels have pattern `risk:xxx`, which describe the potential risk on adopting the work, such as `risk:db-migration`. The intention was to better understand the impact and create awareness for PRs that need more rigorous testing.
|
||||
- **Status labels:** These labels describe the status (`abandoned`, `wontfix`, `cant-reproduce`, etc.) Issue/PRs that are rejected or closed without completion should have one or more status labels.
|
||||
- **Version labels:** These have the pattern `vx.x` such as `v0.28`. Version labels on issues describe the version the bug was reported on. Version labels on PR describe the first release that will include the PR.
|
||||
|
||||
Committers may also update title to reflect the issue/PR content if the author-provided title is not descriptive enough.
|
||||
|
||||
If the PR passes CI tests and does not have any `need:` labels, it is ready for review, add label `review` and/or `design-review`.
|
||||
|
||||
If an issue/PR has been inactive for at least 30 days, it will be closed. If it does not have any status label, add `inactive`.
|
||||
|
||||
When creating a PR, if you're aiming to have it included in a specific release, please tag it with the version label. For example, to have a PR considered for inclusion in Superset 1.1 use the label `v1.1`.
|
||||
|
||||
## Revert Guidelines
|
||||
|
||||
Reverting changes that are causing issues in the master branch is a normal and expected part of the development process. In an open source community, the ramifications of a change cannot always be fully understood. With that in mind, here are some considerations to keep in mind when considering a revert:
|
||||
|
||||
- **Availability of the PR author:** If the original PR author or the engineer who merged the code is highly available and can provide a fix in a reasonable time frame, this would counter-indicate reverting.
|
||||
- **Severity of the issue:** How severe is the problem on master? Is it keeping the project from moving forward? Is there user impact? What percentage of users will experience a problem?
|
||||
- **Size of the change being reverted:** Reverting a single small PR is a much lower-risk proposition than reverting a massive, multi-PR change.
|
||||
- **Age of the change being reverted:** Reverting a recently-merged PR will be more acceptable than reverting an older PR. A bug discovered in an older PR is unlikely to be causing widespread serious issues.
|
||||
- **Risk inherent in reverting:** Will the reversion break critical functionality? Is the medicine more dangerous than the disease?
|
||||
- **Difficulty of crafting a fix:** In the case of issues with a clear solution, it may be preferable to implement and merge a fix rather than a revert.
|
||||
|
||||
Should you decide that reverting is desirable, it is the responsibility of the Contributor performing the revert to:
|
||||
|
||||
- **Contact the interested parties:** The PR's author and the engineer who merged the work should both be contacted and informed of the revert.
|
||||
- **Provide concise reproduction steps:** Ensure that the issue can be clearly understood and duplicated by the original author of the PR.
|
||||
- **Put the revert through code review:** The revert must be approved by another committer.
|
||||
|
||||
**Revert liberally to keep `master` stable**:
|
||||
- Build failures
|
||||
- Test failures
|
||||
- Critical bugs in production
|
||||
- Security vulnerabilities
|
||||
|
||||
**How to revert**:
|
||||
1. Use GitHub's revert button when possible
|
||||
2. Create a PR with clear explanation
|
||||
3. Tag the original author
|
||||
4. Work with them on a fix
|
||||
|
||||
## Design Guidelines
|
||||
|
||||
### Capitalization Guidelines
|
||||
|
||||
#### Sentence case
|
||||
|
||||
Use sentence-case capitalization for everything in the UI (except these **).
|
||||
|
||||
Sentence case is predominantly lowercase. Capitalize only the initial character of the first word, and other words that require capitalization, like:
|
||||
|
||||
- **Proper nouns.** Objects in the product _are not_ considered proper nouns e.g. dashboards, charts, saved queries etc. Proprietary feature names eg. SQL Lab, Preset Manager _are_ considered proper nouns
|
||||
- **Acronyms** (e.g. CSS, HTML)
|
||||
- When referring to **UI labels that are themselves capitalized** from sentence case (e.g. page titles - Dashboards page, Charts page, Saved queries page, etc.)
|
||||
- User input that is reflected in the UI. E.g. a user-named a dashboard tab
|
||||
|
||||
**Sentence case vs. Title case:**
|
||||
Title case: "A Dog Takes a Walk in Paris"
|
||||
Sentence case: "A dog takes a walk in Paris"
|
||||
|
||||
**Why sentence case?**
|
||||
|
||||
- It's generally accepted as the quickest to read
|
||||
- It's the easiest form to distinguish between common and proper nouns
|
||||
|
||||
**Good examples:**
|
||||
- "Select a database"
|
||||
- "Create new chart"
|
||||
- "View all dashboards"
|
||||
|
||||
**Bad examples:**
|
||||
- "Select a Database"
|
||||
- "Create New Chart"
|
||||
- "View All Dashboards"
|
||||
|
||||
#### How to refer to UI elements
|
||||
|
||||
When writing about a UI element, use the same capitalization as used in the UI.
|
||||
|
||||
For example, if an input field is labeled "Name" then you refer to this as the "Name input field". Similarly, if a button has the label "Save" in it, then it is correct to refer to the "Save button".
|
||||
|
||||
Where a product page is titled "Settings", you refer to this in writing as follows:
|
||||
"Edit your personal information on the Settings page".
|
||||
|
||||
Often a product page will have the same title as the objects it contains. In this case, refer to the page as it appears in the UI, and the objects as common nouns:
|
||||
|
||||
- Upload a dashboard on the Dashboards page
|
||||
- Go to Dashboards
|
||||
- View dashboard
|
||||
- View all dashboards
|
||||
- Upload CSS templates on the CSS templates page
|
||||
- Queries that you save will appear on the Saved queries page
|
||||
- Create custom queries in SQL Lab then create dashboards
|
||||
|
||||
When writing about UI elements:
|
||||
- Use **bold** for clickable elements: "Click **Save**"
|
||||
- Use quotes for text fields: 'Enter "My Dashboard" in the name field'
|
||||
- Be specific about element types: button, link, dropdown, etc.
|
||||
|
||||
#### **Exceptions to sentence case
|
||||
|
||||
Only use title case for:
|
||||
- Product names (Apache Superset)
|
||||
- Proper nouns
|
||||
- Acronyms (SQL, API, CSV)
|
||||
- Input labels, buttons and UI tabs are all caps
|
||||
- User input values (e.g. column names, SQL Lab tab names) should be in their original case
|
||||
|
||||
## Programming Language Conventions
|
||||
|
||||
### Python
|
||||
|
||||
We use:
|
||||
- **[Ruff](https://docs.astral.sh/ruff/)** for linting and formatting
|
||||
- **[Mypy](http://mypy-lang.org/)** for type checking
|
||||
|
||||
Python code should:
|
||||
- Follow PEP 8
|
||||
- Use type hints for all new code
|
||||
- Use descriptive variable names
|
||||
- Include docstrings for modules, classes, and functions
|
||||
- Handle exceptions appropriately
|
||||
- Avoid global variables
|
||||
|
||||
Parameters in the `config.py` (which are accessible via the Flask app.config dictionary) are
|
||||
assumed to always be defined and thus should be accessed directly via,
|
||||
|
||||
```python
|
||||
blueprints = app.config["BLUEPRINTS"]
|
||||
```
|
||||
|
||||
rather than,
|
||||
|
||||
```python
|
||||
blueprints = app.config.get("BLUEPRINTS")
|
||||
```
|
||||
|
||||
or similar as the later will cause typing issues. The former is of type `List[Callable]`
|
||||
whereas the later is of type `Optional[List[Callable]]`.
|
||||
|
||||
#### Typing / Type Hints
|
||||
|
||||
All new Python code should include type hints:
|
||||
|
||||
To ensure clarity, consistency, all readability, _all_ new functions should use
|
||||
[type hints](https://docs.python.org/3/library/typing.html) and include a
|
||||
docstring.
|
||||
|
||||
Note per [PEP-484](https://www.python.org/dev/peps/pep-0484/#exceptions) no
|
||||
syntax for listing explicitly raised exceptions is proposed and thus the
|
||||
recommendation is to put this information in a docstring, i.e.,
|
||||
|
||||
```python
|
||||
import math
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
|
||||
|
||||
def sqrt(x: Union[float, int]) -> Union[float, int]:
|
||||
"""
|
||||
Return the square root of x.
|
||||
|
||||
:param x: A number
|
||||
:returns: The square root of the given number
|
||||
:raises ValueError: If the number is negative
|
||||
"""
|
||||
|
||||
return math.sqrt(x)
|
||||
|
||||
|
||||
def process_data(
|
||||
data: List[Dict[str, Any]],
|
||||
filter_empty: bool = True
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Process a list of data dictionaries.
|
||||
|
||||
Args:
|
||||
data: List of dictionaries containing data
|
||||
filter_empty: Whether to filter empty entries
|
||||
|
||||
Returns:
|
||||
Processed data dictionary or None if no valid data
|
||||
"""
|
||||
if not data:
|
||||
return None
|
||||
|
||||
# Process data...
|
||||
return processed_data
|
||||
```
|
||||
|
||||
Use `mypy` to check types:
|
||||
```bash
|
||||
mypy superset
|
||||
```
|
||||
|
||||
### TypeScript
|
||||
|
||||
We use:
|
||||
- **ESLint** for linting
|
||||
- **Prettier** for formatting
|
||||
- **TypeScript** strict mode
|
||||
|
||||
TypeScript is fully supported and is the recommended language for writing all new frontend
|
||||
components. When modifying existing functions/components, migrating to TypeScript is
|
||||
appreciated, but not required. Examples of migrating functions/components to TypeScript can be
|
||||
found in [#9162](https://github.com/apache/superset/pull/9162) and [#9180](https://github.com/apache/superset/pull/9180).
|
||||
|
||||
TypeScript code should:
|
||||
- Avoid `any` types - use proper TypeScript types
|
||||
- Use functional components with hooks for React
|
||||
- Include JSDoc comments for complex functions
|
||||
- Use consistent naming conventions
|
||||
- Handle errors appropriately
|
||||
|
||||
Example:
|
||||
```typescript
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email?: string;
|
||||
}
|
||||
|
||||
export function processUser(user: User): string {
|
||||
// Avoid using 'any'
|
||||
const { name, email } = user;
|
||||
return email ? `${name} <${email}>` : name;
|
||||
}
|
||||
```
|
||||
|
||||
## Additional Guidelines
|
||||
|
||||
### Commit Messages
|
||||
|
||||
- Use clear, descriptive commit messages
|
||||
- Start with a verb in imperative mood
|
||||
- Reference issue numbers when applicable
|
||||
|
||||
Good: "Fix dashboard filter bug when dataset is deleted"
|
||||
Bad: "Fixed stuff"
|
||||
|
||||
### Code Review Etiquette
|
||||
|
||||
- Be respectful and constructive
|
||||
- Focus on the code, not the person
|
||||
- Provide specific suggestions for improvement
|
||||
- Acknowledge good work
|
||||
- Be open to different approaches
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update docs for any user-facing changes
|
||||
- Include code examples where helpful
|
||||
- Keep language clear and concise
|
||||
- Test documentation changes locally
|
||||
|
||||
### Security
|
||||
|
||||
- Never commit secrets or credentials
|
||||
- Validate all user input
|
||||
- Use parameterized queries for SQL
|
||||
- Follow OWASP guidelines
|
||||
- Report security issues privately to private@superset.apache.org
|
||||
|
||||
## Questions?
|
||||
|
||||
If you have questions about these guidelines, ask in:
|
||||
- [Slack #development](https://apache-superset.slack.com)
|
||||
- [GitHub Discussions](https://github.com/apache/superset/discussions)
|
||||
593
docs/developer_docs/contributing/howtos.md
Normal file
593
docs/developer_docs/contributing/howtos.md
Normal file
@@ -0,0 +1,593 @@
|
||||
---
|
||||
title: Development How-tos
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Development How-tos
|
||||
|
||||
This guide contains specific instructions for common development tasks in Superset.
|
||||
|
||||
## Contributing to Documentation
|
||||
|
||||
The documentation site is built using [Docusaurus](https://docusaurus.io/). All documentation lives in the `docs` folder, written in Markdown format.
|
||||
|
||||
### Local Development
|
||||
|
||||
To set up your local environment for documentation development:
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
|
||||
The site will be available at http://localhost:3000
|
||||
|
||||
### Build
|
||||
|
||||
To create a production build:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm run serve # Test the build locally
|
||||
```
|
||||
|
||||
### Deployment
|
||||
|
||||
Documentation is automatically deployed when changes are merged to master.
|
||||
|
||||
## Creating Visualization Plugins
|
||||
|
||||
Visualization plugins allow you to add custom chart types to Superset. They are built as npm packages that integrate with the Superset frontend.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm or yarn
|
||||
- A local Superset development environment
|
||||
|
||||
### Creating a simple Hello World viz plugin
|
||||
|
||||
1. **Install the Superset Yeoman generator**:
|
||||
```bash
|
||||
npm install -g @superset-ui/generator-superset
|
||||
```
|
||||
|
||||
2. **Create a new plugin**:
|
||||
```bash
|
||||
mkdir superset-plugin-chart-hello-world
|
||||
cd superset-plugin-chart-hello-world
|
||||
yo @superset-ui/superset
|
||||
```
|
||||
|
||||
3. **Follow the prompts**:
|
||||
- Package name: `superset-plugin-chart-hello-world`
|
||||
- Chart type: Choose your preferred type
|
||||
- Include storybook: Yes (recommended for development)
|
||||
|
||||
4. **Develop your plugin**:
|
||||
The generator creates a complete plugin structure with TypeScript, React components, and build configuration.
|
||||
|
||||
5. **Test your plugin locally**:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
6. **Link to your local Superset**:
|
||||
```bash
|
||||
npm link
|
||||
# In your Superset frontend directory:
|
||||
npm link superset-plugin-chart-hello-world
|
||||
```
|
||||
|
||||
7. **Import and register in Superset**:
|
||||
Edit `superset-frontend/src/visualizations/presets/MainPreset.ts` to include your plugin.
|
||||
|
||||
## Testing
|
||||
|
||||
### Python Testing
|
||||
|
||||
Run Python tests using pytest:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/unit_tests/test_specific.py
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=superset
|
||||
|
||||
# Run only unit tests
|
||||
pytest tests/unit_tests
|
||||
|
||||
# Run only integration tests
|
||||
pytest tests/integration_tests
|
||||
```
|
||||
|
||||
#### Testing with local Presto connections
|
||||
|
||||
To test against Presto:
|
||||
|
||||
```bash
|
||||
# Start Presto locally using Docker
|
||||
docker run -p 8080:8080 \
|
||||
--name presto \
|
||||
-d prestodb/presto
|
||||
|
||||
# Configure in superset_config.py
|
||||
SQLALCHEMY_DATABASE_URI = 'presto://localhost:8080/hive/default'
|
||||
```
|
||||
|
||||
### Frontend Testing
|
||||
|
||||
Run frontend tests using Jest:
|
||||
|
||||
```bash
|
||||
cd superset-frontend
|
||||
|
||||
# Run all tests
|
||||
npm run test
|
||||
|
||||
# Run with coverage
|
||||
npm run test -- --coverage
|
||||
|
||||
# Run in watch mode
|
||||
npm run test -- --watch
|
||||
|
||||
# Run specific test file
|
||||
npm run test -- MyComponent.test.tsx
|
||||
```
|
||||
|
||||
### E2E Integration Testing
|
||||
|
||||
We support both Playwright (recommended) and Cypress for end-to-end testing.
|
||||
|
||||
#### Playwright (Recommended - NEW)
|
||||
|
||||
Playwright is our new E2E testing framework, gradually replacing Cypress.
|
||||
|
||||
```bash
|
||||
# Navigate to frontend directory
|
||||
cd superset-frontend
|
||||
|
||||
# Run all Playwright tests
|
||||
npm run playwright:test
|
||||
# or: npx playwright test
|
||||
|
||||
# Run with interactive UI for debugging
|
||||
npm run playwright:ui
|
||||
# or: npx playwright test --ui
|
||||
|
||||
# Run in headed mode (see browser)
|
||||
npm run playwright:headed
|
||||
# or: npx playwright test --headed
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test tests/auth/login.spec.ts
|
||||
|
||||
# Run with debug mode (step through tests)
|
||||
npm run playwright:debug tests/auth/login.spec.ts
|
||||
# or: npx playwright test --debug tests/auth/login.spec.ts
|
||||
|
||||
# Generate test report
|
||||
npm run playwright:report
|
||||
```
|
||||
|
||||
#### Cypress (DEPRECATED - will be removed)
|
||||
|
||||
Cypress is being phased out in favor of Playwright but is still available:
|
||||
|
||||
```bash
|
||||
# Set base URL for Cypress
|
||||
export CYPRESS_BASE_URL='http://localhost:8088'
|
||||
export CYPRESS_DATABASE=test
|
||||
export CYPRESS_USERNAME=admin
|
||||
export CYPRESS_PASSWORD=admin
|
||||
|
||||
# Navigate to Cypress directory
|
||||
cd superset-frontend/cypress-base
|
||||
|
||||
# Run interactively
|
||||
npm run cypress-debug
|
||||
|
||||
# Run headless (like CI)
|
||||
npm run cypress-run-chrome
|
||||
|
||||
# Run specific file
|
||||
npm run cypress-run-chrome -- --spec "cypress/e2e/dashboard/dashboard.test.ts"
|
||||
```
|
||||
|
||||
### Debugging Server App
|
||||
|
||||
For debugging the Flask backend:
|
||||
|
||||
#### Using PyCharm/IntelliJ
|
||||
|
||||
1. Create a new Python configuration
|
||||
2. Set script path to `superset/app.py`
|
||||
3. Set environment variables:
|
||||
- `FLASK_ENV=development`
|
||||
- `SUPERSET_CONFIG_PATH=/path/to/superset_config.py`
|
||||
4. Set breakpoints and run in debug mode
|
||||
|
||||
#### Using VS Code
|
||||
|
||||
1. Add to `.vscode/launch.json`:
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Flask",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "flask",
|
||||
"env": {
|
||||
"FLASK_APP": "superset/app.py",
|
||||
"FLASK_ENV": "development"
|
||||
},
|
||||
"args": ["run", "--no-debugger", "--no-reload"],
|
||||
"jinja": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. Set breakpoints and press F5 to debug
|
||||
|
||||
### Debugging Server App in Kubernetes Environment
|
||||
|
||||
To debug Flask running in a POD inside a kubernetes cluster, you'll need to make sure the pod runs as root and is granted the `SYS_PTRACE` capability. These settings should not be used in production environments.
|
||||
|
||||
```yaml
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["SYS_PTRACE"]
|
||||
```
|
||||
|
||||
See [set capabilities for a container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container) for more details.
|
||||
|
||||
Once the pod is running as root and has the `SYS_PTRACE` capability it will be able to debug the Flask app.
|
||||
|
||||
You can follow the same instructions as in `docker compose`. Enter the pod and install the required library and packages: gdb, netstat and debugpy.
|
||||
|
||||
Often in a Kubernetes environment nodes are not addressable from outside the cluster. VSCode will thus be unable to remotely connect to port 5678 on a Kubernetes node. In order to do this you need to create a tunnel that port forwards 5678 to your local machine.
|
||||
|
||||
```bash
|
||||
kubectl port-forward pod/superset-<some random id> 5678:5678
|
||||
```
|
||||
|
||||
You can now launch your VSCode debugger with the same config as above. VSCode will connect to 127.0.0.1:5678 which is forwarded by kubectl to your remote kubernetes POD.
|
||||
|
||||
### Storybook
|
||||
|
||||
See the dedicated [Storybook documentation](../testing/storybook) for information on running Storybook locally and adding new stories.
|
||||
|
||||
## Contributing Translations
|
||||
|
||||
Superset uses Flask-Babel for internationalization.
|
||||
|
||||
### Enabling language selection
|
||||
|
||||
Edit `superset_config.py`:
|
||||
|
||||
```python
|
||||
LANGUAGES = {
|
||||
'en': {'flag': 'us', 'name': 'English'},
|
||||
'fr': {'flag': 'fr', 'name': 'French'},
|
||||
'zh': {'flag': 'cn', 'name': 'Chinese'},
|
||||
}
|
||||
```
|
||||
|
||||
### Creating a new language dictionary
|
||||
|
||||
```bash
|
||||
# Initialize a new language
|
||||
pybabel init -i superset/translations/messages.pot -d superset/translations -l de
|
||||
```
|
||||
|
||||
### Extracting new strings for translation
|
||||
|
||||
```bash
|
||||
# Extract Python strings
|
||||
pybabel extract -F babel.cfg -o superset/translations/messages.pot -k lazy_gettext superset
|
||||
|
||||
# Extract JavaScript strings
|
||||
npm run build-translation
|
||||
```
|
||||
|
||||
### Updating language files
|
||||
|
||||
```bash
|
||||
# Update all language files with new strings
|
||||
pybabel update -i superset/translations/messages.pot -d superset/translations
|
||||
```
|
||||
|
||||
### Applying translations
|
||||
|
||||
```bash
|
||||
# Frontend
|
||||
cd superset-frontend
|
||||
npm run build-translation
|
||||
|
||||
# Backend
|
||||
pybabel compile -d superset/translations
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
### Python
|
||||
|
||||
We use Ruff for Python linting and formatting:
|
||||
|
||||
```bash
|
||||
# Auto-format using ruff
|
||||
ruff format .
|
||||
|
||||
# Lint check with ruff
|
||||
ruff check .
|
||||
|
||||
# Lint fix with ruff
|
||||
ruff check --fix .
|
||||
```
|
||||
|
||||
Pre-commit hooks run automatically on `git commit` if installed.
|
||||
|
||||
### TypeScript / JavaScript
|
||||
|
||||
We use a hybrid linting approach combining OXC (Oxidation Compiler) for standard rules and a custom AST-based checker for Superset-specific patterns.
|
||||
|
||||
#### Quick Commands
|
||||
|
||||
```bash
|
||||
cd superset-frontend
|
||||
|
||||
# Run both OXC and custom rules
|
||||
npm run lint:full
|
||||
|
||||
# Run OXC linter only (faster for most checks)
|
||||
npm run lint
|
||||
|
||||
# Fix auto-fixable issues with OXC
|
||||
npm run lint-fix
|
||||
|
||||
# Run custom rules checker only
|
||||
npm run check:custom-rules
|
||||
|
||||
# Run tsc (typescript) checks
|
||||
npm run type
|
||||
|
||||
# Format with Prettier
|
||||
npm run prettier
|
||||
```
|
||||
|
||||
#### Architecture
|
||||
|
||||
The linting system consists of two components:
|
||||
|
||||
1. **OXC Linter** (`oxlint`) - A Rust-based linter that's 50-100x faster than ESLint
|
||||
- Handles all standard JavaScript/TypeScript rules
|
||||
- Configured via `oxlint.json`
|
||||
- Runs via `npm run lint` or `npm run lint-fix`
|
||||
|
||||
2. **Custom Rules Checker** - A Node.js AST-based checker for Superset-specific patterns
|
||||
- Enforces no literal colors (use theme colors)
|
||||
- Prevents FontAwesome usage (use @superset-ui/core Icons)
|
||||
- Validates i18n template usage (no template variables)
|
||||
- Runs via `npm run check:custom-rules`
|
||||
|
||||
#### Why This Approach?
|
||||
|
||||
- **50-100x faster linting** compared to ESLint for standard rules via OXC
|
||||
- **Apache-compatible** - No custom binaries, ASF-friendly
|
||||
- **Maintainable** - Custom rules in JavaScript, not Rust
|
||||
- **Flexible** - Can evolve as OXC adds plugin support
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
**"Plugin 'basic-custom-plugin' not found" Error**
|
||||
|
||||
Ensure you're using the explicit config:
|
||||
```bash
|
||||
npx oxlint --config oxlint.json
|
||||
```
|
||||
|
||||
**Custom Rules Not Running**
|
||||
|
||||
Verify the AST parsing dependencies are installed:
|
||||
```bash
|
||||
npm ls @babel/parser @babel/traverse glob
|
||||
```
|
||||
|
||||
#### Adding New Custom Rules
|
||||
|
||||
1. Edit `scripts/check-custom-rules.js`
|
||||
2. Add a new check function following the AST visitor pattern
|
||||
3. Call the function in `processFile()`
|
||||
4. Test with `npm run check:custom-rules`
|
||||
|
||||
## GitHub Ephemeral Environments
|
||||
|
||||
For every PR, an ephemeral environment is automatically deployed for testing.
|
||||
|
||||
Access pattern: `https://pr-{PR_NUMBER}.superset.apache.org`
|
||||
|
||||
Features:
|
||||
- Automatically deployed on PR creation/update
|
||||
- Includes sample data
|
||||
- Destroyed when PR is closed
|
||||
- Useful for UI/UX review
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Using Docker for Development
|
||||
|
||||
```bash
|
||||
# Rebuild specific service
|
||||
docker compose build superset
|
||||
|
||||
# View logs
|
||||
docker compose logs -f superset
|
||||
|
||||
# Execute commands in container
|
||||
docker compose exec superset bash
|
||||
|
||||
# Reset database
|
||||
docker compose down -v
|
||||
docker compose up
|
||||
```
|
||||
|
||||
### Hot Reloading
|
||||
|
||||
**Frontend**: Webpack dev server provides hot module replacement automatically.
|
||||
|
||||
**Backend**: Use Flask debug mode:
|
||||
```bash
|
||||
FLASK_ENV=development superset run -p 8088 --with-threads --reload
|
||||
```
|
||||
|
||||
### Performance Profiling
|
||||
|
||||
For Python profiling:
|
||||
```python
|
||||
# In superset_config.py
|
||||
PROFILING = True
|
||||
```
|
||||
|
||||
For React profiling:
|
||||
- Use React DevTools Profiler
|
||||
- Enable performance marks in Chrome DevTools
|
||||
|
||||
### Database Migrations
|
||||
|
||||
```bash
|
||||
# Create a new migration
|
||||
superset db migrate -m "Description of changes"
|
||||
|
||||
# Apply migrations
|
||||
superset db upgrade
|
||||
|
||||
# Downgrade
|
||||
superset db downgrade
|
||||
```
|
||||
|
||||
### Useful Aliases
|
||||
|
||||
Add to your shell profile:
|
||||
|
||||
```bash
|
||||
alias sdev='FLASK_ENV=development superset run -p 8088 --with-threads --reload'
|
||||
alias stest='pytest tests/unit_tests'
|
||||
alias slint='pre-commit run --all-files'
|
||||
alias sfront='cd superset-frontend && npm run dev-server'
|
||||
```
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Node/npm Issues
|
||||
|
||||
```bash
|
||||
# Clear npm cache
|
||||
npm cache clean --force
|
||||
|
||||
# Reinstall dependencies
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
### Python Environment Issues
|
||||
|
||||
```bash
|
||||
# Recreate virtual environment
|
||||
deactivate
|
||||
rm -rf venv
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements/development.txt
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
### Database Issues
|
||||
|
||||
```bash
|
||||
# Reset local database
|
||||
superset db downgrade -r base
|
||||
superset db upgrade
|
||||
superset init
|
||||
```
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
```bash
|
||||
# Find process using port
|
||||
lsof -i :8088
|
||||
# Kill process
|
||||
kill -9 [PID]
|
||||
```
|
||||
|
||||
## Reporting Security Vulnerabilities
|
||||
|
||||
Please report security vulnerabilities to **private@superset.apache.org**.
|
||||
|
||||
In the event a community member discovers a security flaw in Superset, it is important to follow the [Apache Security Guidelines](https://www.apache.org/security/committers.html) and release a fix as quickly as possible before public disclosure. Reporting security vulnerabilities through the usual GitHub Issues channel is not ideal as it will publicize the flaw before a fix can be applied.
|
||||
|
||||
## SQL Lab Async Configuration
|
||||
|
||||
It's possible to configure a local database to operate in `async` mode, to work on `async` related features.
|
||||
|
||||
To do this, you'll need to:
|
||||
|
||||
- Add an additional database entry. We recommend you copy the connection string from the database labeled `main`, and then enable `SQL Lab` and the features you want to use. Don't forget to check the `Async` box
|
||||
- Configure a results backend, here's a local `FileSystemCache` example, not recommended for production, but perfect for testing (stores cache in `/tmp`)
|
||||
|
||||
```python
|
||||
from flask_caching.backends.filesystemcache import FileSystemCache
|
||||
RESULTS_BACKEND = FileSystemCache('/tmp/sqllab')
|
||||
```
|
||||
|
||||
- Start up a celery worker
|
||||
|
||||
```bash
|
||||
celery --app=superset.tasks.celery_app:app worker -O fair
|
||||
```
|
||||
|
||||
Note that:
|
||||
- for changes that affect the worker logic, you'll have to restart the `celery worker` process for the changes to be reflected.
|
||||
- The message queue used is a `sqlite` database using the `SQLAlchemy` experimental broker. Ok for testing, but not recommended in production
|
||||
- In some cases, you may want to create a context that is more aligned to your production environment, and use the similar broker as well as results backend configuration
|
||||
|
||||
## Async Chart Queries
|
||||
|
||||
It's possible to configure database queries for charts to operate in `async` mode. This is especially useful for dashboards with many charts that may otherwise be affected by browser connection limits. To enable async queries for dashboards and Explore, the following dependencies are required:
|
||||
|
||||
- Redis 5.0+ (the feature utilizes [Redis Streams](https://redis.io/topics/streams-intro))
|
||||
- Cache backends enabled via the `CACHE_CONFIG` and `DATA_CACHE_CONFIG` config settings
|
||||
- Celery workers configured and running to process async tasks
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Check the [FAQ](https://superset.apache.org/docs/frequently-asked-questions)
|
||||
- Ask in [Slack](https://apache-superset.slack.com)
|
||||
- Search [GitHub Issues](https://github.com/apache/superset/issues)
|
||||
- Post in [GitHub Discussions](https://github.com/apache/superset/discussions)
|
||||
418
docs/developer_docs/contributing/issue-reporting.md
Normal file
418
docs/developer_docs/contributing/issue-reporting.md
Normal file
@@ -0,0 +1,418 @@
|
||||
---
|
||||
title: Issue Reporting
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Issue Reporting
|
||||
|
||||
Learn how to effectively report bugs and request features for Apache Superset.
|
||||
|
||||
## Before Opening an Issue
|
||||
|
||||
### Pre-Issue Checklist
|
||||
|
||||
1. **Search Existing Issues**
|
||||
```
|
||||
Search: https://github.com/apache/superset/issues
|
||||
- Use keywords from your error message
|
||||
- Check both open and closed issues
|
||||
- Look for similar problems
|
||||
```
|
||||
|
||||
2. **Check Documentation**
|
||||
- [User Documentation](https://superset.apache.org/docs/intro)
|
||||
- [FAQ](https://superset.apache.org/docs/frequently-asked-questions)
|
||||
- [Configuration Guide](https://superset.apache.org/docs/configuration/configuring-superset)
|
||||
|
||||
3. **Verify Version**
|
||||
```bash
|
||||
# Check Superset version
|
||||
superset version
|
||||
|
||||
# Try latest version
|
||||
pip install --upgrade apache-superset
|
||||
```
|
||||
|
||||
4. **Reproduce Consistently**
|
||||
- Can you reproduce the issue?
|
||||
- Does it happen every time?
|
||||
- What specific actions trigger it?
|
||||
|
||||
## Bug Reports
|
||||
|
||||
### Bug Report Template
|
||||
|
||||
```markdown
|
||||
### Bug Description
|
||||
A clear and concise description of the bug.
|
||||
|
||||
### How to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
|
||||
### Expected Behavior
|
||||
What you expected to happen.
|
||||
|
||||
### Actual Behavior
|
||||
What actually happened. Include error messages.
|
||||
|
||||
### Screenshots/Videos
|
||||
If applicable, add screenshots or recordings.
|
||||
|
||||
### Environment
|
||||
- Superset version: [e.g., 3.0.0]
|
||||
- Python version: [e.g., 3.9.7]
|
||||
- Node version: [e.g., 18.17.0]
|
||||
- Database: [e.g., PostgreSQL 14]
|
||||
- Browser: [e.g., Chrome 120]
|
||||
- OS: [e.g., Ubuntu 22.04]
|
||||
|
||||
### Additional Context
|
||||
- Using Docker: Yes/No
|
||||
- Configuration overrides:
|
||||
- Feature flags enabled:
|
||||
- Authentication method:
|
||||
```
|
||||
|
||||
### What Makes a Good Bug Report
|
||||
|
||||
#### ✅ Good Example
|
||||
```markdown
|
||||
### Bug Description
|
||||
When filtering a dashboard with a date range filter, charts using
|
||||
SQL Lab datasets don't update, while charts using regular datasets do.
|
||||
|
||||
### How to Reproduce
|
||||
1. Create a dashboard with 2 charts:
|
||||
- Chart A: Uses a SQL Lab virtual dataset
|
||||
- Chart B: Uses a regular table dataset
|
||||
2. Add a date range filter (last 30 days)
|
||||
3. Apply the filter
|
||||
4. Chart B updates, Chart A shows no change
|
||||
|
||||
### Expected Behavior
|
||||
Both charts should filter to show last 30 days of data.
|
||||
|
||||
### Actual Behavior
|
||||
Only Chart B updates. Chart A still shows all data.
|
||||
No error messages in browser console or server logs.
|
||||
|
||||
### Screenshots
|
||||
[Dashboard before filter]: attachment1.png
|
||||
[Dashboard after filter]: attachment2.png
|
||||
[Network tab showing requests]: attachment3.png
|
||||
|
||||
### Environment
|
||||
- Superset version: 3.0.0
|
||||
- Python version: 3.9.16
|
||||
- Database: PostgreSQL 14.9
|
||||
- Browser: Chrome 120.0.6099.71
|
||||
- OS: macOS 14.2
|
||||
```
|
||||
|
||||
#### ❌ Poor Example
|
||||
```markdown
|
||||
Dashboard filters don't work. Please fix.
|
||||
```
|
||||
|
||||
### Required Information
|
||||
|
||||
#### Error Messages
|
||||
```python
|
||||
# Include full error traceback
|
||||
Traceback (most recent call last):
|
||||
File "...", line X, in function
|
||||
error details
|
||||
SupersetException: Detailed error message
|
||||
```
|
||||
|
||||
#### Logs
|
||||
```bash
|
||||
# Backend logs
|
||||
docker logs superset_app 2>&1 | tail -100
|
||||
|
||||
# Or from development
|
||||
tail -f ~/.superset/superset.log
|
||||
```
|
||||
|
||||
#### Browser Console
|
||||
```javascript
|
||||
// Include JavaScript errors
|
||||
// Chrome: F12 → Console tab
|
||||
// Include network errors
|
||||
// Chrome: F12 → Network tab
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
```python
|
||||
# Relevant config from superset_config.py
|
||||
FEATURE_FLAGS = {
|
||||
"ENABLE_TEMPLATE_PROCESSING": True,
|
||||
# ... other flags
|
||||
}
|
||||
```
|
||||
|
||||
## Feature Requests
|
||||
|
||||
### Feature Request Template
|
||||
|
||||
```markdown
|
||||
### Is your feature request related to a problem?
|
||||
A clear description of the problem you're trying to solve.
|
||||
|
||||
### Describe the solution you'd like
|
||||
A clear description of what you want to happen.
|
||||
|
||||
### Describe alternatives you've considered
|
||||
Other solutions or features you've considered.
|
||||
|
||||
### Additional context
|
||||
Any other context, mockups, or examples.
|
||||
|
||||
### Are you willing to contribute?
|
||||
- [ ] Yes, I can implement this feature
|
||||
- [ ] Yes, I can help test
|
||||
- [ ] No, but I can provide feedback
|
||||
```
|
||||
|
||||
### Good Feature Requests Include
|
||||
|
||||
1. **Clear Use Case**
|
||||
```markdown
|
||||
As a [type of user], I want [feature] so that [benefit].
|
||||
|
||||
Example:
|
||||
As a data analyst, I want to schedule dashboard screenshots
|
||||
so that I can automatically send reports to stakeholders.
|
||||
```
|
||||
|
||||
2. **Mockups/Designs**
|
||||
- UI mockups
|
||||
- Workflow diagrams
|
||||
- API specifications
|
||||
|
||||
3. **Impact Analysis**
|
||||
- Who benefits?
|
||||
- How many users affected?
|
||||
- Performance implications?
|
||||
|
||||
## Security Issues
|
||||
|
||||
### 🔴 IMPORTANT: Security Vulnerabilities
|
||||
|
||||
**DO NOT** create public issues for security vulnerabilities!
|
||||
|
||||
Instead:
|
||||
1. Email: security@apache.org
|
||||
2. Subject: `[Superset] Security Vulnerability`
|
||||
3. Include:
|
||||
- Description of vulnerability
|
||||
- Steps to reproduce
|
||||
- Potential impact
|
||||
- Suggested fix (if any)
|
||||
|
||||
### Security Issue Template
|
||||
|
||||
```markdown
|
||||
Send to: security@apache.org
|
||||
|
||||
### Vulnerability Description
|
||||
[Describe the security issue]
|
||||
|
||||
### Type
|
||||
- [ ] SQL Injection
|
||||
- [ ] XSS
|
||||
- [ ] CSRF
|
||||
- [ ] Authentication Bypass
|
||||
- [ ] Information Disclosure
|
||||
- [ ] Other: [specify]
|
||||
|
||||
### Affected Versions
|
||||
[List affected versions]
|
||||
|
||||
### Steps to Reproduce
|
||||
[Detailed steps - be specific]
|
||||
|
||||
### Impact
|
||||
[What can an attacker do?]
|
||||
|
||||
### Suggested Fix
|
||||
[If you have suggestions]
|
||||
```
|
||||
|
||||
## Issue Labels
|
||||
|
||||
### Priority Labels
|
||||
- `P0`: Critical - System unusable
|
||||
- `P1`: High - Major feature broken
|
||||
- `P2`: Medium - Important but workaround exists
|
||||
- `P3`: Low - Nice to have
|
||||
|
||||
### Type Labels
|
||||
- `bug`: Something isn't working
|
||||
- `feature`: New feature request
|
||||
- `enhancement`: Improvement to existing feature
|
||||
- `documentation`: Documentation improvements
|
||||
- `question`: Question about usage
|
||||
|
||||
### Component Labels
|
||||
- `dashboard`: Dashboard functionality
|
||||
- `sqllab`: SQL Lab
|
||||
- `explore`: Chart builder
|
||||
- `visualization`: Chart types
|
||||
- `api`: REST API
|
||||
- `security`: Security related
|
||||
|
||||
### Status Labels
|
||||
- `needs-triage`: Awaiting review
|
||||
- `confirmed`: Bug confirmed
|
||||
- `in-progress`: Being worked on
|
||||
- `blocked`: Blocked by dependency
|
||||
- `stale`: No activity for 30+ days
|
||||
|
||||
## Issue Lifecycle
|
||||
|
||||
### 1. Creation
|
||||
- User creates issue with template
|
||||
- Auto-labeled as `needs-triage`
|
||||
|
||||
### 2. Triage
|
||||
- Maintainer reviews within 7 days
|
||||
- Labels applied (priority, type, component)
|
||||
- Questions asked if needed
|
||||
|
||||
### 3. Confirmation
|
||||
- Bug reproduced or feature discussed
|
||||
- Label changed to `confirmed`
|
||||
- Assigned to milestone if applicable
|
||||
|
||||
### 4. Development
|
||||
- Contributor claims issue
|
||||
- Label changed to `in-progress`
|
||||
- PR linked to issue
|
||||
|
||||
### 5. Resolution
|
||||
- PR merged
|
||||
- Issue auto-closed
|
||||
- Or manually closed with explanation
|
||||
|
||||
## Following Up
|
||||
|
||||
### If No Response
|
||||
|
||||
After 7 days without response:
|
||||
```markdown
|
||||
@apache/superset-committers This issue hasn't been triaged yet.
|
||||
Could someone please take a look?
|
||||
```
|
||||
|
||||
### Providing Updates
|
||||
|
||||
```markdown
|
||||
Update: I found that this only happens when [condition].
|
||||
Here's additional debugging information: [details]
|
||||
```
|
||||
|
||||
### Issue Staleness
|
||||
|
||||
- Bot marks stale after 30 days of inactivity
|
||||
- Closes after 7 more days without activity
|
||||
- To keep open: Comment with updates
|
||||
|
||||
## Tips for Success
|
||||
|
||||
### Do's
|
||||
- ✅ Search before creating
|
||||
- ✅ Use templates
|
||||
- ✅ Provide complete information
|
||||
- ✅ Include screenshots/videos
|
||||
- ✅ Be responsive to questions
|
||||
- ✅ Test with latest version
|
||||
- ✅ One issue per report
|
||||
|
||||
### Don'ts
|
||||
- ❌ "+1" or "me too" comments (use reactions)
|
||||
- ❌ Multiple issues in one report
|
||||
- ❌ Vague descriptions
|
||||
- ❌ Screenshots of text (copy/paste instead)
|
||||
- ❌ Private/sensitive data in reports
|
||||
- ❌ Demanding immediate fixes
|
||||
|
||||
## Useful Commands
|
||||
|
||||
### Gathering System Info
|
||||
|
||||
```bash
|
||||
# Full environment info
|
||||
python -c "
|
||||
import sys
|
||||
import superset
|
||||
import sqlalchemy
|
||||
import pandas
|
||||
import numpy
|
||||
|
||||
print(f'Python: {sys.version}')
|
||||
print(f'Superset: {superset.__version__}')
|
||||
print(f'SQLAlchemy: {sqlalchemy.__version__}')
|
||||
print(f'Pandas: {pandas.__version__}')
|
||||
print(f'NumPy: {numpy.__version__}')
|
||||
"
|
||||
|
||||
# Database versions
|
||||
superset shell
|
||||
>>> from superset import db
|
||||
>>> print(db.engine.dialect.server_version_info)
|
||||
```
|
||||
|
||||
### Creating Minimal Reproductions
|
||||
|
||||
```python
|
||||
# Create test script
|
||||
# minimal_repro.py
|
||||
from superset import create_app
|
||||
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
# Your reproduction code here
|
||||
pass
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
### Before Creating an Issue
|
||||
|
||||
1. **Slack**: Ask in #troubleshooting
|
||||
2. **GitHub Discussions**: Search/ask questions
|
||||
3. **Stack Overflow**: Tag `apache-superset`
|
||||
4. **Mailing List**: user@superset.apache.org
|
||||
|
||||
### Issue Not a Bug?
|
||||
|
||||
Consider:
|
||||
- **Feature Request**: Use feature request template
|
||||
- **Question**: Use GitHub Discussions
|
||||
- **Configuration Help**: Ask in Slack
|
||||
- **Development Help**: See [Contributing Guide](./overview)
|
||||
|
||||
Next: [Understanding the release process](./release-process)
|
||||
166
docs/developer_docs/contributing/overview.md
Normal file
166
docs/developer_docs/contributing/overview.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: Overview
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Contributing
|
||||
|
||||
Superset is an [Apache Software foundation](https://www.apache.org/theapacheway/index.html) project.
|
||||
The core contributors (or committers) to Superset communicate primarily in the following channels (which can be joined by anyone):
|
||||
|
||||
- [Mailing list](https://lists.apache.org/list.html?dev@superset.apache.org)
|
||||
- [Apache Superset Slack community](http://bit.ly/join-superset-slack)
|
||||
- [GitHub issues](https://github.com/apache/superset/issues)
|
||||
- [GitHub pull requests](https://github.com/apache/superset/pulls)
|
||||
- [GitHub discussions](https://github.com/apache/superset/discussions)
|
||||
- [Superset Community Calendar](https://superset.apache.org/community)
|
||||
|
||||
More references:
|
||||
|
||||
- [Superset Wiki (code guidelines and additional resources)](https://github.com/apache/superset/wiki)
|
||||
|
||||
## Orientation
|
||||
|
||||
Here's a list of repositories that contain Superset-related packages:
|
||||
|
||||
- [apache/superset](https://github.com/apache/superset)
|
||||
is the main repository containing the `apache_superset` Python package
|
||||
distributed on
|
||||
[pypi](https://pypi.org/project/apache_superset/). This repository
|
||||
also includes Superset's main TypeScript/JavaScript bundles and react apps under
|
||||
the [superset-frontend](https://github.com/apache/superset/tree/master/superset-frontend)
|
||||
folder.
|
||||
- [github.com/apache-superset](https://github.com/apache-superset) is the
|
||||
GitHub organization under which we manage Superset-related
|
||||
small tools, forks and Superset-related experimental ideas.
|
||||
|
||||
## Types of Contributions
|
||||
|
||||
### Report Bug
|
||||
|
||||
The best way to report a bug is to file an issue on GitHub. Please include:
|
||||
|
||||
- Your operating system name and version.
|
||||
- Superset version.
|
||||
- Detailed steps to reproduce the bug.
|
||||
- Any details about your local setup that might be helpful in troubleshooting.
|
||||
|
||||
When posting Python stack traces, please quote them using
|
||||
[Markdown blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/).
|
||||
|
||||
_Please note that feature requests opened as GitHub Issues will be moved to Discussions._
|
||||
|
||||
### Submit Ideas or Feature Requests
|
||||
|
||||
The best way is to start an ["Ideas" Discussion thread](https://github.com/apache/superset/discussions/categories/ideas) on GitHub:
|
||||
|
||||
- Explain in detail how it would work.
|
||||
- Keep the scope as narrow as possible, to make it easier to implement.
|
||||
- Remember that this is a volunteer-driven project, and that your contributions are as welcome as anyone's :)
|
||||
|
||||
To propose large features or major changes to codebase, and help usher in those changes, please create a **Superset Improvement Proposal (SIP)**. See template from [SIP-0](https://github.com/apache/superset/issues/5602)
|
||||
|
||||
### Fix Bugs
|
||||
|
||||
Look through the GitHub issues. Issues tagged with `#bug` are
|
||||
open to whoever wants to implement them.
|
||||
|
||||
### Implement Features
|
||||
|
||||
Look through the GitHub issues. Issues tagged with
|
||||
`#feature` are open to whoever wants to implement them.
|
||||
|
||||
### Improve Documentation
|
||||
|
||||
Superset could always use better documentation,
|
||||
whether as part of the official Superset docs,
|
||||
in docstrings, `docs/*.rst` or even on the web as blog posts or
|
||||
articles. See [Documentation](./howtos#contributing-to-documentation) for more details.
|
||||
|
||||
### Add Translations
|
||||
|
||||
If you are proficient in a non-English language, you can help translate
|
||||
text strings from Superset's UI. You can jump into the existing
|
||||
language dictionaries at
|
||||
`superset/translations/<language_code>/LC_MESSAGES/messages.po`, or
|
||||
even create a dictionary for a new language altogether.
|
||||
See [Translating](./howtos#contributing-translations) for more details.
|
||||
|
||||
### Ask Questions
|
||||
|
||||
There is a dedicated [`apache-superset` tag](https://stackoverflow.com/questions/tagged/apache-superset) on [StackOverflow](https://stackoverflow.com/). Please use it when asking questions.
|
||||
|
||||
## Types of Contributors
|
||||
|
||||
Following the project governance model of the Apache Software Foundation (ASF), Apache Superset has a specific set of contributor roles:
|
||||
|
||||
### PMC Member
|
||||
|
||||
A Project Management Committee (PMC) member is a person who has been elected by the PMC to help manage the project. PMC members are responsible for the overall health of the project, including community development, release management, and project governance. PMC members are also responsible for the technical direction of the project.
|
||||
|
||||
For more information about Apache Project PMCs, please refer to https://www.apache.org/foundation/governance/pmcs.html
|
||||
|
||||
### Committer
|
||||
|
||||
A committer is a person who has been elected by the PMC to have write access (commit access) to the code repository. They can modify the code, documentation, and website and accept contributions from others.
|
||||
|
||||
The official list of committers and PMC members can be found [here](https://projects.apache.org/committee.html?superset).
|
||||
|
||||
### Contributor
|
||||
|
||||
A contributor is a person who has contributed to the project in any way, including but not limited to code, tests, documentation, issues, and discussions.
|
||||
|
||||
> You can also review the Superset project's guidelines for PMC member promotion here: https://github.com/apache/superset/wiki/Guidelines-for-promoting-Superset-Committers-to-the-Superset-PMC
|
||||
|
||||
### Security Team
|
||||
|
||||
The security team is a selected subset of PMC members, committers and non-committers who are responsible for handling security issues.
|
||||
|
||||
New members of the security team are selected by the PMC members in a vote. You can request to be added to the team by sending a message to private@superset.apache.org. However, the team should be small and focused on solving security issues, so the requests will be evaluated on a case-by-case basis and the team size will be kept relatively small, limited to only actively security-focused contributors.
|
||||
|
||||
This security team must follow the [ASF vulnerability handling process](https://apache.org/security/committers.html#asf-project-security-for-committers).
|
||||
|
||||
Each new security issue is tracked as a JIRA ticket on the [ASF's JIRA Superset security project](https://issues.apache.org/jira/secure/RapidBoard.jspa?rapidView=588&projectKey=SUPERSETSEC)
|
||||
|
||||
Security team members must:
|
||||
|
||||
- Have an [ICLA](https://www.apache.org/licenses/contributor-agreements.html) signed with Apache Software Foundation.
|
||||
- Not reveal information about pending and unfixed security issues to anyone (including their employers) unless specifically authorised by the security team members, e.g., if the security team agrees that diagnosing and solving an issue requires the involvement of external experts.
|
||||
|
||||
A release manager, the contributor overseeing the release of a specific version of Apache Superset, is by default a member of the security team. However, they are not expected to be active in assessing, discussing, and fixing security issues.
|
||||
|
||||
Security team members should also follow these general expectations:
|
||||
|
||||
- Actively participate in assessing, discussing, fixing, and releasing security issues in Superset.
|
||||
- Avoid discussing security fixes in public forums. Pull request (PR) descriptions should not contain any information about security issues. The corresponding JIRA ticket should contain a link to the PR.
|
||||
- Security team members who contribute to a fix may be listed as remediation developers in the CVE report, along with their job affiliation (if they choose to include it).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Ready to contribute? Here's how to get started:
|
||||
|
||||
1. **[Set up your environment](./development-setup)** - Get Superset running locally
|
||||
2. **[Find something to work on](#types-of-contributions)** - Pick an issue or feature
|
||||
3. **[Submit your contribution](./submitting-pr)** - Create a pull request
|
||||
4. **[Follow guidelines](./guidelines)** - Ensure code quality
|
||||
|
||||
Welcome to the Apache Superset community! 🚀
|
||||
94
docs/developer_docs/contributing/pkg-resources-migration.md
Normal file
94
docs/developer_docs/contributing/pkg-resources-migration.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: pkg_resources Migration Guide
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# pkg_resources Deprecation and Migration Guide
|
||||
|
||||
## Background
|
||||
|
||||
As of setuptools 81.0.0, the `pkg_resources` API is deprecated and will be removed. This affects several packages in the Python ecosystem.
|
||||
|
||||
## Current Status
|
||||
|
||||
### Superset Codebase
|
||||
|
||||
The Superset codebase has already migrated away from `pkg_resources` to the modern `importlib.metadata` API:
|
||||
|
||||
- `superset/db_engine_specs/__init__.py` - Uses `from importlib.metadata import entry_points`
|
||||
- All entry point loading uses the modern API
|
||||
|
||||
### Production Dependencies
|
||||
|
||||
Some third-party dependencies may still use `pkg_resources`. Monitor your dependency tree for packages that haven't migrated yet.
|
||||
|
||||
## Migration Path
|
||||
|
||||
### Short-term Solution
|
||||
|
||||
Pin setuptools to version 80.x to prevent breaking changes:
|
||||
|
||||
```python
|
||||
# requirements/base.in
|
||||
setuptools<81
|
||||
```
|
||||
|
||||
This prevents the removal of `pkg_resources` while dependent packages are updated.
|
||||
|
||||
### Long-term Solution
|
||||
|
||||
Update all dependencies to use `importlib.metadata` instead of `pkg_resources`:
|
||||
|
||||
#### Migration Example
|
||||
|
||||
**Old (deprecated):**
|
||||
```python
|
||||
import pkg_resources
|
||||
|
||||
version = pkg_resources.get_distribution("package_name").version
|
||||
entry_points = pkg_resources.iter_entry_points("group_name")
|
||||
```
|
||||
|
||||
**New (recommended):**
|
||||
```python
|
||||
from importlib.metadata import version, entry_points
|
||||
|
||||
pkg_version = version("package_name")
|
||||
eps = entry_points(group="group_name")
|
||||
```
|
||||
|
||||
## Action Items
|
||||
|
||||
### For Superset Maintainers
|
||||
1. The Superset codebase already uses `importlib.metadata`
|
||||
2. Monitor third-party dependencies for updates
|
||||
3. Update setuptools pin once the ecosystem is ready
|
||||
|
||||
### For Extension Developers
|
||||
1. **Update your packages** to use `importlib.metadata` instead of `pkg_resources`
|
||||
2. **Test with setuptools >= 81.0.0** once all packages are migrated
|
||||
|
||||
## References
|
||||
|
||||
- [setuptools pkg_resources deprecation notice](https://setuptools.pypa.io/en/latest/pkg_resources.html)
|
||||
- [importlib.metadata documentation](https://docs.python.org/3/library/importlib.metadata.html)
|
||||
- [Migration guide](https://setuptools.pypa.io/en/latest/deprecated/pkg_resources.html)
|
||||
469
docs/developer_docs/contributing/release-process.md
Normal file
469
docs/developer_docs/contributing/release-process.md
Normal file
@@ -0,0 +1,469 @@
|
||||
---
|
||||
title: Release Process
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Release Process
|
||||
|
||||
Understand Apache Superset's release process, versioning strategy, and how to participate.
|
||||
|
||||
## Release Cadence
|
||||
|
||||
### Schedule
|
||||
- **Major releases (X.0.0)**: Annually (approximately)
|
||||
- **Minor releases (X.Y.0)**: Quarterly
|
||||
- **Patch releases (X.Y.Z)**: As needed for critical fixes
|
||||
|
||||
### Version Numbering
|
||||
|
||||
Superset follows [Semantic Versioning](https://semver.org/):
|
||||
|
||||
```
|
||||
MAJOR.MINOR.PATCH
|
||||
↓ ↓ ↓
|
||||
│ │ └── Bug fixes, security patches
|
||||
│ └────── New features, backwards compatible
|
||||
└──────────── Breaking changes
|
||||
```
|
||||
|
||||
### Examples
|
||||
- `3.0.0`: Major release with breaking changes
|
||||
- `3.1.0`: Minor release with new features
|
||||
- `3.1.1`: Patch release with bug fixes
|
||||
|
||||
## Release Types
|
||||
|
||||
### Major Releases (X.0.0)
|
||||
|
||||
#### Includes
|
||||
- Breaking API changes
|
||||
- Deprecated feature removals
|
||||
- Major architectural changes
|
||||
- Database migration requirements
|
||||
|
||||
#### Process
|
||||
- 2-3 month preparation period
|
||||
- Multiple release candidates (RC)
|
||||
- Extensive testing period
|
||||
- Migration guides required
|
||||
|
||||
### Minor Releases (X.Y.0)
|
||||
|
||||
#### Includes
|
||||
- New features
|
||||
- Performance improvements
|
||||
- Non-breaking API additions
|
||||
- Minor UI/UX updates
|
||||
|
||||
#### Process
|
||||
- 1 month preparation
|
||||
- 1-2 release candidates
|
||||
- Standard testing period
|
||||
|
||||
### Patch Releases (X.Y.Z)
|
||||
|
||||
#### Includes
|
||||
- Bug fixes
|
||||
- Security patches
|
||||
- Documentation fixes
|
||||
- Dependency updates (security)
|
||||
|
||||
#### Process
|
||||
- Fast track for critical issues
|
||||
- May skip RC for urgent security fixes
|
||||
- Minimal testing requirements
|
||||
|
||||
## Release Process (For Release Managers)
|
||||
|
||||
### 1. Pre-Release Preparation
|
||||
|
||||
#### Feature Freeze
|
||||
```bash
|
||||
# Create release branch
|
||||
git checkout -b release-X.Y
|
||||
git push upstream release-X.Y
|
||||
|
||||
# Update version
|
||||
# Edit setup.py and package.json
|
||||
VERSION = "X.Y.0rc1"
|
||||
```
|
||||
|
||||
#### Update Documentation
|
||||
- CHANGELOG.md
|
||||
- UPDATING.md (for breaking changes)
|
||||
- Documentation version
|
||||
|
||||
#### Release Notes Template
|
||||
```markdown
|
||||
# Apache Superset X.Y.0
|
||||
|
||||
## 🎉 Highlights
|
||||
- Major feature 1
|
||||
- Major feature 2
|
||||
|
||||
## 🚀 New Features
|
||||
- Feature 1 (#PR)
|
||||
- Feature 2 (#PR)
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
- Fix 1 (#PR)
|
||||
- Fix 2 (#PR)
|
||||
|
||||
## ⚠️ Breaking Changes
|
||||
- Breaking change 1
|
||||
- Migration required for X
|
||||
|
||||
## 📝 Documentation
|
||||
- Doc update 1 (#PR)
|
||||
|
||||
## 🙏 Thank You
|
||||
Thanks to all contributors!
|
||||
```
|
||||
|
||||
### 2. Create Release Candidate
|
||||
|
||||
#### Build RC
|
||||
```bash
|
||||
# Tag release candidate
|
||||
git tag -a vX.Y.Zrc1 -m "Apache Superset X.Y.Z RC1"
|
||||
git push upstream vX.Y.Zrc1
|
||||
|
||||
# Build source distribution
|
||||
python setup.py sdist
|
||||
|
||||
# Build wheel
|
||||
python setup.py bdist_wheel
|
||||
|
||||
# Sign artifacts
|
||||
gpg --armor --detach-sig dist/apache-superset-X.Y.Zrc1.tar.gz
|
||||
```
|
||||
|
||||
#### Upload to staging
|
||||
```bash
|
||||
# Upload to Apache staging
|
||||
svn co https://dist.apache.org/repos/dist/dev/superset
|
||||
cd superset
|
||||
mkdir X.Y.Zrc1
|
||||
cp /path/to/dist/* X.Y.Zrc1/
|
||||
svn add X.Y.Zrc1
|
||||
svn commit -m "Add Apache Superset X.Y.Z RC1"
|
||||
```
|
||||
|
||||
### 3. Voting Process
|
||||
|
||||
#### Call for Vote Email
|
||||
|
||||
Send to dev@superset.apache.org:
|
||||
|
||||
```
|
||||
Subject: [VOTE] Release Apache Superset X.Y.Z RC1
|
||||
|
||||
Hi all,
|
||||
|
||||
I'd like to call a vote to release Apache Superset version X.Y.Z RC1.
|
||||
|
||||
The release candidate:
|
||||
- Git tag: vX.Y.Zrc1
|
||||
- Git hash: abc123def456
|
||||
- Source: https://dist.apache.org/repos/dist/dev/superset/X.Y.Zrc1/
|
||||
|
||||
Resources:
|
||||
- Release notes: [link]
|
||||
- CHANGELOG: [link]
|
||||
- PR list: [link]
|
||||
|
||||
The vote will be open for at least 72 hours.
|
||||
|
||||
[ ] +1 approve
|
||||
[ ] +0 no opinion
|
||||
[ ] -1 disapprove (and reason why)
|
||||
|
||||
Thanks,
|
||||
[Your name]
|
||||
```
|
||||
|
||||
#### Voting Rules
|
||||
- **Duration**: Minimum 72 hours
|
||||
- **Required**: 3 +1 votes from PMC members
|
||||
- **Veto**: Any -1 vote must be addressed
|
||||
|
||||
#### Testing Checklist
|
||||
```markdown
|
||||
- [ ] Source builds successfully
|
||||
- [ ] Docker image builds
|
||||
- [ ] Basic functionality works
|
||||
- [ ] No critical bugs
|
||||
- [ ] License check passes
|
||||
- [ ] Security scan clean
|
||||
```
|
||||
|
||||
### 4. Release Approval
|
||||
|
||||
#### Tally Votes
|
||||
```
|
||||
Subject: [RESULT][VOTE] Release Apache Superset X.Y.Z RC1
|
||||
|
||||
The vote to release Apache Superset X.Y.Z RC1 has passed.
|
||||
|
||||
+1 votes (binding):
|
||||
- PMC Member 1
|
||||
- PMC Member 2
|
||||
- PMC Member 3
|
||||
|
||||
+1 votes (non-binding):
|
||||
- Contributor 1
|
||||
- Contributor 2
|
||||
|
||||
0 votes:
|
||||
- None
|
||||
|
||||
-1 votes:
|
||||
- None
|
||||
|
||||
Thank you to everyone who tested and voted!
|
||||
```
|
||||
|
||||
### 5. Perform Release
|
||||
|
||||
#### Promote RC to Release
|
||||
```bash
|
||||
# Tag final release
|
||||
git tag -a vX.Y.Z -m "Apache Superset X.Y.Z"
|
||||
git push upstream vX.Y.Z
|
||||
|
||||
# Move from dev to release
|
||||
svn mv https://dist.apache.org/repos/dist/dev/superset/X.Y.Zrc1 \
|
||||
https://dist.apache.org/repos/dist/release/superset/X.Y.Z
|
||||
```
|
||||
|
||||
#### Publish to PyPI
|
||||
```bash
|
||||
# Upload to PyPI
|
||||
python -m twine upload dist/*X.Y.Z*
|
||||
```
|
||||
|
||||
#### Build Docker Images
|
||||
```bash
|
||||
# Build and push Docker images
|
||||
docker build -t apache/superset:X.Y.Z .
|
||||
docker push apache/superset:X.Y.Z
|
||||
docker tag apache/superset:X.Y.Z apache/superset:latest
|
||||
docker push apache/superset:latest
|
||||
```
|
||||
|
||||
### 6. Post-Release Tasks
|
||||
|
||||
#### Update Documentation
|
||||
```bash
|
||||
# Update docs version
|
||||
cd docs
|
||||
# Update docusaurus.config.js with new version
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### Announcement Email
|
||||
|
||||
Send to announce@apache.org, dev@superset.apache.org:
|
||||
|
||||
```
|
||||
Subject: [ANNOUNCE] Apache Superset X.Y.Z Released
|
||||
|
||||
The Apache Superset team is pleased to announce the release of
|
||||
Apache Superset X.Y.Z.
|
||||
|
||||
Apache Superset is a modern data exploration and visualization platform.
|
||||
|
||||
This release includes [number] commits from [number] contributors.
|
||||
|
||||
Highlights:
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Bug fixes and improvements
|
||||
|
||||
Download: https://superset.apache.org/docs/installation/
|
||||
Release Notes: https://github.com/apache/superset/releases/tag/vX.Y.Z
|
||||
PyPI: https://pypi.org/project/apache-superset/
|
||||
Docker: docker pull apache/superset:X.Y.Z
|
||||
|
||||
Thanks to all contributors who made this release possible!
|
||||
|
||||
The Apache Superset Team
|
||||
```
|
||||
|
||||
#### Update GitHub Release
|
||||
```bash
|
||||
# Create GitHub release
|
||||
gh release create vX.Y.Z \
|
||||
--title "Apache Superset X.Y.Z" \
|
||||
--notes-file RELEASE_NOTES.md
|
||||
```
|
||||
|
||||
## For Contributors
|
||||
|
||||
### During Feature Freeze
|
||||
|
||||
#### What's Allowed
|
||||
- ✅ Bug fixes
|
||||
- ✅ Documentation updates
|
||||
- ✅ Test improvements
|
||||
- ✅ Security fixes
|
||||
|
||||
#### What's Not Allowed
|
||||
- ❌ New features
|
||||
- ❌ Major refactoring
|
||||
- ❌ Breaking changes
|
||||
- ❌ Risky changes
|
||||
|
||||
### Testing RCs
|
||||
|
||||
#### How to Test
|
||||
```bash
|
||||
# Install RC from staging
|
||||
pip install https://dist.apache.org/repos/dist/dev/superset/X.Y.Zrc1/apache-superset-X.Y.Zrc1.tar.gz
|
||||
|
||||
# Or use Docker
|
||||
docker pull apache/superset:X.Y.Zrc1
|
||||
```
|
||||
|
||||
#### What to Test
|
||||
- Your use cases
|
||||
- New features mentioned in release notes
|
||||
- Upgrade from previous version
|
||||
- Database migrations
|
||||
- Critical workflows
|
||||
|
||||
#### Reporting Issues
|
||||
```markdown
|
||||
Found issue in RC1:
|
||||
- Description: [what's wrong]
|
||||
- Steps to reproduce: [how to trigger]
|
||||
- Impact: [blocker/major/minor]
|
||||
- Suggested fix: [if known]
|
||||
```
|
||||
|
||||
### CHANGELOG Maintenance
|
||||
|
||||
#### Format
|
||||
```markdown
|
||||
## X.Y.Z (YYYY-MM-DD)
|
||||
|
||||
### Features
|
||||
- feat: Description (#PR_NUMBER)
|
||||
|
||||
### Fixes
|
||||
- fix: Description (#PR_NUMBER)
|
||||
|
||||
### Breaking Changes
|
||||
- BREAKING: Description (#PR_NUMBER)
|
||||
Migration: Steps to migrate
|
||||
```
|
||||
|
||||
#### Generating CHANGELOG
|
||||
```bash
|
||||
# Use git log to generate initial list
|
||||
git log --oneline vX.Y-1.Z..vX.Y.Z | grep -E "^[a-f0-9]+ (feat|fix|perf|refactor|docs)"
|
||||
|
||||
# Group by type and format
|
||||
```
|
||||
|
||||
## Breaking Changes Process
|
||||
|
||||
### Documentation Required
|
||||
|
||||
#### UPDATING.md Entry
|
||||
```markdown
|
||||
# X.Y.Z
|
||||
|
||||
## Breaking Change: [Title]
|
||||
|
||||
### Description
|
||||
What changed and why.
|
||||
|
||||
### Before
|
||||
```python
|
||||
# Old way
|
||||
old_function(param1, param2)
|
||||
```
|
||||
|
||||
### After
|
||||
```python
|
||||
# New way
|
||||
new_function(param1, param2, param3)
|
||||
```
|
||||
|
||||
### Migration Steps
|
||||
1. Update your code to...
|
||||
2. Run migration script...
|
||||
3. Test that...
|
||||
```
|
||||
|
||||
### Deprecation Process
|
||||
|
||||
1. **Version N**: Mark as deprecated
|
||||
```python
|
||||
@deprecated(version="3.0.0", remove_in="4.0.0")
|
||||
def old_function():
|
||||
warnings.warn("Use new_function instead", DeprecationWarning)
|
||||
```
|
||||
|
||||
2. **Version N+1**: Keep deprecated with warnings
|
||||
|
||||
3. **Version N+2**: Remove completely
|
||||
|
||||
## Security Releases
|
||||
|
||||
### Expedited Process
|
||||
- No RC required for critical security fixes
|
||||
- Coordinate with security@apache.org
|
||||
- Embargo period may apply
|
||||
- CVE assignment through ASF security team
|
||||
|
||||
### Security Advisory Template
|
||||
```markdown
|
||||
CVE-YYYY-XXXXX: [Title]
|
||||
|
||||
Severity: [Critical/High/Medium/Low]
|
||||
Affected Versions: < X.Y.Z
|
||||
Fixed Version: X.Y.Z
|
||||
|
||||
Description:
|
||||
[Vulnerability description]
|
||||
|
||||
Mitigation:
|
||||
[How to fix or work around]
|
||||
|
||||
Credit:
|
||||
[Reporter name/organization]
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### Internal
|
||||
- [Apache Release Policy](https://www.apache.org/legal/release-policy.html)
|
||||
- [Superset Release History](https://github.com/apache/superset/releases)
|
||||
- [Version Strategy Discussion](https://github.com/apache/superset/discussions)
|
||||
|
||||
### Tools
|
||||
- [Release Scripts](https://github.com/apache/superset/tree/master/scripts/release)
|
||||
- [Superset Repository Scripts](https://github.com/apache/superset/tree/master/scripts)
|
||||
|
||||
Next: Return to [Contributing Overview](./overview)
|
||||
133
docs/developer_docs/contributing/resources.md
Normal file
133
docs/developer_docs/contributing/resources.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
title: Resources
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Resources
|
||||
|
||||
## High Level Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
||||
%% Top Level
|
||||
LB["<b>Load Balancer(s)</b><br/>(optional)"]
|
||||
LB -.-> WebServers
|
||||
|
||||
%% Web Servers
|
||||
subgraph WebServers ["<b>Web Server(s)</b>"]
|
||||
WS1["<b>Frontend</b><br/>(React, AntD, ECharts, AGGrid)"]
|
||||
WS2["<b>Backend</b><br/>(Python, Flask, SQLAlchemy, Pandas, ...)"]
|
||||
end
|
||||
|
||||
%% Infra
|
||||
subgraph InfraServices ["<b>Infra</b>"]
|
||||
DB[("<b>Metadata Database</b><br/>(Postgres / MySQL)")]
|
||||
|
||||
subgraph Caching ["<b>Caching Subservices<br/></b>(Redis, memcache, S3, ...)"]
|
||||
direction LR
|
||||
DummySpace[" "]:::invisible
|
||||
QueryCache["<b>Query Results Cache</b><br/>(Accelerated Dashboards)"]
|
||||
CsvCache["<b>CSV Exports Cache</b>"]
|
||||
ThumbnailCache["<b>Thumbnails Cache</b>"]
|
||||
AlertImageCache["<b>Alert/Report Images Cache</b>"]
|
||||
QueryCache -- " " --> CsvCache
|
||||
linkStyle 1 stroke:transparent;
|
||||
ThumbnailCache -- " " --> AlertImageCache
|
||||
linkStyle 2 stroke:transparent;
|
||||
end
|
||||
|
||||
Broker(("<b>Message Queue</b><br/>(Redis / RabbitMQ / SQS)"))
|
||||
end
|
||||
|
||||
AsyncBackend["<b>Async Workers (Celery)</b><br>required for Alerts & Reports, thumbnails, CSV exports, long-running workloads, ..."]
|
||||
|
||||
%% External DBs
|
||||
subgraph ExternalDatabases ["<b>Analytics Databases</b>"]
|
||||
direction LR
|
||||
BigQuery[(BigQuery)]
|
||||
Snowflake[(Snowflake)]
|
||||
Redshift[(Redshift)]
|
||||
Postgres[(Postgres)]
|
||||
Postgres[(... any ...)]
|
||||
end
|
||||
|
||||
%% Connections
|
||||
LB -.-> WebServers
|
||||
WebServers --> DB
|
||||
WebServers -.-> Caching
|
||||
WebServers -.-> Broker
|
||||
WebServers -.-> ExternalDatabases
|
||||
|
||||
Broker -.-> AsyncBackend
|
||||
|
||||
AsyncBackend -.-> ExternalDatabases
|
||||
AsyncBackend -.-> Caching
|
||||
|
||||
|
||||
|
||||
%% Legend styling
|
||||
classDef requiredNode stroke-width:2px,stroke:black;
|
||||
class Required requiredNode;
|
||||
class Optional optionalNode;
|
||||
|
||||
%% Hide real arrow
|
||||
linkStyle 0 stroke:transparent;
|
||||
|
||||
%% Styling
|
||||
classDef optionalNode stroke-dasharray: 5 5, opacity:0.9;
|
||||
class LB optionalNode;
|
||||
class Caching optionalNode;
|
||||
class AsyncBackend optionalNode;
|
||||
class Broker optionalNode;
|
||||
class QueryCache optionalNode;
|
||||
class CsvCache optionalNode;
|
||||
class ThumbnailCache optionalNode;
|
||||
class AlertImageCache optionalNode;
|
||||
class Celery optionalNode;
|
||||
|
||||
classDef invisible fill:transparent,stroke:transparent;
|
||||
```
|
||||
|
||||
## Entity-Relationship Diagram
|
||||
|
||||
For the full interactive Entity-Relationship Diagram, please visit the [developer documentation](/developer-docs/contributing/resources).
|
||||
|
||||
You can also [download the .svg](https://github.com/apache/superset/tree/master/docs/static/img/erd.svg) directly from GitHub.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Official Documentation
|
||||
- [Apache Superset Documentation](https://superset.apache.org/docs/intro)
|
||||
- [API Documentation](https://superset.apache.org/docs/api)
|
||||
- [Configuration Guide](https://superset.apache.org/admin-docs/configuration/configuring-superset)
|
||||
|
||||
### Community Resources
|
||||
- [Apache Superset Blog](https://preset.io/blog/)
|
||||
- [YouTube Channel](https://www.youtube.com/channel/UCMuwrvBsg_jjI2gLcm04R0g)
|
||||
- [Twitter/X](https://twitter.com/ApacheSuperset)
|
||||
|
||||
### Development Tools
|
||||
- [GitHub Repository](https://github.com/apache/superset)
|
||||
- [PyPI Package](https://pypi.org/project/apache-superset/)
|
||||
- [Docker Hub](https://hub.docker.com/r/apache/superset)
|
||||
- [npm Packages](https://www.npmjs.com/search?q=%40superset-ui)
|
||||
321
docs/developer_docs/contributing/submitting-pr.md
Normal file
321
docs/developer_docs/contributing/submitting-pr.md
Normal file
@@ -0,0 +1,321 @@
|
||||
---
|
||||
title: Submitting Pull Requests
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Submitting Pull Requests
|
||||
|
||||
Learn how to create and submit high-quality pull requests to Apache Superset.
|
||||
|
||||
## Before You Start
|
||||
|
||||
### Prerequisites
|
||||
- [ ] Development environment is set up
|
||||
- [ ] You've forked and cloned the repository
|
||||
- [ ] You've read the [contributing overview](./overview)
|
||||
- [ ] You've found or created an issue to work on
|
||||
|
||||
### PR Readiness Checklist
|
||||
- [ ] Code follows [coding guidelines](../guidelines/design-guidelines)
|
||||
- [ ] Tests are passing locally
|
||||
- [ ] Linting passes (`pre-commit run --all-files`)
|
||||
- [ ] Documentation is updated if needed
|
||||
|
||||
## Creating Your Pull Request
|
||||
|
||||
### 1. Create a Feature Branch
|
||||
|
||||
```bash
|
||||
# Update your fork
|
||||
git fetch upstream
|
||||
git checkout master
|
||||
git merge upstream/master
|
||||
git push origin master
|
||||
|
||||
# Create feature branch
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
### 2. Make Your Changes
|
||||
|
||||
```bash
|
||||
# Make changes
|
||||
edit files...
|
||||
|
||||
# Run tests
|
||||
pytest tests/unit_tests/
|
||||
cd superset-frontend && npm run test
|
||||
|
||||
# Run linting
|
||||
pre-commit run --all-files
|
||||
|
||||
# Commit with conventional format
|
||||
git add .
|
||||
git commit -m "feat(dashboard): add new filter component"
|
||||
```
|
||||
|
||||
### 3. PR Title Format
|
||||
|
||||
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
type(scope): description
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation only
|
||||
- `style`: Code style (formatting, semicolons, etc.)
|
||||
- `refactor`: Code refactoring
|
||||
- `perf`: Performance improvement
|
||||
- `test`: Adding tests
|
||||
- `chore`: Maintenance tasks
|
||||
- `ci`: CI/CD changes
|
||||
- `build`: Build system changes
|
||||
- `revert`: Reverting changes
|
||||
|
||||
**Scopes:**
|
||||
- `dashboard`: Dashboard functionality
|
||||
- `sqllab`: SQL Lab features
|
||||
- `explore`: Chart explorer
|
||||
- `chart`: Visualization components
|
||||
- `api`: REST API endpoints
|
||||
- `db`: Database connections
|
||||
- `security`: Security features
|
||||
- `config`: Configuration
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
feat(sqllab): add query cost estimation
|
||||
fix(dashboard): resolve filter cascading issue
|
||||
docs(api): update REST endpoint documentation
|
||||
refactor(explore): simplify chart controls logic
|
||||
perf(dashboard): optimize chart loading
|
||||
```
|
||||
|
||||
### 4. PR Description Template
|
||||
|
||||
Use the template from `.github/PULL_REQUEST_TEMPLATE.md`:
|
||||
|
||||
```markdown
|
||||
### SUMMARY
|
||||
Brief description of changes and motivation.
|
||||
|
||||
### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
|
||||
[Required for UI changes]
|
||||
|
||||
### TESTING INSTRUCTIONS
|
||||
1. Step-by-step instructions
|
||||
2. How to verify the fix/feature
|
||||
3. Any specific test scenarios
|
||||
|
||||
### ADDITIONAL INFORMATION
|
||||
- [ ] Has associated issue: #12345
|
||||
- [ ] Required feature flags:
|
||||
- [ ] API changes:
|
||||
- [ ] DB migration required:
|
||||
|
||||
### CHECKLIST
|
||||
- [ ] CI checks pass
|
||||
- [ ] Tests added/updated
|
||||
- [ ] Documentation updated
|
||||
- [ ] PR title follows conventions
|
||||
```
|
||||
|
||||
### 5. Submit the PR
|
||||
|
||||
```bash
|
||||
# Push to your fork
|
||||
git push origin feature/your-feature-name
|
||||
|
||||
# Create PR via GitHub CLI
|
||||
gh pr create --title "feat(sqllab): add query cost estimation" \
|
||||
--body-file .github/PULL_REQUEST_TEMPLATE.md
|
||||
|
||||
# Or use the GitHub web interface
|
||||
```
|
||||
|
||||
## PR Best Practices
|
||||
|
||||
### Keep PRs Focused
|
||||
- One feature/fix per PR
|
||||
- Break large changes into smaller PRs
|
||||
- Separate refactoring from feature changes
|
||||
|
||||
### Write Good Commit Messages
|
||||
```bash
|
||||
# Good
|
||||
git commit -m "fix(dashboard): prevent duplicate API calls when filters change"
|
||||
|
||||
# Bad
|
||||
git commit -m "fix bug"
|
||||
git commit -m "updates"
|
||||
```
|
||||
|
||||
### Include Tests
|
||||
```python
|
||||
# Backend test example
|
||||
def test_new_feature():
|
||||
"""Test that new feature works correctly."""
|
||||
result = new_feature_function()
|
||||
assert result == expected_value
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Frontend test example
|
||||
test('renders new component', () => {
|
||||
const { getByText } = render(<NewComponent />);
|
||||
expect(getByText('Expected Text')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
### Add Screenshots for UI Changes
|
||||
```markdown
|
||||
### Before
|
||||

|
||||
|
||||
### After
|
||||

|
||||
```
|
||||
|
||||
### Update Documentation
|
||||
- Update relevant docs in `/docs` directory
|
||||
- Add docstrings to new functions/classes
|
||||
- Update UPDATING.md for breaking changes
|
||||
|
||||
## CI Checks
|
||||
|
||||
### Required Checks
|
||||
All PRs must pass:
|
||||
- `Python Tests` - Backend unit/integration tests
|
||||
- `Frontend Tests` - JavaScript/TypeScript tests
|
||||
- `Linting` - Code style checks
|
||||
- `Type Checking` - MyPy and TypeScript
|
||||
- `License Check` - Apache license headers
|
||||
- `Documentation Build` - Docs compile successfully
|
||||
|
||||
### Common CI Failures
|
||||
|
||||
#### Python Test Failures
|
||||
```bash
|
||||
# Run locally to debug
|
||||
pytest tests/unit_tests/ -v
|
||||
pytest tests/integration_tests/ -v
|
||||
```
|
||||
|
||||
#### Frontend Test Failures
|
||||
```bash
|
||||
cd superset-frontend
|
||||
npm run test -- --coverage
|
||||
```
|
||||
|
||||
#### Linting Failures
|
||||
```bash
|
||||
# Auto-fix many issues
|
||||
pre-commit run --all-files
|
||||
|
||||
# Manual fixes may be needed for:
|
||||
# - MyPy type errors
|
||||
# - Complex ESLint issues
|
||||
# - License headers
|
||||
```
|
||||
|
||||
## Responding to Reviews
|
||||
|
||||
### Address Feedback Promptly
|
||||
```bash
|
||||
# Make requested changes
|
||||
edit files...
|
||||
|
||||
# Add commits (don't amend during review)
|
||||
git add .
|
||||
git commit -m "fix: address review feedback"
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
|
||||
### Request Re-review
|
||||
- Click "Re-request review" after addressing feedback
|
||||
- Comment on resolved discussions
|
||||
- Thank reviewers for their time
|
||||
|
||||
### Handling Conflicts
|
||||
```bash
|
||||
# Update your branch
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
|
||||
# Resolve conflicts
|
||||
edit conflicted files...
|
||||
git add .
|
||||
git rebase --continue
|
||||
|
||||
# Force push (only to your feature branch!)
|
||||
git push --force-with-lease origin feature/your-feature-name
|
||||
```
|
||||
|
||||
## After Merge
|
||||
|
||||
### Clean Up
|
||||
```bash
|
||||
# Delete local branch
|
||||
git checkout master
|
||||
git branch -d feature/your-feature-name
|
||||
|
||||
# Delete remote branch
|
||||
git push origin --delete feature/your-feature-name
|
||||
|
||||
# Update your fork
|
||||
git fetch upstream
|
||||
git merge upstream/master
|
||||
git push origin master
|
||||
```
|
||||
|
||||
### Follow Up
|
||||
- Monitor for any issues reported
|
||||
- Help with documentation if needed
|
||||
- Consider related improvements
|
||||
|
||||
## Tips for Success
|
||||
|
||||
### Do
|
||||
- ✅ Keep PRs small and focused
|
||||
- ✅ Write descriptive PR titles and descriptions
|
||||
- ✅ Include tests for new functionality
|
||||
- ✅ Respond to feedback constructively
|
||||
- ✅ Update documentation
|
||||
- ✅ Be patient with the review process
|
||||
|
||||
### Don't
|
||||
- ❌ Submit PRs with failing tests
|
||||
- ❌ Include unrelated changes
|
||||
- ❌ Force push to master
|
||||
- ❌ Ignore CI failures
|
||||
- ❌ Skip the PR template
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Slack**: Ask in #development or #beginners
|
||||
- **GitHub**: Tag @apache/superset-committers for attention
|
||||
- **Mailing List**: dev@superset.apache.org
|
||||
|
||||
Next: [Understanding code review process](./code-review)
|
||||
239
docs/developer_docs/extensions/architecture.md
Normal file
239
docs/developer_docs/extensions/architecture.md
Normal file
@@ -0,0 +1,239 @@
|
||||
---
|
||||
title: Architecture
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Architecture
|
||||
|
||||
Apache Superset's extension system is designed to enable powerful customization while maintaining stability, security, and performance. This page explains the architectural principles, system design, and technical mechanisms that make the extension ecosystem possible.
|
||||
|
||||
## Architectural Principles
|
||||
|
||||
The extension architecture is built on six core principles that guide all technical decisions and ensure extensions can be developed safely and predictably:
|
||||
|
||||
### 1. Lean Core
|
||||
|
||||
Superset's core should remain minimal, with many features delegated to extensions. Built-in features use the same APIs and extension mechanisms available to external developers. This approach:
|
||||
- Reduces maintenance burden and complexity
|
||||
- Encourages modularity
|
||||
- Allows the community to innovate independently of the main codebase
|
||||
|
||||
### 2. Explicit Contribution Points
|
||||
|
||||
All extension points are clearly defined and documented. Extension authors know exactly where and how they can interact with the host system. Each extension declares its capabilities in a metadata file, enabling the host to:
|
||||
- Manage the extension lifecycle
|
||||
- Provide a consistent user experience
|
||||
- Validate extension compatibility
|
||||
|
||||
### 3. Versioned and Stable APIs
|
||||
|
||||
Public interfaces for extensions follow semantic versioning, allowing for:
|
||||
- Safe evolution of the platform
|
||||
- Backward compatibility
|
||||
- Clear upgrade paths for extension authors
|
||||
|
||||
### 4. Lazy Loading and Activation
|
||||
|
||||
Extensions are loaded and activated only when needed, which:
|
||||
- Minimizes performance overhead
|
||||
- Reduces resource consumption
|
||||
- Improves startup time
|
||||
|
||||
### 5. Composability and Reuse
|
||||
|
||||
The architecture encourages reusing extension points and patterns across different modules, promoting:
|
||||
- Consistency across extensions
|
||||
- Reduced duplication
|
||||
- Shared best practices
|
||||
|
||||
### 6. Community-Driven Evolution
|
||||
|
||||
The system evolves based on real-world feedback and contributions. New extension points and capabilities are added as needs emerge, ensuring the platform remains relevant and flexible.
|
||||
|
||||
## System Overview
|
||||
|
||||
The extension architecture is built around three main components that work together to create a flexible, maintainable ecosystem:
|
||||
|
||||
### Core Packages
|
||||
|
||||
Two core packages provide the foundation for extension development:
|
||||
|
||||
**Frontend: `@apache-superset/core`**
|
||||
|
||||
This package provides essential building blocks for frontend extensions and the host application:
|
||||
- Shared UI components
|
||||
- Utility functions
|
||||
- APIs and hooks
|
||||
- Type definitions
|
||||
|
||||
By centralizing these resources, both extensions and built-in features use the same APIs, ensuring consistency, type safety, and a seamless user experience. The package is versioned to support safe platform evolution while maintaining compatibility.
|
||||
|
||||
**Backend: `apache-superset-core`**
|
||||
|
||||
This package exposes key classes and APIs for backend extensions:
|
||||
- Database connectors
|
||||
- API extensions
|
||||
- Security manager customization
|
||||
- Core utilities and models
|
||||
|
||||
It includes dependencies on critical libraries like Flask-AppBuilder and SQLAlchemy, and follows semantic versioning for compatibility and stability.
|
||||
|
||||
### Developer Tools
|
||||
|
||||
**`apache-superset-extensions-cli`**
|
||||
|
||||
The CLI provides comprehensive commands for extension development:
|
||||
- Project scaffolding
|
||||
- Code generation
|
||||
- Building and bundling
|
||||
- Packaging for distribution
|
||||
|
||||
By standardizing these processes, the CLI ensures extensions are built consistently, remain compatible with evolving versions of Superset, and follow best practices.
|
||||
|
||||
### Host Application
|
||||
|
||||
The Superset host application serves as the runtime environment for extensions:
|
||||
|
||||
**Extension Management**
|
||||
- Exposes `/api/v1/extensions` endpoint for registration and management
|
||||
- Provides a dedicated UI for managing extensions
|
||||
- Stores extension metadata in the `extensions` database table
|
||||
|
||||
**Extension Storage**
|
||||
|
||||
The extensions table contains:
|
||||
- Extension name, version, and author
|
||||
- Contributed features and exposed modules
|
||||
- Metadata and configuration
|
||||
- Built frontend and/or backend code
|
||||
|
||||
### Architecture Diagram
|
||||
|
||||
The following diagram illustrates how these components work together:
|
||||
|
||||
<img width="955" height="586" alt="Extension System Architecture" src="https://github.com/user-attachments/assets/cc2a41df-55a4-48c8-b056-35f7a1e567c6" />
|
||||
|
||||
The diagram shows:
|
||||
1. **Extension projects** depend on core packages for development
|
||||
2. **Core packages** provide APIs and type definitions
|
||||
3. **The host application** implements the APIs and manages extensions
|
||||
4. **Extensions** integrate seamlessly with the host through well-defined interfaces
|
||||
|
||||
## Dynamic Module Loading
|
||||
|
||||
One of the most sophisticated aspects of the extension architecture is how frontend code is dynamically loaded at runtime using Webpack's Module Federation.
|
||||
|
||||
### Module Federation
|
||||
|
||||
The architecture leverages Webpack's Module Federation to enable dynamic loading of frontend assets. This allows extensions to be built independently from Superset.
|
||||
|
||||
### How It Works
|
||||
|
||||
**Extension Configuration**
|
||||
|
||||
Extensions configure Webpack to expose their entry points:
|
||||
|
||||
``` typescript
|
||||
new ModuleFederationPlugin({
|
||||
name: 'my_extension',
|
||||
filename: 'remoteEntry.[contenthash].js',
|
||||
exposes: {
|
||||
'./index': './src/index.tsx',
|
||||
},
|
||||
externalsType: 'window',
|
||||
externals: {
|
||||
'@apache-superset/core': 'superset',
|
||||
},
|
||||
shared: {
|
||||
react: { singleton: true },
|
||||
'react-dom': { singleton: true },
|
||||
'antd-v5': { singleton: true }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This configuration does several important things:
|
||||
|
||||
**`exposes`** - Declares which modules are available to the host application. The extension makes `./index` available as its entry point.
|
||||
|
||||
**`externals` and `externalsType`** - Tell Webpack that when the extension imports `@apache-superset/core`, it should use `window.superset` at runtime instead of bundling its own copy. This ensures extensions use the host's implementation of shared packages.
|
||||
|
||||
**`shared`** - Prevents duplication of common libraries like React and Ant Design. The `singleton: true` setting ensures only one instance of each library exists, avoiding version conflicts and reducing bundle size.
|
||||
|
||||
### Runtime Resolution
|
||||
|
||||
The following diagram illustrates the module loading process:
|
||||
|
||||
<img width="913" height="558" alt="Module Federation Flow" src="https://github.com/user-attachments/assets/e5e4d2ae-e8b5-4d17-a2a1-3667c65f25ca" />
|
||||
|
||||
Here's what happens at runtime:
|
||||
|
||||
1. **Extension Registration**: When an extension is registered, Superset stores its remote entry URL
|
||||
2. **Dynamic Loading**: When the extension is activated, the host fetches the remote entry file
|
||||
3. **Module Resolution**: The extension imports `@apache-superset/core`, which resolves to `window.superset`
|
||||
4. **Execution**: The extension code runs with access to the host's APIs and shared dependencies
|
||||
|
||||
### Host API Setup
|
||||
|
||||
On the Superset side, the APIs are mapped to `window.superset` during application bootstrap:
|
||||
|
||||
``` typescript
|
||||
import * as supersetCore from '@apache-superset/core';
|
||||
import {
|
||||
authentication,
|
||||
core,
|
||||
commands,
|
||||
extensions,
|
||||
sqlLab,
|
||||
} from 'src/extensions';
|
||||
|
||||
export default function setupExtensionsAPI() {
|
||||
window.superset = {
|
||||
...supersetCore,
|
||||
authentication,
|
||||
core,
|
||||
commands,
|
||||
extensions,
|
||||
sqlLab,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
This function runs before any extensions are loaded, ensuring the APIs are available when extensions import from `@apache-superset/core`.
|
||||
|
||||
### Benefits
|
||||
|
||||
This architecture provides several key benefits:
|
||||
|
||||
- **Independent development**: Extensions can be built separately from Superset's codebase
|
||||
- **Version isolation**: Each extension can be developed with its own release cycle
|
||||
- **Shared dependencies**: Common libraries are shared, reducing memory usage and bundle size
|
||||
- **Type safety**: TypeScript types flow from the core package to extensions
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you understand the architecture, explore:
|
||||
|
||||
- **[Dependencies](./dependencies)** - Managing dependencies and understanding API stability
|
||||
- **[Quick Start](./quick-start)** - Build your first extension
|
||||
- **[Contribution Types](./contribution-types)** - What kinds of extensions you can build
|
||||
- **[Development](./development)** - Project structure, APIs, and development workflow
|
||||
131
docs/developer_docs/extensions/components/alert.mdx
Normal file
131
docs/developer_docs/extensions/components/alert.mdx
Normal file
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: Alert
|
||||
sidebar_label: Alert
|
||||
---
|
||||
|
||||
<!--
|
||||
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 { StoryWithControls } from '../../../src/components/StorybookWrapper';
|
||||
import { Alert } from '@apache-superset/core/ui';
|
||||
|
||||
# Alert
|
||||
|
||||
Alert component for displaying important messages to users. Wraps Ant Design Alert with sensible defaults and improved accessibility.
|
||||
|
||||
## Live Example
|
||||
|
||||
<StoryWithControls
|
||||
component={Alert}
|
||||
props={{
|
||||
closable: true,
|
||||
type: 'info',
|
||||
message: 'This is a sample alert message.',
|
||||
description: 'Sample description for additional context.',
|
||||
showIcon: true
|
||||
}}
|
||||
controls={[
|
||||
{
|
||||
name: 'type',
|
||||
label: 'Type',
|
||||
type: 'select',
|
||||
options: [
|
||||
'info',
|
||||
'error',
|
||||
'warning',
|
||||
'success'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'closable',
|
||||
label: 'Closable',
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'showIcon',
|
||||
label: 'Show Icon',
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
label: 'Message',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: 'text'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
## Try It
|
||||
|
||||
Edit the code below to experiment with the component:
|
||||
|
||||
```tsx live
|
||||
function Demo() {
|
||||
return (
|
||||
<Alert
|
||||
closable
|
||||
type="info"
|
||||
message="This is a sample alert message."
|
||||
description="Sample description for additional context."
|
||||
showIcon
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `closable` | `boolean` | `true` | Whether the Alert can be closed with a close button. |
|
||||
| `type` | `string` | `"info"` | Type of the alert (e.g., info, error, warning, success). |
|
||||
| `message` | `string` | `"This is a sample alert message."` | Message |
|
||||
| `description` | `string` | `"Sample description for additional context."` | Description |
|
||||
| `showIcon` | `boolean` | `true` | Whether to display an icon in the Alert. |
|
||||
|
||||
## Usage in Extensions
|
||||
|
||||
This component is available in the `@apache-superset/core/ui` package, which is automatically available to Superset extensions.
|
||||
|
||||
```tsx
|
||||
import { Alert } from '@apache-superset/core/ui';
|
||||
|
||||
function MyExtension() {
|
||||
return (
|
||||
<Alert
|
||||
closable
|
||||
type="info"
|
||||
message="This is a sample alert message."
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Source Links
|
||||
|
||||
- [Story file](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-core/src/ui/components/Alert/Alert.stories.tsx)
|
||||
- [Component source](https://github.com/apache/superset/blob/master/superset-frontend/packages/superset-core/src/ui/components/Alert/index.tsx)
|
||||
|
||||
---
|
||||
|
||||
*This page was auto-generated from the component's Storybook story.*
|
||||
93
docs/developer_docs/extensions/components/index.mdx
Normal file
93
docs/developer_docs/extensions/components/index.mdx
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Extension Components
|
||||
sidebar_label: Overview
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
# Extension Components
|
||||
|
||||
These UI components are available to Superset extension developers through the `@apache-superset/core/ui` package. They provide a consistent look and feel with the rest of Superset and are designed to be used in extension panels, views, and other UI elements.
|
||||
|
||||
## Available Components
|
||||
|
||||
- [Alert](./alert)
|
||||
|
||||
## Usage
|
||||
|
||||
All components are exported from the `@apache-superset/core/ui` package:
|
||||
|
||||
```tsx
|
||||
import { Alert } from '@apache-superset/core/ui';
|
||||
|
||||
export function MyExtensionPanel() {
|
||||
return (
|
||||
<Alert type="info">
|
||||
Welcome to my extension!
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Adding New Components
|
||||
|
||||
Components in `@apache-superset/core/ui` are automatically documented here. To add a new extension component:
|
||||
|
||||
1. Add the component to `superset-frontend/packages/superset-core/src/ui/components/`
|
||||
2. Export it from `superset-frontend/packages/superset-core/src/ui/components/index.ts`
|
||||
3. Create a Storybook story with an `Interactive` export:
|
||||
|
||||
```tsx
|
||||
export default {
|
||||
title: 'Extension Components/MyComponent',
|
||||
component: MyComponent,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Description of the component...',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const InteractiveMyComponent = (args) => <MyComponent {...args} />;
|
||||
|
||||
InteractiveMyComponent.args = {
|
||||
variant: 'primary',
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
InteractiveMyComponent.argTypes = {
|
||||
variant: {
|
||||
control: { type: 'select' },
|
||||
options: ['primary', 'secondary'],
|
||||
},
|
||||
disabled: {
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
4. Run `yarn start` in `docs/` - the page generates automatically!
|
||||
|
||||
## Interactive Documentation
|
||||
|
||||
For interactive examples with controls, visit the [Storybook](/storybook/?path=/docs/extension-components--docs).
|
||||
155
docs/developer_docs/extensions/contribution-types.md
Normal file
155
docs/developer_docs/extensions/contribution-types.md
Normal file
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: Contribution Types
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Contribution Types
|
||||
|
||||
To facilitate the development of extensions, we define a set of well-defined contribution types that extensions can implement. These contribution types serve as the building blocks for extensions, allowing them to interact with the host application and provide new functionality.
|
||||
|
||||
## Frontend
|
||||
|
||||
Frontend contribution types allow extensions to extend Superset's user interface with new views, commands, and menu items.
|
||||
|
||||
### Views
|
||||
|
||||
Extensions can add new views or panels to the host application, such as custom SQL Lab panels, dashboards, or other UI components. Each view is registered with a unique ID and can be activated or deactivated as needed. Contribution areas are uniquely identified (e.g., `sqllab.panels` for SQL Lab panels), enabling seamless integration into specific parts of the application.
|
||||
|
||||
```json
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "my_extension.main",
|
||||
"name": "My Panel Name"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
Extensions can define custom commands that can be executed within the host application, such as context-aware actions or menu options. Each command can specify properties like a unique command identifier, an icon, a title, and a description. These commands can be invoked by users through menus, keyboard shortcuts, or other UI elements, enabling extensions to add rich, interactive functionality to Superset.
|
||||
|
||||
```json
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "my_extension.copy_query",
|
||||
"icon": "CopyOutlined",
|
||||
"title": "Copy Query",
|
||||
"description": "Copy the current query to clipboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Menus
|
||||
|
||||
Extensions can contribute new menu items or context menus to the host application, providing users with additional actions and options. Each menu item can specify properties such as the target view, the command to execute, its placement (primary, secondary, or context), and conditions for when it should be displayed. Menu contribution areas are uniquely identified (e.g., `sqllab.editor` for the SQL Lab editor), allowing extensions to seamlessly integrate their functionality into specific menus and workflows within Superset.
|
||||
|
||||
```json
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"menus": {
|
||||
"sqllab": {
|
||||
"editor": {
|
||||
"primary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "my_extension.copy_query"
|
||||
}
|
||||
],
|
||||
"secondary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "my_extension.prettify"
|
||||
}
|
||||
],
|
||||
"context": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "my_extension.clear"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Editors
|
||||
|
||||
Extensions can replace Superset's default text editors with custom implementations. This enables enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions. When an extension registers an editor for a language, it replaces the default Ace editor in all locations that use that language (SQL Lab, Dashboard Properties, CSS editors, etc.).
|
||||
|
||||
```json
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"editors": [
|
||||
{
|
||||
"id": "my_extension.monaco_sql",
|
||||
"name": "Monaco SQL Editor",
|
||||
"languages": ["sql"],
|
||||
"description": "Monaco-based SQL editor with IntelliSense"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [Editors Extension Point](./extension-points/editors) for implementation details.
|
||||
|
||||
## Backend
|
||||
|
||||
Backend contribution types allow extensions to extend Superset's server-side capabilities with new API endpoints, MCP tools, and MCP prompts.
|
||||
|
||||
### REST API Endpoints
|
||||
|
||||
Extensions can register custom REST API endpoints under the `/api/v1/extensions/` namespace. This dedicated namespace prevents conflicts with built-in endpoints and provides a clear separation between core and extension functionality.
|
||||
|
||||
```json
|
||||
"backend": {
|
||||
"entryPoints": ["my_extension.entrypoint"],
|
||||
"files": ["backend/src/my_extension/**/*.py"]
|
||||
}
|
||||
```
|
||||
|
||||
The entry point module registers the API with Superset:
|
||||
|
||||
```python
|
||||
from superset_core.api.rest_api import add_extension_api
|
||||
from .api import MyExtensionAPI
|
||||
|
||||
add_extension_api(MyExtensionAPI)
|
||||
```
|
||||
|
||||
### MCP Tools and Prompts
|
||||
|
||||
Extensions can contribute Model Context Protocol (MCP) tools and prompts that AI agents can discover and use. See [MCP Integration](./mcp) for detailed documentation.
|
||||
166
docs/developer_docs/extensions/dependencies.md
Normal file
166
docs/developer_docs/extensions/dependencies.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: Dependencies
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Dependencies
|
||||
|
||||
This guide explains how to manage dependencies in your Superset extensions, including the difference between public APIs and internal code, and best practices for maintaining stable extensions.
|
||||
|
||||
## Core Packages vs Internal Code
|
||||
|
||||
Extensions run in the same context as Superset during runtime. This means extension developers can technically import any module from the Superset codebase, not just the public APIs. Understanding the distinction between public and internal code is critical for building maintainable extensions.
|
||||
|
||||
### Public APIs (Stable)
|
||||
|
||||
The core packages follow [semantic versioning](https://semver.org/) and provide stable, documented APIs:
|
||||
|
||||
| Package | Language | Description |
|
||||
|---------|----------|-------------|
|
||||
| `@apache-superset/core` | JavaScript/TypeScript | Frontend APIs, UI components, hooks, and utilities |
|
||||
| `apache-superset-core` | Python | Backend APIs, models, DAOs, and utilities |
|
||||
|
||||
**Benefits of using core packages:**
|
||||
|
||||
- **Semantic versioning**: Breaking changes are communicated through version numbers
|
||||
- **Documentation**: APIs are documented with clear usage examples
|
||||
- **Stability commitment**: We strive to maintain backward compatibility
|
||||
- **Type safety**: Full TypeScript and Python type definitions
|
||||
|
||||
### Internal Code (Unstable)
|
||||
|
||||
Any code that is not exported through the core packages is considered internal. This includes:
|
||||
|
||||
- Direct imports from `superset-frontend/src/` modules
|
||||
- Direct imports from `superset/` Python modules (outside of `superset_core`)
|
||||
- Undocumented functions, classes, or utilities
|
||||
|
||||
:::warning Use at Your Own Risk
|
||||
Internal code can change at any time without notice. If you depend on internal modules, your extension may break when Superset is upgraded. There is no guarantee of backward compatibility for internal code.
|
||||
:::
|
||||
|
||||
**Example of internal vs public imports:**
|
||||
|
||||
```typescript
|
||||
// ✅ Public API - stable
|
||||
import { Button, sqlLab } from '@apache-superset/core';
|
||||
|
||||
// ❌ Internal code - may break without notice
|
||||
import { someInternalFunction } from 'src/explore/components/SomeComponent';
|
||||
```
|
||||
|
||||
```python
|
||||
# ✅ Public API - stable
|
||||
from superset_core.api.models import Database
|
||||
from superset_core.api.daos import DatabaseDAO
|
||||
|
||||
# ❌ Internal code - may break without notice
|
||||
from superset.views.core import SomeInternalClass
|
||||
```
|
||||
|
||||
## API Evolution
|
||||
|
||||
The core packages are still evolving. While we follow semantic versioning, the APIs may change as we add new extension points and refine existing ones based on community feedback.
|
||||
|
||||
**What this means for extension developers:**
|
||||
|
||||
- Check the release notes when upgrading Superset
|
||||
- Test your extensions against new Superset versions before deploying
|
||||
- Participate in discussions about API changes to influence the direction
|
||||
- In some cases, using internal dependencies may be acceptable while the public API is being developed for your use case
|
||||
|
||||
### When Internal Dependencies May Be Acceptable
|
||||
|
||||
While public APIs are always preferred, there are situations where using internal code may be reasonable:
|
||||
|
||||
1. **Missing functionality**: The public API doesn't yet expose what you need
|
||||
2. **Prototype/experimental extensions**: You're exploring capabilities before committing to a stable implementation
|
||||
3. **Bridge period**: You need functionality that's planned for the public API but not yet released
|
||||
|
||||
In these cases, document your internal dependencies clearly and plan to migrate to public APIs when they become available.
|
||||
|
||||
## Core Library Dependencies
|
||||
|
||||
An important architectural principle of the Superset extension system is that **we do not provide abstractions on top of core dependencies** like React (frontend) or SQLAlchemy (backend).
|
||||
|
||||
### Why We Don't Abstract Core Libraries
|
||||
|
||||
Abstracting libraries like React or SQLAlchemy would:
|
||||
|
||||
- Create maintenance overhead keeping abstractions in sync with upstream
|
||||
- Limit access to the full power of these libraries
|
||||
- Add unnecessary abstraction layers
|
||||
- Fragment the ecosystem with Superset-specific variants
|
||||
|
||||
### Depending on Core Libraries Directly
|
||||
|
||||
Extension developers should depend on and use core libraries directly:
|
||||
|
||||
**Frontend (examples):**
|
||||
- [React](https://react.dev/) - UI framework
|
||||
- [Ant Design](https://ant.design/) - UI component library (prefer Superset components from `@apache-superset/core/ui` when available to preserve visual consistency)
|
||||
- [Emotion](https://emotion.sh/) - CSS-in-JS styling
|
||||
- ...
|
||||
|
||||
**Backend (examples):**
|
||||
- [SQLAlchemy](https://www.sqlalchemy.org/) - Database toolkit
|
||||
- [Flask](https://flask.palletsprojects.com/) - Web framework
|
||||
- [Flask-AppBuilder](https://flask-appbuilder.readthedocs.io/) - Application framework
|
||||
- ...
|
||||
|
||||
:::info Version Compatibility
|
||||
When Superset upgrades its core dependencies (e.g., a new major version of Ant Design or SQLAlchemy), extension developers should upgrade their extensions accordingly. This ensures compatibility and access to the latest features and security fixes.
|
||||
:::
|
||||
|
||||
## API Versioning and Changelog
|
||||
|
||||
Once the extensions API reaches **v1**, we will maintain a dedicated `CHANGELOG.md` file to track all changes to the public APIs. This will include:
|
||||
|
||||
- New APIs and features
|
||||
- Deprecation notices
|
||||
- Breaking changes with migration guides
|
||||
- Bug fixes affecting API behavior
|
||||
|
||||
Until then, monitor the Superset release notes and test your extensions with each new release.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do
|
||||
|
||||
- **Prefer public APIs**: Always check if functionality exists in `@apache-superset/core` or `apache-superset-core` before using internal code
|
||||
- **Pin versions**: Specify compatible Superset versions in your extension metadata
|
||||
- **Test upgrades**: Verify your extension works with new Superset releases before deploying
|
||||
- **Report missing APIs**: If you need functionality not in the public API, open a GitHub issue to request it
|
||||
- **Use core libraries directly**: Leverage Ant Design, SQLAlchemy, and other core libraries directly
|
||||
|
||||
### Don't
|
||||
|
||||
- **Assume stability of internal code**: Internal modules can change or be removed in any release
|
||||
- **Depend on implementation details**: Even if something works, it may not be supported
|
||||
- **Skip upgrade testing**: Always test your extension against new Superset versions
|
||||
- **Expect abstractions**: Use core dependencies directly rather than expecting Superset-specific abstractions
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Architecture](./architecture)** - Understand the extension system design
|
||||
- **[Development](./development)** - Learn about APIs and development workflow
|
||||
- **[Quick Start](./quick-start)** - Build your first extension
|
||||
49
docs/developer_docs/extensions/deployment.md
Normal file
49
docs/developer_docs/extensions/deployment.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Deployment
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Deployment
|
||||
|
||||
Once an extension has been developed, the deployment process involves packaging and uploading it to the host application.
|
||||
|
||||
Packaging is handled by the `superset-extensions bundle` command, which:
|
||||
|
||||
1. Builds frontend assets using Webpack (with Module Federation configuration).
|
||||
2. Collects backend Python source files and all necessary resources.
|
||||
3. Generates a `manifest.json` with build-time metadata, including the contents of `extension.json` and references to built assets.
|
||||
4. Packages everything into a `.supx` file (a zip archive with a specific structure required by Superset).
|
||||
|
||||
To deploy an extension, place the `.supx` file in the extensions directory configured via `EXTENSIONS_PATH` in your `superset_config.py`:
|
||||
|
||||
``` python
|
||||
EXTENSIONS_PATH = "/path/to/extensions"
|
||||
```
|
||||
|
||||
During application startup, Superset automatically discovers and loads all `.supx` files from this directory:
|
||||
|
||||
1. Scans the configured directory for `.supx` files.
|
||||
2. Validates each file is a properly formatted zip archive.
|
||||
3. Extracts and validates the extension manifest and metadata.
|
||||
4. Loads the extension, making it available for use.
|
||||
|
||||
This file-based approach simplifies deployment in containerized environments and enables version control of extensions alongside infrastructure configuration.
|
||||
317
docs/developer_docs/extensions/development.md
Normal file
317
docs/developer_docs/extensions/development.md
Normal file
@@ -0,0 +1,317 @@
|
||||
---
|
||||
title: Development
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Development
|
||||
|
||||
This guide covers everything you need to know about developing extensions for Superset, from project structure to development workflow.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The [apache-superset-extensions-cli](https://github.com/apache/superset/tree/master/superset-extensions-cli) package provides a command-line interface (CLI) that streamlines the extension development workflow. It offers the following commands:
|
||||
|
||||
```
|
||||
superset-extensions init: Generates the initial folder structure and scaffolds a new extension project.
|
||||
|
||||
superset-extensions build: Builds extension assets.
|
||||
|
||||
superset-extensions bundle: Packages the extension into a .supx file.
|
||||
|
||||
superset-extensions dev: Automatically rebuilds the extension as files change.
|
||||
```
|
||||
|
||||
When creating a new extension with `superset-extensions init`, the CLI generates a standardized folder structure:
|
||||
|
||||
```
|
||||
dataset-references/
|
||||
├── extension.json
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ ├── webpack.config.js
|
||||
│ ├── tsconfig.json
|
||||
│ └── package.json
|
||||
├── backend/
|
||||
│ ├── src/
|
||||
│ │ └── superset_extensions/
|
||||
│ │ └── dataset_references/
|
||||
│ ├── tests/
|
||||
│ ├── pyproject.toml
|
||||
│ └── requirements.txt
|
||||
├── dist/
|
||||
│ ├── manifest.json
|
||||
│ ├── frontend
|
||||
│ │ └── dist/
|
||||
│ │ ├── remoteEntry.d7a9225d042e4ccb6354.js
|
||||
│ │ └── 900.038b20cdff6d49cfa8d9.js
|
||||
│ └── backend
|
||||
│ └── superset_extensions/
|
||||
│ └── dataset_references/
|
||||
│ ├── __init__.py
|
||||
│ ├── api.py
|
||||
│ └── entrypoint.py
|
||||
├── dataset-references-1.0.0.supx
|
||||
└── README.md
|
||||
```
|
||||
|
||||
**Note**: The extension ID (`dataset-references`) serves as the basis for all technical names:
|
||||
- Directory name: `dataset-references` (kebab-case)
|
||||
- Backend Python package: `dataset_references` (snake_case)
|
||||
- Frontend package name: `dataset-references` (kebab-case)
|
||||
- Module Federation name: `datasetReferences` (camelCase)
|
||||
|
||||
The `extension.json` file serves as the declared metadata for the extension, containing the extension's name, version, author, description, and a list of capabilities. This file is essential for the host application to understand how to load and manage the extension.
|
||||
|
||||
The `frontend` directory contains the source code for the frontend components of the extension, including React components, styles, and assets. The `webpack.config.js` file is used to configure Webpack for building the frontend code, while the `tsconfig.json` file defines the TypeScript configuration for the project. The `package.json` file specifies the dependencies and scripts for building and testing the frontend code.
|
||||
|
||||
The `backend` directory contains the source code for the backend components of the extension, including Python modules, tests, and configuration files. The `pyproject.toml` file is used to define the Python package and its dependencies, while the `requirements.txt` file lists the required Python packages for the extension. The `src` folder contains the functional backend source files, `tests` directory contains unit tests for the backend code, ensuring that the extension behaves as expected and meets the defined requirements.
|
||||
|
||||
The `dist` directory is built when running the `build` or `dev` command, and contains the files that will be included in the bundle. The `manifest.json` file contains critical metadata about the extension, including the majority of the contents of the `extension.json` file, but also other build-time information, like the name of the built Webpack Module Federation remote entry file. The files in the `dist` directory will be zipped into the final `.supx` file. Although this file is technically a zip archive, the `.supx` extension makes it clear that it is a Superset extension package and follows a specific file layout. This packaged file can be distributed and installed in Superset instances.
|
||||
|
||||
The `README.md` file provides documentation and instructions for using the extension, including how to install, configure, and use its functionality.
|
||||
|
||||
## Extension Metadata
|
||||
|
||||
The `extension.json` file contains all metadata necessary for the host application to understand and manage the extension:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "dataset-references",
|
||||
"name": "Dataset References",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "dataset-references.main",
|
||||
"name": "Dataset References"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"moduleFederation": {
|
||||
"exposes": ["./index"],
|
||||
"name": "datasetReferences"
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"entryPoints": ["superset_extensions.dataset_references.entrypoint"],
|
||||
"files": ["backend/src/superset_extensions/dataset_references/**/*.py"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `contributions` section declares how the extension extends Superset's functionality through views, commands, menus, and other contribution types. The `backend` section specifies entry points and files to include in the bundle.
|
||||
|
||||
## Interacting with the Host
|
||||
|
||||
Extensions interact with Superset through well-defined, versioned APIs provided by the `@apache-superset/core` (frontend) and `apache-superset-core` (backend) packages. These APIs are designed to be stable, discoverable, and consistent for both built-in and external extensions.
|
||||
|
||||
**Note**: The `superset_core.api` module provides abstract classes that are replaced with concrete implementations via dependency injection when Superset initializes. This allows extensions to use the same interfaces as the host application.
|
||||
|
||||
### Frontend APIs
|
||||
|
||||
The frontend extension APIs (via `@apache-superset/core`) are organized into logical namespaces such as `authentication`, `commands`, `extensions`, `sqlLab`, and others. Each namespace groups related functionality, making it easy for extension authors to discover and use the APIs relevant to their needs. For example, the `sqlLab` namespace provides events and methods specific to SQL Lab, allowing extensions to react to user actions and interact with the SQL Lab environment:
|
||||
|
||||
```typescript
|
||||
export const getCurrentTab: () => Tab | undefined;
|
||||
|
||||
export const getDatabases: () => Database[];
|
||||
|
||||
export const getTabs: () => Tab[];
|
||||
|
||||
export const onDidChangeActivePanel: Event<Panel>;
|
||||
|
||||
export const onDidChangeTabTitle: Event<string>;
|
||||
|
||||
export const onDidQueryRun: Event<QueryContext>;
|
||||
|
||||
export const onDidQueryStop: Event<QueryContext>;
|
||||
```
|
||||
|
||||
The following code demonstrates more examples of the existing frontend APIs:
|
||||
|
||||
```typescript
|
||||
import { core, commands, sqlLab, authentication, Button } from '@apache-superset/core';
|
||||
import MyPanel from './MyPanel';
|
||||
|
||||
export function activate(context) {
|
||||
// Register a new panel (view) in SQL Lab and use shared UI components in your extension's React code
|
||||
const panelDisposable = core.registerView('my_extension.panel', <MyPanel><Button/></MyPanel>);
|
||||
|
||||
// Register a custom command
|
||||
const commandDisposable = commands.registerCommand(
|
||||
'my_extension.copy_query',
|
||||
() => {
|
||||
// Command logic here
|
||||
},
|
||||
);
|
||||
|
||||
// Listen for query run events in SQL Lab
|
||||
const eventDisposable = sqlLab.onDidQueryRun(queryContext => {
|
||||
console.log('Query started on database:', queryContext.tab.databaseId);
|
||||
});
|
||||
|
||||
// Access a CSRF token for secure API requests
|
||||
authentication.getCSRFToken().then(token => {
|
||||
// Use token as needed
|
||||
});
|
||||
|
||||
// Add all disposables for automatic cleanup on deactivation
|
||||
context.subscriptions.push(panelDisposable, commandDisposable, eventDisposable);
|
||||
}
|
||||
```
|
||||
|
||||
### Backend APIs
|
||||
|
||||
Backend APIs (via `apache-superset-core`) follow a similar pattern, providing access to Superset's models, sessions, and query capabilities. Extensions can register REST API endpoints, access the metadata database, and interact with Superset's core functionality.
|
||||
|
||||
Extension endpoints are registered under a dedicated `/extensions` namespace to avoid conflicting with built-in endpoints and also because they don't share the same version constraints. By grouping all extension endpoints under `/extensions`, Superset establishes a clear boundary between core and extension functionality, making it easier to manage, document, and secure both types of APIs.
|
||||
|
||||
```python
|
||||
from superset_core.api.models import Database, get_session
|
||||
from superset_core.api.daos import DatabaseDAO
|
||||
from superset_core.api.rest_api import add_extension_api
|
||||
from .api import DatasetReferencesAPI
|
||||
|
||||
# Register a new extension REST API
|
||||
add_extension_api(DatasetReferencesAPI)
|
||||
|
||||
# Fetch Superset entities via the DAO to apply base filters that filter out entities
|
||||
# that the user doesn't have access to
|
||||
databases = DatabaseDAO.find_all()
|
||||
|
||||
# ..or apply simple filters on top of base filters
|
||||
databases = DatabaseDAO.filter_by(uuid=database.uuid)
|
||||
if not databases:
|
||||
raise Exception("Database not found")
|
||||
|
||||
return databases[0]
|
||||
|
||||
# Perform complex queries using SQLAlchemy Query, also filtering out
|
||||
# inaccessible entities
|
||||
session = get_session()
|
||||
databases_query = session.query(Database).filter(
|
||||
Database.database_name.ilike("%abc%")
|
||||
)
|
||||
return DatabaseDAO.query(databases_query)
|
||||
```
|
||||
|
||||
In the future, we plan to expand the backend APIs to support configuring security models, database engines, SQL Alchemy dialects, etc.
|
||||
|
||||
## Development Mode
|
||||
|
||||
Development mode accelerates extension development by letting developers see changes in Superset quickly, without the need for repeated packaging and uploading. To enable development mode, set the `LOCAL_EXTENSIONS` configuration in your `superset_config.py`:
|
||||
|
||||
```python
|
||||
LOCAL_EXTENSIONS = [
|
||||
"/path/to/your/extension1",
|
||||
"/path/to/your/extension2",
|
||||
]
|
||||
```
|
||||
|
||||
This instructs Superset to load and serve extensions directly from disk, so you can iterate quickly. Running `superset-extensions dev` watches for file changes and rebuilds assets automatically, while the Webpack development server (started separately with `npm run dev-server`) serves updated files as soon as they're modified. This enables immediate feedback for React components, styles, and other frontend code. Changes to backend files are also detected automatically and immediately synced, ensuring that both frontend and backend updates are reflected in your development environment.
|
||||
|
||||
Example output when running in development mode:
|
||||
|
||||
```
|
||||
superset-extensions dev
|
||||
|
||||
⚙️ Building frontend assets…
|
||||
✅ Frontend rebuilt
|
||||
✅ Backend files synced
|
||||
✅ Manifest updated
|
||||
👀 Watching for changes in: /dataset_references/frontend, /dataset_references/backend
|
||||
```
|
||||
|
||||
## Contributing Extension-Compatible Components
|
||||
|
||||
Components in `@apache-superset/core` are automatically documented in the Developer Docs. Simply add a component to the package and it will appear in the extension documentation.
|
||||
|
||||
### Requirements
|
||||
|
||||
1. **Location**: The component must be in `superset-frontend/packages/superset-core/src/ui/components/`
|
||||
2. **Exported**: The component must be exported from the package's `index.ts`
|
||||
3. **Story**: The component must have a Storybook story
|
||||
|
||||
### Creating a Story for Your Component
|
||||
|
||||
Create a story file with an `Interactive` export that defines args and argTypes:
|
||||
|
||||
```typescript
|
||||
// MyComponent.stories.tsx
|
||||
import { MyComponent } from '.';
|
||||
|
||||
export default {
|
||||
title: 'Extension Components/MyComponent',
|
||||
component: MyComponent,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'A brief description of what this component does.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Define an interactive story with args
|
||||
export const InteractiveMyComponent = (args) => <MyComponent {...args} />;
|
||||
|
||||
InteractiveMyComponent.args = {
|
||||
variant: 'primary',
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
InteractiveMyComponent.argTypes = {
|
||||
variant: {
|
||||
control: { type: 'select' },
|
||||
options: ['primary', 'secondary', 'danger'],
|
||||
},
|
||||
disabled: {
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### How Documentation is Generated
|
||||
|
||||
When the docs site is built (`yarn start` or `yarn build` in the `docs/` directory):
|
||||
|
||||
1. The `generate-extension-components` script scans all stories in `superset-core`
|
||||
2. For each story, it generates an MDX page with:
|
||||
- Component description
|
||||
- **Live interactive example** with controls extracted from `argTypes`
|
||||
- **Editable code playground** for experimentation
|
||||
- Props table from story `args`
|
||||
- Usage code snippet
|
||||
- Links to source files
|
||||
3. Pages appear automatically in **Developer Docs → Extensions → Components**
|
||||
|
||||
### Best Practices
|
||||
|
||||
- **Use descriptive titles**: The title path determines the component's location in docs (e.g., `Extension Components/Alert`)
|
||||
- **Define argTypes**: These become interactive controls in the documentation
|
||||
- **Provide default args**: These populate the initial state of the live example
|
||||
- **Write clear descriptions**: Help extension developers understand when to use each component
|
||||
241
docs/developer_docs/extensions/extension-points/editors.md
Normal file
241
docs/developer_docs/extensions/extension-points/editors.md
Normal file
@@ -0,0 +1,241 @@
|
||||
---
|
||||
title: Editors
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Editor Contributions
|
||||
|
||||
Extensions can replace Superset's default text editors with custom implementations. This allows you to provide enhanced editing experiences using alternative editor frameworks like Monaco, CodeMirror, or custom solutions.
|
||||
|
||||
## Overview
|
||||
|
||||
Superset uses text editors in various places throughout the application:
|
||||
|
||||
| Language | Locations |
|
||||
|----------|-----------|
|
||||
| `sql` | SQL Lab, Metric/Filter Popovers |
|
||||
| `json` | Dashboard Properties, Annotation Modal, Theme Modal |
|
||||
| `css` | Dashboard Properties, CSS Template Modal |
|
||||
| `markdown` | Dashboard Markdown component |
|
||||
| `yaml` | Template Params Editor |
|
||||
|
||||
By registering an editor provider for a language, your extension replaces the default Ace editor in **all** locations that use that language.
|
||||
|
||||
## Manifest Configuration
|
||||
|
||||
Declare editor contributions in your `extension.json` manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "monaco-editor",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"editors": [
|
||||
{
|
||||
"id": "monaco-editor.sql",
|
||||
"name": "Monaco SQL Editor",
|
||||
"languages": ["sql"],
|
||||
"description": "Monaco-based SQL editor with IntelliSense"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementing an Editor
|
||||
|
||||
Your editor component must implement the `EditorProps` interface and expose an `EditorHandle` via `forwardRef`. For the complete interface definitions, see `@apache-superset/core/api/editors.ts`.
|
||||
|
||||
### Key EditorProps
|
||||
|
||||
```typescript
|
||||
interface EditorProps {
|
||||
/** Controlled value */
|
||||
value: string;
|
||||
/** Content change handler */
|
||||
onChange: (value: string) => void;
|
||||
/** Language mode for syntax highlighting */
|
||||
language: EditorLanguage;
|
||||
/** Keyboard shortcuts to register */
|
||||
hotkeys?: EditorHotkey[];
|
||||
/** Callback when editor is ready with imperative handle */
|
||||
onReady?: (handle: EditorHandle) => void;
|
||||
/** Host-specific context (e.g., database info from SQL Lab) */
|
||||
metadata?: Record<string, unknown>;
|
||||
// ... additional props for styling, annotations, etc.
|
||||
}
|
||||
```
|
||||
|
||||
### Key EditorHandle Methods
|
||||
|
||||
```typescript
|
||||
interface EditorHandle {
|
||||
/** Focus the editor */
|
||||
focus(): void;
|
||||
/** Get the current editor content */
|
||||
getValue(): string;
|
||||
/** Get the current cursor position */
|
||||
getCursorPosition(): Position;
|
||||
/** Move the cursor to a specific position */
|
||||
moveCursorToPosition(position: Position): void;
|
||||
/** Set the selection range */
|
||||
setSelection(selection: Range): void;
|
||||
/** Scroll to a specific line */
|
||||
scrollToLine(line: number): void;
|
||||
// ... additional methods for text manipulation, annotations, etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Example Implementation
|
||||
|
||||
Here's an example of a Monaco-based SQL editor implementing the key interfaces shown above:
|
||||
|
||||
### MonacoSQLEditor.tsx
|
||||
|
||||
```typescript
|
||||
import { forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import type { editors } from '@apache-superset/core';
|
||||
|
||||
const MonacoSQLEditor = forwardRef<editors.EditorHandle, editors.EditorProps>(
|
||||
(props, ref) => {
|
||||
const { value, onChange, hotkeys, onReady } = props;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
|
||||
|
||||
// Implement EditorHandle interface
|
||||
const handle: editors.EditorHandle = {
|
||||
focus: () => editorRef.current?.focus(),
|
||||
getValue: () => editorRef.current?.getValue() ?? '',
|
||||
getCursorPosition: () => {
|
||||
const pos = editorRef.current?.getPosition();
|
||||
return { line: (pos?.lineNumber ?? 1) - 1, column: (pos?.column ?? 1) - 1 };
|
||||
},
|
||||
// ... implement remaining methods
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => handle, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const editor = monaco.editor.create(containerRef.current, { value, language: 'sql' });
|
||||
editorRef.current = editor;
|
||||
|
||||
editor.onDidChangeModelContent(() => onChange(editor.getValue()));
|
||||
|
||||
// Register hotkeys
|
||||
hotkeys?.forEach(hotkey => {
|
||||
editor.addAction({
|
||||
id: hotkey.name,
|
||||
label: hotkey.name,
|
||||
run: () => hotkey.exec(handle),
|
||||
});
|
||||
});
|
||||
|
||||
onReady?.(handle);
|
||||
return () => editor.dispose();
|
||||
}, []);
|
||||
|
||||
return <div ref={containerRef} style={{ height: '100%', width: '100%' }} />;
|
||||
},
|
||||
);
|
||||
|
||||
export default MonacoSQLEditor;
|
||||
```
|
||||
|
||||
### activate.ts
|
||||
|
||||
```typescript
|
||||
import { editors } from '@apache-superset/core';
|
||||
import MonacoSQLEditor from './MonacoSQLEditor';
|
||||
|
||||
export function activate(context) {
|
||||
// Register the Monaco editor for SQL using the contribution ID from extension.json
|
||||
const disposable = editors.registerEditorProvider(
|
||||
'monaco-sql-editor.sql',
|
||||
MonacoSQLEditor,
|
||||
);
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
```
|
||||
|
||||
## Handling Hotkeys
|
||||
|
||||
Superset passes keyboard shortcuts via the `hotkeys` prop. Each hotkey includes an `exec` function that receives the `EditorHandle`:
|
||||
|
||||
```typescript
|
||||
interface EditorHotkey {
|
||||
name: string;
|
||||
key: string; // e.g., "Ctrl-Enter", "Alt-Shift-F"
|
||||
description?: string;
|
||||
exec: (handle: EditorHandle) => void;
|
||||
}
|
||||
```
|
||||
|
||||
Your editor must register these hotkeys with your editor framework and call `exec(handle)` when triggered.
|
||||
|
||||
## Keywords
|
||||
|
||||
Superset passes static autocomplete suggestions via the `keywords` prop. These include table names, column names, and SQL functions based on the current database context:
|
||||
|
||||
```typescript
|
||||
interface EditorKeyword {
|
||||
name: string;
|
||||
value?: string; // Text to insert (defaults to name)
|
||||
meta?: string; // Category like "table", "column", "function"
|
||||
score?: number; // Sorting priority
|
||||
}
|
||||
```
|
||||
|
||||
Your editor should convert these to your framework's completion format and register them for autocomplete.
|
||||
|
||||
## Completion Providers
|
||||
|
||||
For dynamic autocomplete (e.g., fetching suggestions as the user types), implement and register a `CompletionProvider` via the `EditorHandle`:
|
||||
|
||||
```typescript
|
||||
const provider: CompletionProvider = {
|
||||
id: 'my-sql-completions',
|
||||
triggerCharacters: ['.', ' '],
|
||||
provideCompletions: async (content, position, context) => {
|
||||
// Use context.metadata for database info
|
||||
// Return array of CompletionItem
|
||||
return [
|
||||
{ label: 'SELECT', insertText: 'SELECT', kind: 'keyword' },
|
||||
// ...
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
// Register during editor initialization
|
||||
const disposable = handle.registerCompletionProvider(provider);
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[SQL Lab Extension Points](./sqllab)** - Learn about other SQL Lab customizations
|
||||
- **[Contribution Types](../contribution-types)** - Explore other contribution types
|
||||
- **[Development](../development)** - Set up your development environment
|
||||
221
docs/developer_docs/extensions/extension-points/sqllab.md
Normal file
221
docs/developer_docs/extensions/extension-points/sqllab.md
Normal file
@@ -0,0 +1,221 @@
|
||||
---
|
||||
title: SQL Lab
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# SQL Lab Extension Points
|
||||
|
||||
SQL Lab provides 4 extension points where extensions can contribute custom UI components. Each area serves a specific purpose and supports different types of customizations. These areas will evolve over time as new features are added to SQL Lab.
|
||||
|
||||
## Layout Overview
|
||||
|
||||
```
|
||||
┌──────────┬─────────────────────────────────────────┬─────────────┐
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ Editor │ │
|
||||
│ │ │ │
|
||||
│ Left │ │ Right │
|
||||
│ Sidebar ├─────────────────────────────────────────┤ Sidebar │
|
||||
│ │ │ │
|
||||
│ │ Panels │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
└──────────┴─────────────────────────────────────────┴─────────────┘
|
||||
```
|
||||
|
||||
| Extension Point | ID | Views | Menus | Description |
|
||||
| ----------------- | --------------------- | ----- | ----- | ---------------------------------------------- |
|
||||
| **Left Sidebar** | `sqllab.leftSidebar` | — | ✓ | Menu actions for the database explorer |
|
||||
| **Editor** | `sqllab.editor` | ✓\* | ✓ | Custom editors + toolbar actions |
|
||||
| **Right Sidebar** | `sqllab.rightSidebar` | ✓ | — | Custom panels (AI assistants, query analysis) |
|
||||
| **Panels** | `sqllab.panels` | ✓ | ✓ | Custom tabs + toolbar actions (data profiling) |
|
||||
|
||||
\*Editor views are contributed via [Editor Contributions](./editors), not standard view contributions.
|
||||
|
||||
## Customization Types
|
||||
|
||||
### Views
|
||||
|
||||
Extensions can add custom views (React components) to **Right Sidebar** and **Panels**. Views appear as new panels or tabs in their respective areas.
|
||||
|
||||
### Menus
|
||||
|
||||
Extensions can add toolbar actions to **Left Sidebar**, **Editor**, and **Panels**. Menu contributions support:
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ [Button] [Button] [•••] │
|
||||
├───────────────────────────────────────────────────────────────┤
|
||||
│ Area Content │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Action Type | Location | Use Case |
|
||||
| --------------------- | ---------------- | ----------------------------------------------------- |
|
||||
| **Primary Actions** | Toolbar buttons | Frequently used actions (e.g., run, refresh, add new) |
|
||||
| **Secondary Actions** | 3-dot menu (•••) | Less common actions (e.g., export, settings) |
|
||||
|
||||
### Custom Editors
|
||||
|
||||
Extensions can replace the default SQL editor with custom implementations (Monaco, CodeMirror, etc.). See [Editor Contributions](./editors) for details.
|
||||
|
||||
## Examples
|
||||
|
||||
### Adding a Panel
|
||||
|
||||
This example adds a "Data Profiler" panel to SQL Lab:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "data_profiler",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "data_profiler.main",
|
||||
"name": "Data Profiler"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { core } from '@apache-superset/core';
|
||||
import DataProfilerPanel from './DataProfilerPanel';
|
||||
|
||||
export function activate(context) {
|
||||
// Register the panel view with the ID declared in extension.json
|
||||
const disposable = core.registerView('data_profiler.main', <DataProfilerPanel />);
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
```
|
||||
|
||||
### Adding Actions to the Editor
|
||||
|
||||
This example adds primary, secondary, and context actions to the editor:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "query_tools",
|
||||
"version": "1.0.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "query_tools.format",
|
||||
"title": "Format Query",
|
||||
"icon": "FormatPainterOutlined"
|
||||
},
|
||||
{
|
||||
"command": "query_tools.explain",
|
||||
"title": "Explain Query"
|
||||
},
|
||||
{
|
||||
"command": "query_tools.copy_as_cte",
|
||||
"title": "Copy as CTE"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"sqllab": {
|
||||
"editor": {
|
||||
"primary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.format"
|
||||
}
|
||||
],
|
||||
"secondary": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.explain"
|
||||
}
|
||||
],
|
||||
"context": [
|
||||
{
|
||||
"view": "builtin.editor",
|
||||
"command": "query_tools.copy_as_cte"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { commands, sqlLab } from '@apache-superset/core';
|
||||
|
||||
export function activate(context) {
|
||||
// Register the commands declared in extension.json
|
||||
const formatCommand = commands.registerCommand(
|
||||
'query_tools.format',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Format the SQL query
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const explainCommand = commands.registerCommand(
|
||||
'query_tools.explain',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Show query explanation
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const copyAsCteCommand = commands.registerCommand(
|
||||
'query_tools.copy_as_cte',
|
||||
async () => {
|
||||
const tab = sqlLab.getCurrentTab();
|
||||
if (tab) {
|
||||
const editor = await tab.getEditor();
|
||||
// Copy selected text as CTE
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
context.subscriptions.push(formatCommand, explainCommand, copyAsCteCommand);
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Contribution Types](../contribution-types)** - Learn about other contribution types (commands, menus)
|
||||
- **[Development](../development)** - Set up your development environment
|
||||
- **[Quick Start](../quick-start)** - Build a complete extension
|
||||
459
docs/developer_docs/extensions/mcp.md
Normal file
459
docs/developer_docs/extensions/mcp.md
Normal file
@@ -0,0 +1,459 @@
|
||||
---
|
||||
title: MCP Integration
|
||||
hide_title: true
|
||||
sidebar_position: 8
|
||||
version: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# MCP Integration
|
||||
|
||||
Model Context Protocol (MCP) integration allows extensions to register custom AI agent capabilities that integrate seamlessly with Superset's MCP service. Extensions can provide both **tools** (executable functions) and **prompts** (interactive guidance) that AI agents can discover and use.
|
||||
|
||||
## What is MCP?
|
||||
|
||||
MCP enables extensions to extend Superset's AI capabilities in two ways:
|
||||
|
||||
### MCP Tools
|
||||
Tools are Python functions that AI agents can call to perform specific tasks. They provide executable functionality that extends Superset's capabilities.
|
||||
|
||||
**Examples of MCP tools:**
|
||||
- Data processing and transformation functions
|
||||
- Custom analytics calculations
|
||||
- Integration with external APIs
|
||||
- Specialized report generation
|
||||
- Business-specific operations
|
||||
|
||||
### MCP Prompts
|
||||
Prompts provide interactive guidance and context to AI agents. They help agents understand how to better assist users with specific workflows or domain knowledge.
|
||||
|
||||
**Examples of MCP prompts:**
|
||||
- Step-by-step workflow guidance
|
||||
- Domain-specific context and knowledge
|
||||
- Interactive troubleshooting assistance
|
||||
- Template generation helpers
|
||||
- Best practices recommendations
|
||||
|
||||
## Getting Started
|
||||
|
||||
## MCP Tools
|
||||
|
||||
### Basic Tool Registration
|
||||
|
||||
The simplest way to create an MCP tool is using the `@tool` decorator:
|
||||
|
||||
```python
|
||||
from superset_core.mcp import tool
|
||||
|
||||
@tool
|
||||
def hello_world() -> dict:
|
||||
"""A simple greeting tool."""
|
||||
return {"message": "Hello from my extension!"}
|
||||
```
|
||||
|
||||
This creates a tool that AI agents can call by name. The tool name defaults to the function name.
|
||||
|
||||
### Decorator Parameters
|
||||
|
||||
The `@tool` decorator accepts several optional parameters:
|
||||
|
||||
**Parameter details:**
|
||||
- **`name`**: Tool identifier (AI agents use this to call your tool)
|
||||
- **`description`**: Explains what the tool does (helps AI agents decide when to use it)
|
||||
- **`tags`**: Categories for organization and discovery
|
||||
- **`protect`**: Whether the tool requires user authentication (defaults to `True`)
|
||||
|
||||
### Naming Your Tools
|
||||
|
||||
For extensions, include your extension ID in tool names to avoid conflicts:
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a more comprehensive example showing best practices:
|
||||
|
||||
```python
|
||||
# backend/mcp_tools.py
|
||||
import random
|
||||
from datetime import datetime, timezone
|
||||
from pydantic import BaseModel, Field
|
||||
from superset_core.mcp import tool
|
||||
|
||||
class RandomNumberRequest(BaseModel):
|
||||
"""Request schema for random number generation."""
|
||||
|
||||
min_value: int = Field(
|
||||
description="Minimum value (inclusive) for random number generation",
|
||||
ge=-2147483648,
|
||||
le=2147483647
|
||||
)
|
||||
max_value: int = Field(
|
||||
description="Maximum value (inclusive) for random number generation",
|
||||
ge=-2147483648,
|
||||
le=2147483647
|
||||
)
|
||||
|
||||
@tool(
|
||||
name="example_extension.random_number",
|
||||
tags=["extension", "utility", "random", "generator"]
|
||||
)
|
||||
def random_number_generator(request: RandomNumberRequest) -> dict:
|
||||
"""
|
||||
Generate a random integer between specified bounds.
|
||||
|
||||
This tool validates input ranges and provides detailed error messages
|
||||
for invalid requests.
|
||||
"""
|
||||
|
||||
# Validate business logic (Pydantic handles type/range validation)
|
||||
if request.min_value > request.max_value:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": f"min_value ({request.min_value}) cannot be greater than max_value ({request.max_value})",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
||||
# Generate random number
|
||||
result = random.randint(request.min_value, request.max_value)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"random_number": result,
|
||||
"min_value": request.min_value,
|
||||
"max_value": request.max_value,
|
||||
"range_size": request.max_value - request.min_value + 1,
|
||||
"timestamp": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Response Format
|
||||
|
||||
Use consistent response structures:
|
||||
|
||||
```python
|
||||
# Success response
|
||||
{
|
||||
"status": "success",
|
||||
"result": "your_data_here",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
|
||||
# Error response
|
||||
{
|
||||
"status": "error",
|
||||
"error": "Clear error message",
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Write clear descriptions and docstrings:
|
||||
|
||||
```python
|
||||
@tool(
|
||||
name="my_extension.process_data",
|
||||
description="Process customer data and generate insights. Requires valid customer ID and date range.",
|
||||
tags=["analytics", "customer", "reporting"]
|
||||
)
|
||||
def process_data(customer_id: int, start_date: str, end_date: str) -> dict:
|
||||
"""
|
||||
Process customer data for the specified date range.
|
||||
|
||||
This tool analyzes customer behavior patterns and generates
|
||||
actionable insights for business decision-making.
|
||||
|
||||
Args:
|
||||
customer_id: Unique customer identifier
|
||||
start_date: Analysis start date (YYYY-MM-DD format)
|
||||
end_date: Analysis end date (YYYY-MM-DD format)
|
||||
|
||||
Returns:
|
||||
Dictionary containing analysis results and recommendations
|
||||
"""
|
||||
# Implementation here
|
||||
pass
|
||||
```
|
||||
|
||||
### Tool Naming
|
||||
|
||||
- **Extension tools**: Use prefixed names like `my_extension.tool_name`
|
||||
- **Descriptive names**: `calculate_tax_amount` vs `calculate`
|
||||
- **Consistent naming**: Follow patterns within your extension
|
||||
|
||||
## How AI Agents Use Your Tools
|
||||
|
||||
Once registered, AI agents can discover and use your tools automatically:
|
||||
|
||||
```
|
||||
User: "Generate a random number between 1 and 100"
|
||||
Agent: I'll use the random number generator tool.
|
||||
→ Calls: example_extension.random_number(min_value=1, max_value=100)
|
||||
← Returns: {"status": "success", "random_number": 42, ...}
|
||||
Agent: I generated the number 42 for you.
|
||||
```
|
||||
|
||||
The AI agent sees your tool's:
|
||||
- **Name**: How to call it
|
||||
- **Description**: What it does and when to use it
|
||||
- **Parameters**: What inputs it expects (from Pydantic schema)
|
||||
- **Tags**: Categories for discovery
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tool Not Available to AI Agents
|
||||
|
||||
1. **Check extension registration**: Verify your tool module is listed in extension entrypoints
|
||||
2. **Verify decorator**: Ensure `@tool` is correctly applied
|
||||
3. **Extension loading**: Confirm your extension is installed and enabled
|
||||
|
||||
### Input Validation Errors
|
||||
|
||||
1. **Pydantic models**: Ensure field types match expected inputs
|
||||
2. **Field constraints**: Check min/max values and string lengths are reasonable
|
||||
3. **Required fields**: Verify which parameters are required vs optional
|
||||
|
||||
### Runtime Issues
|
||||
|
||||
1. **Error handling**: Add try/catch blocks with clear error messages
|
||||
2. **Response format**: Use consistent status/error/timestamp structure
|
||||
3. **Testing**: Test your tools with various input scenarios
|
||||
|
||||
### Development Tips
|
||||
|
||||
1. **Start simple**: Begin with basic tools, add complexity gradually
|
||||
2. **Test locally**: Use MCP clients (like Claude Desktop) to test your tools
|
||||
3. **Clear descriptions**: Write tool descriptions as if explaining to a new user
|
||||
4. **Meaningful tags**: Use tags that help categorize and discover tools
|
||||
5. **Error messages**: Provide specific, actionable error messages
|
||||
|
||||
## MCP Prompts
|
||||
|
||||
### Basic Prompt Registration
|
||||
|
||||
Create interactive prompts using the `@prompt` decorator:
|
||||
|
||||
```python
|
||||
from superset_core.mcp import prompt
|
||||
from fastmcp import Context
|
||||
|
||||
@prompt("my_extension.workflow_guide")
|
||||
async def workflow_guide(ctx: Context) -> str:
|
||||
"""Interactive guide for data analysis workflows."""
|
||||
return """
|
||||
# Data Analysis Workflow Guide
|
||||
|
||||
Here's a step-by-step approach to effective data analysis in Superset:
|
||||
|
||||
## 1. Data Discovery
|
||||
- Start by exploring your datasets using the dataset browser
|
||||
- Check data quality and identify key metrics
|
||||
- Look for patterns and relationships in your data
|
||||
|
||||
## 2. Chart Creation
|
||||
- Choose appropriate visualizations for your data types
|
||||
- Apply filters to focus on relevant subsets
|
||||
- Configure proper aggregations and groupings
|
||||
|
||||
## 3. Dashboard Assembly
|
||||
- Combine related charts into coherent dashboards
|
||||
- Use filters and parameters for interactivity
|
||||
- Add markdown components for context and explanations
|
||||
|
||||
Would you like guidance on any specific step?
|
||||
"""
|
||||
```
|
||||
|
||||
### Advanced Prompt Examples
|
||||
|
||||
#### Domain-Specific Context
|
||||
|
||||
```python
|
||||
@prompt(
|
||||
"sales_extension.sales_analysis_guide",
|
||||
title="Sales Analysis Guide",
|
||||
description="Specialized guidance for sales data analysis workflows"
|
||||
)
|
||||
async def sales_analysis_guide(ctx: Context) -> str:
|
||||
"""Provides sales-specific analysis guidance and best practices."""
|
||||
return """
|
||||
# Sales Data Analysis Best Practices
|
||||
|
||||
## Key Metrics to Track
|
||||
- **Revenue Growth**: Month-over-month and year-over-year trends
|
||||
- **Conversion Rates**: Lead-to-opportunity-to-close ratios
|
||||
- **Customer Lifetime Value**: Total value per customer segment
|
||||
- **Sales Cycle Length**: Time from lead to close by product/region
|
||||
|
||||
## Recommended Chart Types
|
||||
- **Time Series**: Revenue trends, seasonal patterns
|
||||
- **Funnel Charts**: Conversion analysis across sales stages
|
||||
- **Geographic Maps**: Regional performance comparison
|
||||
- **Cohort Analysis**: Customer retention and growth patterns
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
- Don't mix different time granularities without proper context
|
||||
- Always normalize for business days when comparing periods
|
||||
- Consider external factors (holidays, market events) in analysis
|
||||
- Segment by relevant dimensions (product, region, channel)
|
||||
|
||||
## Next Steps
|
||||
1. Identify your primary sales KPIs
|
||||
2. Create baseline trend charts for each metric
|
||||
3. Build comparative views across segments
|
||||
4. Set up automated alerts for significant changes
|
||||
"""
|
||||
```
|
||||
|
||||
#### Interactive Troubleshooting
|
||||
|
||||
```python
|
||||
@prompt("support_extension.troubleshoot_charts")
|
||||
async def troubleshoot_charts(ctx: Context) -> str:
|
||||
"""Interactive troubleshooting assistant for chart issues."""
|
||||
return """
|
||||
# Chart Troubleshooting Assistant
|
||||
|
||||
Let's diagnose your chart issue step by step:
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### 🚫 No Data Showing
|
||||
**Possible causes:**
|
||||
- Filters are too restrictive
|
||||
- Date range doesn't match your data
|
||||
- Database connection issues
|
||||
- Missing permissions
|
||||
|
||||
**Check:** Try removing all filters and expanding the date range first.
|
||||
|
||||
### 📊 Unexpected Aggregation Results
|
||||
**Possible causes:**
|
||||
- Incorrect grouping dimensions
|
||||
- Wrong aggregation function (SUM vs COUNT vs AVG)
|
||||
- Data quality issues (duplicates, nulls)
|
||||
- Time zone mismatches
|
||||
|
||||
**Check:** Verify your GROUP BY columns and aggregation logic.
|
||||
|
||||
### 🐌 Slow Performance
|
||||
**Possible causes:**
|
||||
- Large dataset without proper indexing
|
||||
- Complex joins or calculations
|
||||
- Missing query optimizations
|
||||
- Resource constraints
|
||||
|
||||
**Check:** Simplify the query and add appropriate filters first.
|
||||
|
||||
## Debug Steps
|
||||
1. **Start Simple**: Create a basic count query first
|
||||
2. **Add Gradually**: Introduce complexity step by step
|
||||
3. **Check SQL**: Review the generated SQL for issues
|
||||
4. **Test Data**: Verify with a small sample first
|
||||
|
||||
What specific issue are you experiencing?
|
||||
"""
|
||||
```
|
||||
|
||||
### Prompt Best Practices
|
||||
|
||||
#### Content Structure
|
||||
- **Use clear headings** and sections for easy navigation
|
||||
- **Provide actionable steps** rather than just theory
|
||||
- **Include examples** relevant to the user's domain
|
||||
- **Offer next steps** to continue the workflow
|
||||
|
||||
#### Interactive Design
|
||||
- **Ask questions** to engage the user
|
||||
- **Provide options** for different scenarios
|
||||
- **Reference specific Superset features** by name
|
||||
- **Link to related tools** when appropriate
|
||||
|
||||
#### Context Awareness
|
||||
```python
|
||||
@prompt("analytics_extension.context_aware_guide")
|
||||
async def context_aware_guide(ctx: Context) -> str:
|
||||
"""Provides guidance based on current user context."""
|
||||
# Access user information if available
|
||||
user_info = getattr(ctx, 'user', None)
|
||||
|
||||
guidance = """# Personalized Analytics Guide\n\n"""
|
||||
|
||||
if user_info:
|
||||
guidance += f"Welcome back! Here's guidance tailored for your role:\n\n"
|
||||
|
||||
guidance += """
|
||||
## Getting Started
|
||||
Based on your previous activity, here are recommended next steps:
|
||||
|
||||
1. **Review Recent Dashboards**: Check your most-used dashboards for updates
|
||||
2. **Explore New Data**: Look for recently added datasets in your domain
|
||||
3. **Share Insights**: Consider sharing successful analyses with your team
|
||||
|
||||
## Advanced Techniques
|
||||
- Set up automated alerts for key metrics
|
||||
- Create parameterized dashboards for different audiences
|
||||
- Use SQL Lab for complex custom analyses
|
||||
"""
|
||||
|
||||
return guidance
|
||||
```
|
||||
|
||||
## Combining Tools and Prompts
|
||||
|
||||
Extensions can provide both tools and prompts that work together:
|
||||
|
||||
```python
|
||||
# Tool for data processing
|
||||
@tool("analytics_extension.calculate_metrics")
|
||||
def calculate_metrics(data: dict) -> dict:
|
||||
"""Calculate advanced analytics metrics."""
|
||||
# Implementation here
|
||||
pass
|
||||
|
||||
# Prompt that guides users to the tool
|
||||
@prompt("analytics_extension.metrics_guide")
|
||||
async def metrics_guide(ctx: Context) -> str:
|
||||
"""Guide users through advanced metrics calculation."""
|
||||
return """
|
||||
# Advanced Metrics Calculation
|
||||
|
||||
Use the `calculate_metrics` tool to compute specialized analytics:
|
||||
|
||||
## Available Metrics
|
||||
- Customer Lifetime Value (CLV)
|
||||
- Cohort Retention Rates
|
||||
- Statistical Significance Tests
|
||||
- Predictive Trend Analysis
|
||||
|
||||
## Usage
|
||||
Call the tool with your dataset to get detailed calculations
|
||||
and recommendations for visualization approaches.
|
||||
|
||||
Would you like to calculate metrics for your current dataset?
|
||||
"""
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Development](./development)** - Project structure, APIs, and dev workflow
|
||||
- **[Security](./security)** - Security best practices for extensions
|
||||
55
docs/developer_docs/extensions/overview.md
Normal file
55
docs/developer_docs/extensions/overview.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Overview
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Overview
|
||||
|
||||
Apache Superset's extension system enables organizations to build custom features without modifying the core codebase. Inspired by the [VS Code extension model](https://code.visualstudio.com/api), this architecture addresses a long-standing challenge: teams previously had to fork Superset or make invasive modifications to add capabilities like query optimizers, custom panels, or specialized integrations—resulting in maintenance overhead and codebase fragmentation.
|
||||
|
||||
The extension system introduces a modular, plugin-based architecture where both built-in features and external extensions use the same well-defined APIs. This "lean core" approach ensures that any capability available to Superset's internal features is equally accessible to community-developed extensions, fostering a vibrant ecosystem while reducing the maintenance burden on core contributors.
|
||||
|
||||
## What are Superset Extensions?
|
||||
|
||||
Superset extensions are self-contained `.supx` packages that extend the platform's capabilities through standardized contribution points. Each extension can include both frontend (React/TypeScript) and backend (Python) components, bundled together and loaded dynamically at runtime using Webpack Module Federation.
|
||||
|
||||
## Extension Capabilities
|
||||
|
||||
Extensions can provide:
|
||||
|
||||
- **Custom UI Components**: New panels, views, and interactive elements
|
||||
- **Commands and Menus**: Custom actions accessible via menus and keyboard shortcuts
|
||||
- **REST API Endpoints**: Backend services under the `/api/v1/extensions/` namespace
|
||||
- **MCP Tools and Prompts**: AI agent capabilities for enhanced user assistance
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Quick Start](./quick-start)** - Build your first extension with a complete walkthrough
|
||||
- **[Architecture](./architecture)** - Design principles and system overview
|
||||
- **[Dependencies](./dependencies)** - Managing dependencies and understanding API stability
|
||||
- **[Contribution Types](./contribution-types)** - Available extension points
|
||||
- **[Development](./development)** - Project structure, APIs, and development workflow
|
||||
- **[Deployment](./deployment)** - Packaging and deploying extensions
|
||||
- **[MCP Integration](./mcp)** - Adding AI agent capabilities using extensions
|
||||
- **[Security](./security)** - Security considerations and best practices
|
||||
- **[Tasks](./tasks)** - Framework for creating and managing long running tasks
|
||||
- **[Community Extensions](./registry)** - Browse extensions shared by the community
|
||||
535
docs/developer_docs/extensions/quick-start.md
Normal file
535
docs/developer_docs/extensions/quick-start.md
Normal file
@@ -0,0 +1,535 @@
|
||||
---
|
||||
title: Quick Start
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Quick Start
|
||||
|
||||
This guide walks you through creating your first Superset extension - a simple "Hello World" panel that displays a message fetched from a backend API endpoint. You'll learn the essential structure and patterns for building full-stack Superset extensions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting, ensure you have:
|
||||
|
||||
- Node.js and npm compatible with your Superset version
|
||||
- Python compatible with your Superset version
|
||||
- A running Superset development environment
|
||||
- Basic knowledge of React, TypeScript, and Flask
|
||||
|
||||
## Step 1: Install the Extensions CLI
|
||||
|
||||
First, install the Apache Superset Extensions CLI:
|
||||
|
||||
```bash
|
||||
pip install apache-superset-extensions-cli
|
||||
```
|
||||
|
||||
## Step 2: Create a New Extension
|
||||
|
||||
Use the CLI to scaffold a new extension project. Extensions can include frontend functionality, backend functionality, or both, depending on your needs. This quickstart demonstrates a full-stack extension with both frontend UI components and backend API endpoints to show the complete integration pattern.
|
||||
|
||||
```bash
|
||||
superset-extensions init
|
||||
```
|
||||
|
||||
The CLI will prompt you for information using a three-step publisher workflow:
|
||||
|
||||
```
|
||||
Extension display name: Hello World
|
||||
Extension name (hello-world): hello-world
|
||||
Publisher (e.g., my-org): my-org
|
||||
Initial version [0.1.0]: 0.1.0
|
||||
License [Apache-2.0]: Apache-2.0
|
||||
Include frontend? [Y/n]: Y
|
||||
Include backend? [Y/n]: Y
|
||||
```
|
||||
|
||||
**Publisher Namespaces**: Extensions use organizational namespaces similar to VS Code extensions, providing collision-safe naming across organizations:
|
||||
- **NPM package**: `@my-org/hello-world` (scoped package for frontend distribution)
|
||||
- **Module Federation name**: `myOrg_helloWorld` (collision-safe JavaScript identifier)
|
||||
- **Backend package**: `my_org-hello_world` (collision-safe Python distribution name)
|
||||
- **Python namespace**: `superset_extensions.my_org.hello_world`
|
||||
|
||||
This approach ensures that extensions from different organizations cannot conflict, even if they use the same technical name (e.g., both `acme.dashboard-widgets` and `corp.dashboard-widgets` can coexist).
|
||||
|
||||
This creates a complete project structure:
|
||||
|
||||
```
|
||||
my-org.hello-world/
|
||||
├── extension.json # Extension metadata and configuration
|
||||
├── backend/ # Backend Python code
|
||||
│ ├── src/
|
||||
│ │ └── superset_extensions/
|
||||
│ │ └── my_org/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── hello_world/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── entrypoint.py # Backend registration
|
||||
│ └── pyproject.toml
|
||||
└── frontend/ # Frontend TypeScript/React code
|
||||
├── src/
|
||||
│ └── index.tsx # Frontend entry point
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── webpack.config.js
|
||||
```
|
||||
|
||||
## Step 3: Configure Extension Metadata
|
||||
|
||||
The generated `extension.json` contains basic metadata. Update it to register your panel in SQL Lab:
|
||||
|
||||
```json
|
||||
{
|
||||
"publisher": "my-org",
|
||||
"name": "hello-world",
|
||||
"displayName": "Hello World",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"frontend": {
|
||||
"contributions": {
|
||||
"views": {
|
||||
"sqllab": {
|
||||
"panels": [
|
||||
{
|
||||
"id": "my-org.hello-world.main",
|
||||
"name": "Hello World"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"moduleFederation": {
|
||||
"exposes": ["./index"],
|
||||
"name": "myOrg_helloWorld"
|
||||
}
|
||||
},
|
||||
"backend": {
|
||||
"entryPoints": ["superset_extensions.my_org.hello_world.entrypoint"],
|
||||
"files": ["backend/src/superset_extensions/my_org/hello_world/**/*.py"]
|
||||
},
|
||||
"permissions": ["can_read"]
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: The `moduleFederation.name` uses collision-safe naming (`myOrg_helloWorld`), and backend entry points use the full nested Python namespace (`superset_extensions.my_org.hello_world`).
|
||||
|
||||
**Key fields:**
|
||||
|
||||
- `publisher`: Organizational namespace for the extension
|
||||
- `name`: Technical identifier (kebab-case)
|
||||
- `displayName`: Human-readable name shown to users
|
||||
- `frontend.contributions.views.sqllab.panels`: Registers your panel in SQL Lab
|
||||
- `backend.entryPoints`: Python modules to load eagerly when extension starts
|
||||
|
||||
## Step 4: Create Backend API
|
||||
|
||||
The CLI generated a basic `backend/src/superset_extensions/my_org/hello_world/entrypoint.py`. We'll create an API endpoint.
|
||||
|
||||
**Create `backend/src/superset_extensions/my_org/hello_world/api.py`**
|
||||
|
||||
```python
|
||||
from flask import Response
|
||||
from flask_appbuilder.api import expose, protect, safe
|
||||
from superset_core.api.rest_api import RestApi
|
||||
|
||||
|
||||
class HelloWorldAPI(RestApi):
|
||||
resource_name = "hello_world"
|
||||
openapi_spec_tag = "Hello World"
|
||||
class_permission_name = "hello_world"
|
||||
|
||||
@expose("/message", methods=("GET",))
|
||||
@protect()
|
||||
@safe
|
||||
def get_message(self) -> Response:
|
||||
"""Gets a hello world message
|
||||
---
|
||||
get:
|
||||
description: >-
|
||||
Get a hello world message from the backend
|
||||
responses:
|
||||
200:
|
||||
description: Hello world message
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
result:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
"""
|
||||
return self.response(
|
||||
200,
|
||||
result={"message": "Hello from the backend!"}
|
||||
)
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
|
||||
- Extends `RestApi` from `superset_core.api.types.rest_api`
|
||||
- Uses Flask-AppBuilder decorators (`@expose`, `@protect`, `@safe`)
|
||||
- Returns responses using `self.response(status_code, result=data)`
|
||||
- The endpoint will be accessible at `/extensions/my-org/hello-world/message`
|
||||
- OpenAPI docstrings are crucial - Flask-AppBuilder uses them to automatically generate interactive API documentation at `/swagger/v1`, allowing developers to explore endpoints, understand schemas, and test the API directly from the browser
|
||||
|
||||
**Update `backend/src/superset_extensions/my_org/hello_world/entrypoint.py`**
|
||||
|
||||
Replace the generated print statement with API registration:
|
||||
|
||||
```python
|
||||
from superset_core.api import rest_api
|
||||
|
||||
from .api import HelloWorldAPI
|
||||
|
||||
rest_api.add_extension_api(HelloWorldAPI)
|
||||
```
|
||||
|
||||
This registers your API with Superset when the extension loads.
|
||||
|
||||
## Step 5: Create Frontend Component
|
||||
|
||||
The CLI generates the frontend configuration files. Below are the key configurations that enable Module Federation integration with Superset.
|
||||
|
||||
**`frontend/package.json`**
|
||||
|
||||
The `@apache-superset/core` package must be listed in both `peerDependencies` (to declare runtime compatibility) and `devDependencies` (to provide TypeScript types during build):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@my-org/hello-world",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"start": "webpack serve --mode development",
|
||||
"build": "webpack --stats-error-details --mode production"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@apache-superset/core": "^x.x.x",
|
||||
"react": "^x.x.x",
|
||||
"react-dom": "^x.x.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apache-superset/core": "^x.x.x",
|
||||
"@types/react": "^x.x.x",
|
||||
"ts-loader": "^x.x.x",
|
||||
"typescript": "^x.x.x",
|
||||
"webpack": "^5.x.x",
|
||||
"webpack-cli": "^x.x.x",
|
||||
"webpack-dev-server": "^x.x.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`frontend/webpack.config.js`**
|
||||
|
||||
The webpack configuration requires specific settings for Module Federation. Key settings include `externalsType: "window"` and `externals` to map `@apache-superset/core` to `window.superset` at runtime, `import: false` for shared modules to use the host's React instead of bundling a separate copy, and `remoteEntry.[contenthash].js` for cache busting:
|
||||
|
||||
```javascript
|
||||
const path = require("path");
|
||||
const { ModuleFederationPlugin } = require("webpack").container;
|
||||
const packageConfig = require("./package.json");
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const isProd = argv.mode === "production";
|
||||
|
||||
return {
|
||||
entry: isProd ? {} : "./src/index.tsx",
|
||||
mode: isProd ? "production" : "development",
|
||||
devServer: {
|
||||
port: 3001,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
filename: isProd ? undefined : "[name].[contenthash].js",
|
||||
chunkFilename: "[name].[contenthash].js",
|
||||
clean: true,
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
publicPath: `/api/v1/extensions/my-org/hello-world/`,
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
||||
},
|
||||
// Map @apache-superset/core imports to window.superset at runtime
|
||||
externalsType: "window",
|
||||
externals: {
|
||||
"@apache-superset/core": "superset",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ModuleFederationPlugin({
|
||||
name: "myOrg_helloWorld",
|
||||
filename: "remoteEntry.[contenthash].js",
|
||||
exposes: {
|
||||
"./index": "./src/index.tsx",
|
||||
},
|
||||
shared: {
|
||||
react: {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies.react,
|
||||
import: false, // Use host's React, don't bundle
|
||||
},
|
||||
"react-dom": {
|
||||
singleton: true,
|
||||
requiredVersion: packageConfig.peerDependencies["react-dom"],
|
||||
import: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**`frontend/tsconfig.json`**
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
```
|
||||
|
||||
**Create `frontend/src/HelloWorldPanel.tsx`**
|
||||
|
||||
Create a new file for the component implementation:
|
||||
|
||||
```tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { authentication } from '@apache-superset/core';
|
||||
|
||||
const HelloWorldPanel: React.FC = () => {
|
||||
const [message, setMessage] = useState<string>('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMessage = async () => {
|
||||
try {
|
||||
const csrfToken = await authentication.getCSRFToken();
|
||||
const response = await fetch('/extensions/my-org/hello-world/message', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken!,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server returned ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setMessage(data.result.message);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMessage();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ padding: '20px', textAlign: 'center' }}>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div style={{ padding: '20px', color: 'red' }}>
|
||||
<strong>Error:</strong> {error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<h3>Hello World Extension</h3>
|
||||
<div
|
||||
style={{
|
||||
padding: '16px',
|
||||
backgroundColor: '#f6ffed',
|
||||
border: '1px solid #b7eb8f',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
<strong>{message}</strong>
|
||||
</div>
|
||||
<p>This message was fetched from the backend API! 🎉</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelloWorldPanel;
|
||||
```
|
||||
|
||||
**Update `frontend/src/index.tsx`**
|
||||
|
||||
Replace the generated code with the extension entry point:
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import { core } from '@apache-superset/core';
|
||||
import HelloWorldPanel from './HelloWorldPanel';
|
||||
|
||||
export const activate = (context: core.ExtensionContext) => {
|
||||
context.disposables.push(
|
||||
core.registerViewProvider('my-org.hello-world.main', () => <HelloWorldPanel />),
|
||||
);
|
||||
};
|
||||
|
||||
export const deactivate = () => {};
|
||||
```
|
||||
|
||||
**Key patterns:**
|
||||
|
||||
- `activate` function is called when the extension loads
|
||||
- `core.registerViewProvider` registers the component with ID `my-org.hello-world.main` (matching `extension.json`)
|
||||
- `authentication.getCSRFToken()` retrieves the CSRF token for API calls
|
||||
- Fetch calls to `/extensions/{publisher}/{name}/{endpoint}` reach your backend API
|
||||
- `context.disposables.push()` ensures proper cleanup
|
||||
|
||||
## Step 6: Install Dependencies
|
||||
|
||||
Install the frontend dependencies:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
cd ..
|
||||
```
|
||||
|
||||
## Step 7: Package the Extension
|
||||
|
||||
Create a `.supx` bundle for deployment:
|
||||
|
||||
```bash
|
||||
superset-extensions bundle
|
||||
```
|
||||
|
||||
This command automatically:
|
||||
|
||||
- Builds frontend assets using Webpack with Module Federation
|
||||
- Collects backend Python source files
|
||||
- Creates a `dist/` directory with:
|
||||
- `manifest.json` - Build metadata and asset references
|
||||
- `frontend/dist/` - Built frontend assets (remoteEntry.js, chunks)
|
||||
- `backend/` - Python source files
|
||||
- Packages everything into `my-org.hello-world-0.1.0.supx` - a zip archive with the specific structure required by Superset
|
||||
|
||||
## Step 8: Deploy to Superset
|
||||
|
||||
To deploy your extension, you need to enable extensions support and configure where Superset should load them from.
|
||||
|
||||
**Configure Superset**
|
||||
|
||||
Add the following to your `superset_config.py`:
|
||||
|
||||
```python
|
||||
# Enable extensions feature
|
||||
FEATURE_FLAGS = {
|
||||
"ENABLE_EXTENSIONS": True,
|
||||
}
|
||||
|
||||
# Set the directory where extensions are stored
|
||||
EXTENSIONS_PATH = "/path/to/extensions/folder"
|
||||
```
|
||||
|
||||
**Copy Extension Bundle**
|
||||
|
||||
Copy your `.supx` file to the configured extensions path:
|
||||
|
||||
```bash
|
||||
cp my-org.hello-world-0.1.0.supx /path/to/extensions/folder/
|
||||
```
|
||||
|
||||
**Restart Superset**
|
||||
|
||||
Restart your Superset instance to load the extension:
|
||||
|
||||
```bash
|
||||
# Restart your Superset server
|
||||
superset run
|
||||
```
|
||||
|
||||
Superset will extract and validate the extension metadata, load the assets, register the extension with its capabilities, and make it available for use.
|
||||
|
||||
## Step 9: Test Your Extension
|
||||
|
||||
1. **Open SQL Lab** in Superset
|
||||
2. Look for the **"Hello World"** panel in the panels dropdown or sidebar
|
||||
3. Open the panel - it should display "Hello from the backend!"
|
||||
4. Check that the message was fetched from your API endpoint
|
||||
|
||||
## Understanding the Flow
|
||||
|
||||
Here's what happens when your extension loads:
|
||||
|
||||
1. **Superset starts**: Reads `extension.json` and loads backend entrypoint
|
||||
2. **Backend registration**: `entrypoint.py` registers your API via `rest_api.add_extension_api()`
|
||||
3. **Frontend loads**: When SQL Lab opens, Superset fetches the remote entry file
|
||||
4. **Module Federation**: Webpack loads your extension code and resolves `@apache-superset/core` to `window.superset`
|
||||
5. **Activation**: `activate()` is called, registering your view provider
|
||||
6. **Rendering**: When the user opens your panel, React renders `<HelloWorldPanel />`
|
||||
7. **API call**: Component fetches data from `/extensions/my-org/hello-world/message`
|
||||
8. **Backend response**: Your Flask API returns the hello world message
|
||||
9. **Display**: Component shows the message to the user
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a working extension, explore:
|
||||
|
||||
- **[Development](./development)** - Project structure, APIs, and development workflow
|
||||
- **[Contribution Types](./contribution-types)** - Other contribution points beyond panels
|
||||
- **[Deployment](./deployment)** - Packaging and deploying your extension
|
||||
- **[Security](./security)** - Security best practices for extensions
|
||||
|
||||
For a complete real-world example, examine the query insights extension in the Superset codebase.
|
||||
55
docs/developer_docs/extensions/registry.md
Normal file
55
docs/developer_docs/extensions/registry.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Community Extensions
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Community Extensions
|
||||
|
||||
This page serves as a registry of community-created Superset extensions. These extensions are developed and maintained by community members and are not officially supported or vetted by the Apache Superset project. **Before installing any community extension, administrators are responsible for evaluating the extension's source code for security vulnerabilities, performance impact, UI/UX quality, and compatibility with their Superset deployment.**
|
||||
|
||||
## Extensions
|
||||
|
||||
| Name | Description | Author | Preview |
|
||||
| ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Extensions API Explorer](https://github.com/michael-s-molina/superset-extensions/tree/main/api_explorer) | A SQL Lab panel that demonstrates the Extensions API by providing an interactive explorer for testing commands like getTabs, getCurrentTab, and getDatabases. Useful for extension developers to understand and experiment with the available APIs. | Michael S. Molina | <a href="/img/extensions/api-explorer.png" target="_blank"><img src="/img/extensions/api-explorer.png" alt="Extensions API Explorer" width="120" /></a> |
|
||||
| [SQL Query Flow Visualizer](https://github.com/msyavuz/superset-sql-visualizer) | A SQL Lab panel that transforms SQL queries into interactive flow diagrams, helping developers and analysts understand query execution paths and data relationships. | Mehmet Salih Yavuz | <a href="/img/extensions/sql-flow-visualizer.png" target="_blank"><img src="/img/extensions/sql-flow-visualizer.png" alt="SQL Flow Visualizer" width="120" /></a> |
|
||||
| [SQL Lab Export to Google Sheets](https://github.com/michael-s-molina/superset-extensions/tree/main/sqllab_gsheets) | A Superset extension that allows users to export SQL Lab query results directly to Google Sheets. | Michael S. Molina | <a href="/img/extensions/gsheets-export.png" target="_blank"><img src="/img/extensions/gsheets-export.png" alt="SQL Lab Export to Google Sheets" width="120" /></a> |
|
||||
| [SQL Lab Export to Parquet](https://github.com/rusackas/superset-extensions/tree/main/sqllab_parquet) | Export SQL Lab query results directly to Apache Parquet format with Snappy compression. | Evan Rusackas | <a href="/img/extensions/parquet-export.png" target="_blank"><img src="/img/extensions/parquet-export.png" alt="SQL Lab Export to Parquet" width="120" /></a> |
|
||||
| [SQL Lab Query Comparison](https://github.com/michael-s-molina/superset-extensions/tree/main/query_comparison) | A SQL Lab extension that enables side-by-side comparison of query results across different tabs, with GitHub-style diff visualization showing added/removed rows and columns. | Michael S. Molina | <a href="/img/extensions/query-comparison.png" target="_blank"><img src="/img/extensions/query-comparison.png" alt="Query Comparison" width="120" /></a> |
|
||||
| [SQL Lab Result Stats](https://github.com/michael-s-molina/superset-extensions/tree/main/result_stats) | A SQL Lab extension that automatically computes statistics for query results, providing type-aware analysis including numeric metrics (min, max, mean, median, std dev), string analysis (length, empty counts), and date range information. | Michael S. Molina | <a href="/img/extensions/result-stats.png" target="_blank"><img src="/img/extensions/result-stats.png" alt="Result Stats" width="120" /></a> |
|
||||
| [SQL Snippets](https://github.com/michael-s-molina/superset-extensions/tree/main/sql_snippets) | A SQL Lab extension that provides reusable SQL code snippets, enabling quick insertion of commonly used code blocks such as license headers, author information, and frequently used SQL patterns. | Michael S. Molina | <a href="/img/extensions/sql-snippets.png" target="_blank"><img src="/img/extensions/sql-snippets.png" alt="SQL Snippets" width="120" /></a> |
|
||||
| [SQL Lab Query Estimator](https://github.com/michael-s-molina/superset-extensions/tree/main/query_estimator) | A SQL Lab panel that analyzes query execution plans to estimate resource impact, detect performance issues like Cartesian products and high-cost operations, and visualize the query plan tree. | Michael S. Molina | <a href="/img/extensions/query-estimator.png" target="_blank"><img src="/img/extensions/query-estimator.png" alt="Query Estimator" width="120" /></a> |
|
||||
| [Editors Bundle](https://github.com/michael-s-molina/superset-extensions/tree/main/editors_bundle) | A Superset extension that demonstrates how to provide custom code editors for different languages. This extension showcases the editor contribution system by registering alternative editors that can replace Superset's default Ace editor. | Michael S. Molina | <a href="/img/extensions/editors-bundle.png" target="_blank"><img src="/img/extensions/editors-bundle.png" alt="Editors Bundle" width="120" /></a> |
|
||||
|
||||
## How to Add Your Extension
|
||||
|
||||
To add your extension to this registry, submit a pull request to the [Apache Superset repository](https://github.com/apache/superset) with the following changes:
|
||||
|
||||
1. Add a row to the **Extensions** table above using this format:
|
||||
|
||||
```markdown
|
||||
| [Your Extension](https://github.com/your-username/your-repo) | A brief description of your extension. | Your Name | <a href="/img/extensions/your-screenshot.png" target="_blank"><img src="/img/extensions/your-screenshot.png" alt="Your Extension" width="120" /></a> |
|
||||
```
|
||||
|
||||
2. Add a screenshot to `docs/static/img/extensions/` (recommended size: 800x450px, PNG or JPG format)
|
||||
|
||||
3. Submit your PR with a title like "docs: Add [Extension Name] to community extensions registry"
|
||||
35
docs/developer_docs/extensions/security.md
Normal file
35
docs/developer_docs/extensions/security.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Security
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Security
|
||||
|
||||
By default, extensions are disabled and must be explicitly enabled by setting the `ENABLE_EXTENSIONS` feature flag. Built-in extensions are included as part of the Superset codebase and are held to the same security standards and review processes as the rest of the application.
|
||||
|
||||
For external extensions, administrators are responsible for evaluating and verifying the security of any extensions they choose to install, just as they would when installing third-party NPM or PyPI packages. At this stage, all extensions run in the same context as the host application, without additional sandboxing. This means that external extensions can impact the security and performance of a Superset environment in the same way as any other installed dependency.
|
||||
|
||||
We plan to introduce an optional sandboxed execution model for extensions in the future (as part of an additional SIP). Until then, administrators should exercise caution and follow best practices when selecting and deploying third-party extensions. A directory of community extensions is available in the [Community Extensions](./registry) page. Note that these extensions are not vetted by the Apache Superset project—administrators must evaluate each extension before installation.
|
||||
|
||||
**Any performance or security vulnerabilities introduced by external extensions should be reported directly to the extension author, not as Superset vulnerabilities.**
|
||||
|
||||
Any security concerns regarding built-in extensions (included in Superset's monorepo) should be reported to the Superset Security mailing list for triage and resolution by maintainers.
|
||||
440
docs/developer_docs/extensions/tasks.md
Normal file
440
docs/developer_docs/extensions/tasks.md
Normal file
@@ -0,0 +1,440 @@
|
||||
---
|
||||
title: Tasks
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Global Task Framework
|
||||
|
||||
The Global Task Framework (GTF) provides a unified way to manage background tasks. It handles task execution, progress tracking, cancellation, and deduplication for both synchronous and asynchronous execution. The framework uses distributed locking internally to ensure race-free operations—you don't need to worry about concurrent task creation or cancellation conflicts.
|
||||
|
||||
## Enabling GTF
|
||||
|
||||
GTF is disabled by default and must be enabled via the `GLOBAL_TASK_FRAMEWORK` feature flag in your `superset_config.py`:
|
||||
|
||||
```python
|
||||
FEATURE_FLAGS = {
|
||||
"GLOBAL_TASK_FRAMEWORK": True,
|
||||
}
|
||||
```
|
||||
|
||||
When GTF is disabled:
|
||||
- The Task List UI menu item is hidden
|
||||
- The `/api/v1/task/*` endpoints return 404
|
||||
- Calling or scheduling a `@task`-decorated function raises `GlobalTaskFrameworkDisabledError`
|
||||
|
||||
:::note Future Migration
|
||||
When GTF is considered stable, it will replace legacy Celery tasks for built-in features like thumbnails and alerts & reports. Enabling this flag prepares your deployment for that migration.
|
||||
:::
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Define a Task
|
||||
|
||||
```python
|
||||
from superset_core.api.tasks import task, get_context
|
||||
|
||||
@task
|
||||
def process_data(dataset_id: int) -> None:
|
||||
ctx = get_context()
|
||||
|
||||
@ctx.on_cleanup
|
||||
def cleanup():
|
||||
logger.info("Processing complete")
|
||||
|
||||
data = fetch_dataset(dataset_id)
|
||||
process_and_cache(data)
|
||||
```
|
||||
|
||||
### Execute a Task
|
||||
|
||||
```python
|
||||
# Async execution - schedules on Celery worker
|
||||
task = process_data.schedule(dataset_id=123)
|
||||
print(task.status) # "pending"
|
||||
|
||||
# Sync execution - runs inline in current process
|
||||
task = process_data(dataset_id=123)
|
||||
# ... blocks until complete
|
||||
print(task.status) # "success"
|
||||
```
|
||||
|
||||
### Async vs Sync Execution
|
||||
|
||||
| Method | When to Use |
|
||||
|--------|-------------|
|
||||
| `.schedule()` | Long-running operations, background processing, when you need to return immediately |
|
||||
| Direct call | Short operations, when deduplication matters, when you need the result before responding |
|
||||
|
||||
Both execution modes provide the same task features: deduplication, progress tracking, cancellation, and visibility in the Task List UI. The difference is whether execution happens in a Celery worker (async) or inline (sync).
|
||||
|
||||
## Task Lifecycle
|
||||
|
||||
```
|
||||
PENDING ──→ IN_PROGRESS ────→ SUCCESS
|
||||
│ │
|
||||
│ ├──────────→ FAILURE
|
||||
│ ↓ ↑
|
||||
│ ABORTING ────────────┘
|
||||
│ │
|
||||
│ ├──────────→ TIMED_OUT (timeout)
|
||||
│ │
|
||||
└─────────────┴──────────→ ABORTED (user cancel)
|
||||
```
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| `PENDING` | Queued, awaiting execution |
|
||||
| `IN_PROGRESS` | Executing |
|
||||
| `ABORTING` | Abort/timeout triggered, abort handlers running |
|
||||
| `SUCCESS` | Completed successfully |
|
||||
| `FAILURE` | Failed with error or abort/cleanup handler exception |
|
||||
| `ABORTED` | Cancelled by user/admin |
|
||||
| `TIMED_OUT` | Exceeded configured timeout |
|
||||
|
||||
## Context API
|
||||
|
||||
Access task context via `get_context()` from within any `@task` function. The context provides methods for updating task metadata and registering handlers.
|
||||
|
||||
### Updating Task Metadata
|
||||
|
||||
Use `update_task()` to report progress and store custom payload data:
|
||||
|
||||
```python
|
||||
@task
|
||||
def my_task(items: list[int]) -> None:
|
||||
ctx = get_context()
|
||||
|
||||
for i, item in enumerate(items):
|
||||
result = process(item)
|
||||
ctx.update_task(
|
||||
progress=(i + 1, len(items)),
|
||||
payload={"last_result": result}
|
||||
)
|
||||
```
|
||||
|
||||
:::tip
|
||||
Call `update_task()` once per iteration for best performance. Frequent DB writes are throttled to limit metastore load, so batching progress and payload updates together in a single call ensures both are persisted at the same time.
|
||||
:::
|
||||
|
||||
#### Progress Formats
|
||||
|
||||
The `progress` parameter accepts three formats:
|
||||
|
||||
| Format | Example | Display |
|
||||
|--------|---------|---------|
|
||||
| `tuple[int, int]` | `progress=(3, 100)` | 3 of 100 (3%) with ETA |
|
||||
| `float` (0.0-1.0) | `progress=0.5` | 50% with ETA |
|
||||
| `int` | `progress=42` | 42 processed |
|
||||
|
||||
:::tip
|
||||
Use the tuple format `(current, total)` whenever possible. It provides the richest information to users: showing both the count and percentage, while still computing ETA automatically.
|
||||
:::
|
||||
|
||||
#### Payload
|
||||
|
||||
The `payload` parameter stores custom metadata that can help users understand what the task is doing. Each call to `update_task()` replaces the previous payload completely.
|
||||
|
||||
In the Task List UI, when a payload is defined, an info icon appears in the **Details** column. Users can hover over it to see the JSON content.
|
||||
|
||||
### Handlers
|
||||
|
||||
Register handlers to run cleanup logic or respond to abort requests:
|
||||
|
||||
| Handler | When it runs | Use case |
|
||||
|---------|--------------|----------|
|
||||
| `on_cleanup` | Always (success, failure, abort) | Release resources, close connections |
|
||||
| `on_abort` | When task is aborted | Set stop flag, cancel external operations |
|
||||
|
||||
```python
|
||||
@task
|
||||
def my_task() -> None:
|
||||
ctx = get_context()
|
||||
|
||||
@ctx.on_cleanup
|
||||
def cleanup():
|
||||
logger.info("Task ended, cleaning up")
|
||||
|
||||
@ctx.on_abort
|
||||
def handle_abort():
|
||||
logger.info("Abort requested")
|
||||
|
||||
# ... task logic
|
||||
```
|
||||
|
||||
Multiple handlers of the same type execute in LIFO order (last registered runs first). Abort handlers run first when abort is detected, then cleanup handlers run when the task ends.
|
||||
|
||||
#### Best-Effort Execution
|
||||
|
||||
**All registered handlers will always be attempted, even if one fails.** This ensures that a failure in one handler doesn't prevent other handlers from running their cleanup logic.
|
||||
|
||||
For example, if you have three cleanup handlers and the second one throws an exception:
|
||||
1. Handler 3 runs ✓
|
||||
2. Handler 2 throws an exception ✗ (logged, but execution continues)
|
||||
3. Handler 1 runs ✓
|
||||
|
||||
If any handler fails, the task is marked as `FAILURE` with combined error details showing all handler failures.
|
||||
|
||||
:::tip
|
||||
Write handlers to be independent and self-contained. Don't assume previous handlers succeeded, and don't rely on shared state between handlers.
|
||||
:::
|
||||
|
||||
## Making Tasks Abortable
|
||||
|
||||
When users click **Cancel** in the Task List, the system decides whether to **abort** (stop) the task or **unsubscribe** (remove the user from a shared task). Abort occurs when:
|
||||
- It's a private or system task
|
||||
- It's a shared task and the user is the last subscriber
|
||||
- An admin checks **Force abort** to stop the task for all subscribers
|
||||
|
||||
Pending tasks can always be aborted: they simply won't start. In-progress tasks require an abort handler to be abortable:
|
||||
|
||||
```python
|
||||
@task
|
||||
def abortable_task(items: list[str]) -> None:
|
||||
ctx = get_context()
|
||||
should_stop = False
|
||||
|
||||
@ctx.on_abort
|
||||
def handle_abort():
|
||||
nonlocal should_stop
|
||||
should_stop = True
|
||||
logger.info("Abort signal received")
|
||||
|
||||
@ctx.on_cleanup
|
||||
def cleanup():
|
||||
logger.info("Task ended, cleaning up")
|
||||
|
||||
for item in items:
|
||||
if should_stop:
|
||||
return # Exit gracefully
|
||||
process(item)
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Registering `on_abort` marks the task as abortable and starts the abort listener
|
||||
- The abort handler fires automatically when abort is triggered
|
||||
- Use a flag pattern to gracefully stop processing at safe points
|
||||
- Without an abort handler, in-progress tasks cannot be aborted: the Cancel button in the Task List UI will be disabled
|
||||
|
||||
The framework automatically skips execution if a task was aborted while pending: no manual check needed at task start.
|
||||
|
||||
:::tip
|
||||
Always implement an abort handler for long-running tasks. This allows users to cancel unneeded tasks and free up worker capacity for other operations.
|
||||
:::
|
||||
|
||||
## Timeouts
|
||||
|
||||
Set a timeout to automatically abort tasks that run too long:
|
||||
|
||||
```python
|
||||
from superset_core.api.tasks import task, get_context, TaskOptions
|
||||
|
||||
# Set default timeout in decorator
|
||||
@task(timeout=300) # 5 minutes
|
||||
def process_data(dataset_id: int) -> None:
|
||||
ctx = get_context()
|
||||
should_stop = False
|
||||
|
||||
@ctx.on_abort
|
||||
def handle_abort():
|
||||
nonlocal should_stop
|
||||
should_stop = True
|
||||
|
||||
for chunk in fetch_large_dataset(dataset_id):
|
||||
if should_stop:
|
||||
return
|
||||
process(chunk)
|
||||
|
||||
# Override timeout at call time
|
||||
task = process_data.schedule(
|
||||
dataset_id=123,
|
||||
options=TaskOptions(timeout=600) # Override to 10 minutes
|
||||
)
|
||||
```
|
||||
|
||||
### How Timeouts Work
|
||||
|
||||
The timeout timer starts when the task begins executing (status changes to `IN_PROGRESS`). When the timeout expires:
|
||||
|
||||
1. **With an abort handler registered:** The task transitions to `ABORTING`, abort handlers run, then cleanup handlers run. The final status depends on handler execution:
|
||||
- If handlers complete successfully → `TIMED_OUT` status
|
||||
- If handlers throw an exception → `FAILURE` status
|
||||
|
||||
2. **Without an abort handler:** The framework cannot forcibly terminate the task. A warning is logged, and the task continues running. The Task List UI shows a warning indicator (⚠️) in the Details column to alert users that the timeout cannot be enforced.
|
||||
|
||||
### Timeout Precedence
|
||||
|
||||
| Source | Priority | Example |
|
||||
|--------|----------|---------|
|
||||
| `TaskOptions.timeout` | Highest | `options=TaskOptions(timeout=600)` |
|
||||
| `@task(timeout=...)` | Default | `@task(timeout=300)` |
|
||||
| Not set | No timeout | Task runs indefinitely |
|
||||
|
||||
Call-time options always override decorator defaults, allowing tasks to have sensible defaults while permitting callers to extend or shorten the timeout for specific use cases.
|
||||
|
||||
:::warning
|
||||
Timeouts require an abort handler to be effective. Without one, the timeout triggers only a warning and the task continues running. Always implement an abort handler when using timeouts.
|
||||
:::
|
||||
|
||||
## Deduplication
|
||||
|
||||
Use `task_key` to prevent duplicate task execution:
|
||||
|
||||
```python
|
||||
from superset_core.api.tasks import TaskOptions
|
||||
|
||||
# Without key - creates new task each time (random UUID)
|
||||
task1 = my_task.schedule(x=1)
|
||||
task2 = my_task.schedule(x=1) # Different task
|
||||
|
||||
# With key - joins existing task if active
|
||||
task1 = my_task.schedule(x=1, options=TaskOptions(task_key="report_123"))
|
||||
task2 = my_task.schedule(x=1, options=TaskOptions(task_key="report_123")) # Returns same task
|
||||
```
|
||||
|
||||
When a task with matching key already exists, the user is added as a subscriber and the existing task is returned. This behavior is consistent across all scopes—private tasks naturally have only one subscriber since their deduplication key includes the user ID.
|
||||
|
||||
Deduplication only applies to active tasks (pending/in-progress). Once a task completes, a new task with the same key can be created.
|
||||
|
||||
### Sync Join-and-Wait
|
||||
|
||||
When a sync call joins an existing task, it blocks until the task completes:
|
||||
|
||||
```python
|
||||
# Schedule async task
|
||||
task = my_task.schedule(options=TaskOptions(task_key="report_123"))
|
||||
|
||||
# Later sync call with same key blocks until completion of the active task
|
||||
task2 = my_task(options=TaskOptions(task_key="report_123"))
|
||||
assert task.uuid == task2.uuid # True
|
||||
print(task2.status) # "success" (terminal status)
|
||||
```
|
||||
|
||||
## Task Scopes
|
||||
|
||||
```python
|
||||
from superset_core.api.tasks import task, TaskScope
|
||||
|
||||
@task # Private by default
|
||||
def private_task(): ...
|
||||
|
||||
@task(scope=TaskScope.SHARED) # Multiple users can subscribe
|
||||
def shared_task(): ...
|
||||
|
||||
@task(scope=TaskScope.SYSTEM) # Admin-only visibility
|
||||
def system_task(): ...
|
||||
```
|
||||
|
||||
| Scope | Visibility | Cancel Behavior |
|
||||
|-------|------------|-----------------|
|
||||
| `PRIVATE` | Creator only | Cancels immediately |
|
||||
| `SHARED` | All subscribers | Last subscriber cancels; others unsubscribe |
|
||||
| `SYSTEM` | Admins only | Admin cancels |
|
||||
|
||||
## Task Cleanup
|
||||
|
||||
Completed tasks accumulate in the database over time. Configure a scheduled prune job to automatically remove old tasks:
|
||||
|
||||
```python
|
||||
# In your superset_config.py, add to your Celery beat schedule:
|
||||
CELERY_CONFIG.beat_schedule["prune_tasks"] = {
|
||||
"task": "prune_tasks",
|
||||
"schedule": crontab(minute=0, hour=0), # Run daily at midnight
|
||||
"kwargs": {
|
||||
"retention_period_days": 90, # Keep tasks for 90 days
|
||||
"max_rows_per_run": 10000, # Limit deletions per run
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The prune job only removes tasks in terminal states (`SUCCESS`, `FAILURE`, `ABORTED`, `TIMED_OUT`). Active tasks (`PENDING`, `IN_PROGRESS`, `ABORTING`) are never pruned.
|
||||
|
||||
See `superset/config.py` for a complete example configuration.
|
||||
|
||||
:::tip Signal Cache for Faster Notifications
|
||||
By default, abort detection and sync join-and-wait use database polling. Configure `SIGNAL_CACHE_CONFIG` to enable Redis pub/sub for real-time notifications. See [Signal Cache Backend](/admin-docs/configuration/cache#signal-cache-backend) for configuration details.
|
||||
:::
|
||||
|
||||
## API Reference
|
||||
|
||||
### @task Decorator
|
||||
|
||||
```python
|
||||
@task(
|
||||
name: str | None = None,
|
||||
scope: TaskScope = TaskScope.PRIVATE,
|
||||
timeout: int | None = None
|
||||
)
|
||||
```
|
||||
|
||||
- `name`: Task identifier (defaults to function name)
|
||||
- `scope`: `PRIVATE`, `SHARED`, or `SYSTEM`
|
||||
- `timeout`: Default timeout in seconds (can be overridden via `TaskOptions`)
|
||||
|
||||
### TaskContext Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `update_task(progress, payload)` | Update progress and/or custom payload |
|
||||
| `on_cleanup(handler)` | Register cleanup handler |
|
||||
| `on_abort(handler)` | Register abort handler (makes task abortable) |
|
||||
|
||||
### TaskOptions
|
||||
|
||||
```python
|
||||
TaskOptions(
|
||||
task_key: str | None = None,
|
||||
task_name: str | None = None,
|
||||
timeout: int | None = None
|
||||
)
|
||||
```
|
||||
|
||||
- `task_key`: Deduplication key (also used as display name if `task_name` is not set)
|
||||
- `task_name`: Human-readable display name for the Task List UI
|
||||
- `timeout`: Timeout in seconds (overrides decorator default)
|
||||
|
||||
:::tip
|
||||
Provide a descriptive `task_name` for better readability in the Task List UI. While `task_key` is used for deduplication and may be technical (e.g., `chart_export_123`), `task_name` can be user-friendly (e.g., `"Export Sales Chart 123"`).
|
||||
:::
|
||||
|
||||
## Error Handling
|
||||
|
||||
Let exceptions propagate: the framework captures them automatically and sets task status to `FAILURE`:
|
||||
|
||||
```python
|
||||
@task
|
||||
def risky_task() -> None:
|
||||
# No try/catch needed - framework handles it
|
||||
result = operation_that_might_fail()
|
||||
```
|
||||
|
||||
On failure, the framework records:
|
||||
- `error_message`: Exception message
|
||||
- `exception_type`: Exception class name
|
||||
- `stack_trace`: Full traceback (visible when `SHOW_STACKTRACE=True`)
|
||||
|
||||
In the Task List UI, failed tasks show error details when hovering over the status. When stack traces are enabled, a separate bug icon appears in the **Details** column for viewing the full traceback.
|
||||
|
||||
Cleanup handlers still run after an exception, so resources can be properly released as necessary.
|
||||
|
||||
:::tip
|
||||
Use descriptive exception messages. In environments where stack traces are hidden (`SHOW_STACKTRACE=False`), users see only the error message and exception type when hovering over failed tasks. Clear messages help users troubleshoot issues without administrator assistance.
|
||||
:::
|
||||
318
docs/developer_docs/guidelines/backend-style-guidelines.md
Normal file
318
docs/developer_docs/guidelines/backend-style-guidelines.md
Normal file
@@ -0,0 +1,318 @@
|
||||
---
|
||||
title: Overview
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Backend Style Guidelines
|
||||
|
||||
This is a list of statements that describe how we do backend development in Superset. While they might not be 100% true for all files in the repo, they represent the gold standard we strive towards for backend quality and style.
|
||||
|
||||
- We use a monolithic Python/Flask/Flask-AppBuilder backend, with small single-responsibility satellite services where necessary.
|
||||
- Files are generally organized by feature or object type. Within each domain, we can have api controllers, models, schemas, commands, and data access objects (DAO).
|
||||
- See: [Proposal for Improving Superset's Python Code Organization](https://github.com/apache/superset/issues/9077)
|
||||
- API controllers use Marshmallow Schemas to serialize/deserialize data.
|
||||
- Authentication and authorization are controlled by the [security manager](https://github.com/apache/superset/blob/master/superset/security/manager).
|
||||
- We use Pytest for unit and integration tests. These live in the `tests` directory.
|
||||
- We add tests for every new piece of functionality added to the backend.
|
||||
- We use pytest fixtures to share setup between tests.
|
||||
- We use SQLAlchemy to access both Superset's application database, and users' analytics databases.
|
||||
- We make changes backwards compatible whenever possible.
|
||||
- If a change cannot be made backwards compatible, it goes into a major release.
|
||||
- See: [Proposal For Semantic Versioning](https://github.com/apache/superset/issues/12566)
|
||||
- We use Swagger for API documentation, with docs written inline on the API endpoint code.
|
||||
- We prefer thin ORM models, putting shared functionality in other utilities.
|
||||
- Several linters/checkers are used to maintain consistent code style and type safety: pylint, mypy, black, isort.
|
||||
- `__init__.py` files are kept empty to avoid implicit dependencies.
|
||||
|
||||
## Code Organization
|
||||
|
||||
### Domain-Driven Structure
|
||||
|
||||
Organize code by business domain rather than technical layers:
|
||||
|
||||
```
|
||||
superset/
|
||||
├── dashboards/
|
||||
│ ├── api.py # API endpoints
|
||||
│ ├── commands/ # Business logic
|
||||
│ ├── dao.py # Data access layer
|
||||
│ ├── models.py # Database models
|
||||
│ └── schemas.py # Serialization schemas
|
||||
├── charts/
|
||||
│ ├── api.py
|
||||
│ ├── commands/
|
||||
│ ├── dao.py
|
||||
│ ├── models.py
|
||||
│ └── schemas.py
|
||||
```
|
||||
|
||||
### API Controllers
|
||||
|
||||
Use Flask-AppBuilder with Marshmallow schemas:
|
||||
|
||||
```python
|
||||
from flask_appbuilder.api import BaseApi
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
class DashboardSchema(Schema):
|
||||
id = fields.Integer()
|
||||
dashboard_title = fields.String(required=True)
|
||||
created_on = fields.DateTime(dump_only=True)
|
||||
|
||||
class DashboardApi(BaseApi):
|
||||
resource_name = "dashboard"
|
||||
openapi_spec_tag = "Dashboards"
|
||||
|
||||
@expose("/", methods=["GET"])
|
||||
@protect()
|
||||
@safe
|
||||
def get_list(self) -> Response:
|
||||
"""Get a list of dashboards"""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
### Commands Pattern
|
||||
|
||||
Use commands for business logic:
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
from superset.commands.base import BaseCommand
|
||||
from superset.dashboards.dao import DashboardDAO
|
||||
|
||||
class CreateDashboardCommand(BaseCommand):
|
||||
def __init__(self, properties: Dict[str, Any]):
|
||||
self._properties = properties
|
||||
|
||||
def run(self) -> Dashboard:
|
||||
self.validate()
|
||||
return DashboardDAO.create(self._properties)
|
||||
|
||||
def validate(self) -> None:
|
||||
if not self._properties.get("dashboard_title"):
|
||||
raise ValidationError("Title is required")
|
||||
```
|
||||
|
||||
### Data Access Objects (DAOs)
|
||||
|
||||
See: [DAO Style Guidelines and Best Practices](./backend/dao-style-guidelines)
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
from superset.dashboards.commands.create import CreateDashboardCommand
|
||||
|
||||
def test_create_dashboard_success():
|
||||
properties = {
|
||||
"dashboard_title": "Test Dashboard",
|
||||
"owners": [1]
|
||||
}
|
||||
|
||||
command = CreateDashboardCommand(properties)
|
||||
dashboard = command.run()
|
||||
|
||||
assert dashboard.dashboard_title == "Test Dashboard"
|
||||
|
||||
def test_create_dashboard_validation_error():
|
||||
properties = {} # Missing required title
|
||||
|
||||
command = CreateDashboardCommand(properties)
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
command.run()
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from superset.app import create_app
|
||||
from superset.extensions import db
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
app = create_app()
|
||||
app.config["TESTING"] = True
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
yield app
|
||||
db.drop_all()
|
||||
|
||||
def test_dashboard_api_create(app, auth_headers):
|
||||
with app.test_client() as client:
|
||||
response = client.post(
|
||||
"/api/v1/dashboard/",
|
||||
json={"dashboard_title": "Test Dashboard"},
|
||||
headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 201
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
### Authorization Decorators
|
||||
|
||||
```python
|
||||
from flask_appbuilder.security.decorators import has_access
|
||||
|
||||
class DashboardApi(BaseApi):
|
||||
@expose("/", methods=["POST"])
|
||||
@protect()
|
||||
@has_access("can_write", "Dashboard")
|
||||
def post(self) -> Response:
|
||||
"""Create a new dashboard"""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
### Input Validation
|
||||
|
||||
```python
|
||||
from marshmallow import Schema, fields, validate
|
||||
|
||||
class DashboardSchema(Schema):
|
||||
dashboard_title = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(min=1, max=500)
|
||||
)
|
||||
slug = fields.String(
|
||||
validate=validate.Regexp(r'^[a-z0-9-]+$')
|
||||
)
|
||||
```
|
||||
|
||||
## Database Operations
|
||||
|
||||
### Use SQLAlchemy ORM
|
||||
|
||||
```python
|
||||
from sqlalchemy import Column, Integer, String, Text
|
||||
from superset.models.helpers import AuditMixinNullable
|
||||
|
||||
class Dashboard(Model, AuditMixinNullable):
|
||||
__tablename__ = "dashboards"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
dashboard_title = Column(String(500))
|
||||
position_json = Column(Text)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Dashboard {self.dashboard_title}>"
|
||||
```
|
||||
|
||||
### Database Migrations
|
||||
|
||||
```python
|
||||
# migration file
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
"dashboards",
|
||||
sa.Column("new_column", sa.String(255), nullable=True)
|
||||
)
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("dashboards", "new_column")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Custom Exceptions
|
||||
|
||||
```python
|
||||
class SupersetException(Exception):
|
||||
"""Base exception for Superset"""
|
||||
pass
|
||||
|
||||
class ValidationError(SupersetException):
|
||||
"""Raised when validation fails"""
|
||||
pass
|
||||
|
||||
class SecurityException(SupersetException):
|
||||
"""Raised when security check fails"""
|
||||
pass
|
||||
```
|
||||
|
||||
### Error Responses
|
||||
|
||||
```python
|
||||
from flask import jsonify
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
@app.errorhandler(ValidationError)
|
||||
def handle_validation_error(error):
|
||||
return jsonify({
|
||||
"message": str(error),
|
||||
"error_type": "VALIDATION_ERROR"
|
||||
}), 400
|
||||
```
|
||||
|
||||
## Type Hints and Documentation
|
||||
|
||||
### Use Type Hints
|
||||
|
||||
```python
|
||||
from typing import List, Optional, Dict, Any
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
def get_dashboards_by_owner(owner_id: int) -> List[Dashboard]:
|
||||
"""Get all dashboards owned by a specific user"""
|
||||
return db.session.query(Dashboard).filter_by(owner_id=owner_id).all()
|
||||
|
||||
def create_dashboard(properties: Dict[str, Any]) -> Optional[Dashboard]:
|
||||
"""Create a new dashboard with the given properties"""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
### API Documentation
|
||||
|
||||
```python
|
||||
from flask_appbuilder.api import BaseApi
|
||||
from flask_appbuilder.api.schemas import get_list_schema
|
||||
|
||||
class DashboardApi(BaseApi):
|
||||
@expose("/", methods=["GET"])
|
||||
@protect()
|
||||
@safe
|
||||
def get_list(self) -> Response:
|
||||
"""Get a list of dashboards
|
||||
---
|
||||
get:
|
||||
description: >-
|
||||
Get a list of dashboards
|
||||
parameters:
|
||||
- in: query
|
||||
schema:
|
||||
type: integer
|
||||
name: page_size
|
||||
description: Number of results per page
|
||||
responses:
|
||||
200:
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DashboardListResponse'
|
||||
"""
|
||||
# Implementation
|
||||
```
|
||||
375
docs/developer_docs/guidelines/backend/dao-style-guidelines.md
Normal file
375
docs/developer_docs/guidelines/backend/dao-style-guidelines.md
Normal file
@@ -0,0 +1,375 @@
|
||||
---
|
||||
title: DAO Style Guidelines and Best Practices
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# DAO Style Guidelines and Best Practices
|
||||
|
||||
A Data Access Object (DAO) is a pattern that provides an abstract interface to the SQLAlchemy Object Relational Mapper (ORM). The DAOs are critical as they form the building block of the application which are wrapped by the associated commands and RESTful API endpoints.
|
||||
|
||||
There are numerous inconsistencies and violations of the DRY principal within the codebase as it relates to DAOs and ORMs—unnecessary commits, non-ACID transactions, etc.—which makes the code unnecessarily complex and convoluted. Addressing the underlying issues with the DAOs _should_ help simplify the downstream operations and improve the developer experience.
|
||||
|
||||
To ensure consistency the following rules should be adhered to:
|
||||
|
||||
## Core Rules
|
||||
|
||||
1. **All database operations (including testing) should be defined within a DAO**, i.e., there should not be any explicit `db.session.add`, `db.session.merge`, etc. calls outside of a DAO.
|
||||
|
||||
2. **A DAO should use `create`, `update`, `delete`, `upsert` terms**—typical database operations which ensure consistency with commands—rather than action based terms like `save`, `saveas`, `override`, etc.
|
||||
|
||||
3. **Sessions should be managed via a [context manager](https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#begin-once)** which auto-commits on success and rolls back on failure, i.e., there should be no explicit `db.session.commit` or `db.session.rollback` calls within the DAO.
|
||||
|
||||
4. **There should be a single atomic transaction representing the entirety of the operation**, i.e., when creating a dataset with associated columns and metrics either all the changes succeed when the transaction is committed, or all the changes are undone when the transaction is rolled back. SQLAlchemy supports [nested transactions](https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#nested-transaction) via the `begin_nested` method which can be nested—inline with how DAOs are invoked.
|
||||
|
||||
5. **The database layer should adopt a "shift left" mentality** i.e., uniqueness/foreign key constraints, relationships, cascades, etc. should all be defined in the database layer rather than being enforced in the application layer.
|
||||
|
||||
6. **Exception-based validation**: Ask for forgiveness rather than permission. Try to perform the operation and rely on database constraints to verify that the model is acceptable, rather than pre-validating conditions.
|
||||
|
||||
7. **Bulk operations**: Provide bulk `create`, `update`, and `delete` methods where applicable for performance optimization.
|
||||
|
||||
8. **Sparse updates**: Updates should only modify explicitly defined attributes.
|
||||
|
||||
9. **Test transactions**: Tests should leverage nested transactions which should be rolled back on teardown, rather than deleting objects.
|
||||
|
||||
## DAO Implementation Examples
|
||||
|
||||
### Basic DAO Structure
|
||||
|
||||
```python
|
||||
from typing import List, Optional, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from superset.extensions import db
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
class DashboardDAO:
|
||||
"""Data Access Object for Dashboard operations"""
|
||||
|
||||
@classmethod
|
||||
def find_by_id(cls, dashboard_id: int) -> Optional[Dashboard]:
|
||||
"""Find dashboard by ID"""
|
||||
return db.session.query(Dashboard).filter_by(id=dashboard_id).first()
|
||||
|
||||
@classmethod
|
||||
def find_by_ids(cls, dashboard_ids: List[int]) -> List[Dashboard]:
|
||||
"""Find dashboards by list of IDs"""
|
||||
return db.session.query(Dashboard).filter(
|
||||
Dashboard.id.in_(dashboard_ids)
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
def create(cls, properties: Dict[str, Any]) -> Dashboard:
|
||||
"""Create a new dashboard"""
|
||||
with db.session.begin():
|
||||
dashboard = Dashboard(**properties)
|
||||
db.session.add(dashboard)
|
||||
db.session.flush() # Get the ID
|
||||
return dashboard
|
||||
|
||||
@classmethod
|
||||
def update(cls, dashboard: Dashboard, properties: Dict[str, Any]) -> Dashboard:
|
||||
"""Update an existing dashboard"""
|
||||
with db.session.begin():
|
||||
for key, value in properties.items():
|
||||
setattr(dashboard, key, value)
|
||||
return dashboard
|
||||
|
||||
@classmethod
|
||||
def delete(cls, dashboard: Dashboard) -> None:
|
||||
"""Delete a dashboard"""
|
||||
with db.session.begin():
|
||||
db.session.delete(dashboard)
|
||||
```
|
||||
|
||||
### Complex DAO Operations
|
||||
|
||||
```python
|
||||
class DatasetDAO:
|
||||
"""Data Access Object for Dataset operations"""
|
||||
|
||||
@classmethod
|
||||
def create_with_columns_and_metrics(
|
||||
cls,
|
||||
dataset_properties: Dict[str, Any],
|
||||
columns: List[Dict[str, Any]],
|
||||
metrics: List[Dict[str, Any]]
|
||||
) -> Dataset:
|
||||
"""Create dataset with associated columns and metrics atomically"""
|
||||
with db.session.begin():
|
||||
# Create the dataset
|
||||
dataset = Dataset(**dataset_properties)
|
||||
db.session.add(dataset)
|
||||
db.session.flush() # Get the dataset ID
|
||||
|
||||
# Create columns
|
||||
for column_props in columns:
|
||||
column_props['dataset_id'] = dataset.id
|
||||
column = TableColumn(**column_props)
|
||||
db.session.add(column)
|
||||
|
||||
# Create metrics
|
||||
for metric_props in metrics:
|
||||
metric_props['dataset_id'] = dataset.id
|
||||
metric = SqlMetric(**metric_props)
|
||||
db.session.add(metric)
|
||||
|
||||
return dataset
|
||||
|
||||
@classmethod
|
||||
def bulk_delete(cls, dataset_ids: List[int]) -> int:
|
||||
"""Delete multiple datasets and return count"""
|
||||
with db.session.begin():
|
||||
count = db.session.query(Dataset).filter(
|
||||
Dataset.id.in_(dataset_ids)
|
||||
).delete(synchronize_session=False)
|
||||
return count
|
||||
```
|
||||
|
||||
### Query Methods
|
||||
|
||||
```python
|
||||
class DashboardDAO:
|
||||
@classmethod
|
||||
def find_by_slug(cls, slug: str) -> Optional[Dashboard]:
|
||||
"""Find dashboard by slug"""
|
||||
return db.session.query(Dashboard).filter_by(slug=slug).first()
|
||||
|
||||
@classmethod
|
||||
def find_by_owner(cls, owner_id: int) -> List[Dashboard]:
|
||||
"""Find all dashboards owned by a user"""
|
||||
return db.session.query(Dashboard).filter_by(
|
||||
created_by_fk=owner_id
|
||||
).all()
|
||||
|
||||
@classmethod
|
||||
def search(
|
||||
cls,
|
||||
query: str,
|
||||
page: int = 0,
|
||||
page_size: int = 25
|
||||
) -> Tuple[List[Dashboard], int]:
|
||||
"""Search dashboards with pagination"""
|
||||
base_query = db.session.query(Dashboard).filter(
|
||||
Dashboard.dashboard_title.ilike(f"%{query}%")
|
||||
)
|
||||
|
||||
total_count = base_query.count()
|
||||
dashboards = base_query.offset(page * page_size).limit(page_size).all()
|
||||
|
||||
return dashboards, total_count
|
||||
```
|
||||
|
||||
### Error Handling in DAOs
|
||||
|
||||
```python
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from superset.exceptions import DAOCreateFailedError, DAODeleteFailedError
|
||||
|
||||
class DashboardDAO:
|
||||
@classmethod
|
||||
def create(cls, properties: Dict[str, Any]) -> Dashboard:
|
||||
"""Create a new dashboard with error handling"""
|
||||
try:
|
||||
with db.session.begin():
|
||||
dashboard = Dashboard(**properties)
|
||||
db.session.add(dashboard)
|
||||
db.session.flush()
|
||||
return dashboard
|
||||
except IntegrityError as ex:
|
||||
raise DAOCreateFailedError(str(ex)) from ex
|
||||
|
||||
@classmethod
|
||||
def delete(cls, dashboard: Dashboard) -> None:
|
||||
"""Delete a dashboard with error handling"""
|
||||
try:
|
||||
with db.session.begin():
|
||||
db.session.delete(dashboard)
|
||||
except IntegrityError as ex:
|
||||
raise DAODeleteFailedError(
|
||||
f"Cannot delete dashboard {dashboard.id}: {str(ex)}"
|
||||
) from ex
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Class Methods
|
||||
|
||||
All DAO methods should be class methods (`@classmethod`) rather than instance methods:
|
||||
|
||||
```python
|
||||
# ✅ Good
|
||||
class DashboardDAO:
|
||||
@classmethod
|
||||
def find_by_id(cls, dashboard_id: int) -> Optional[Dashboard]:
|
||||
return db.session.query(Dashboard).filter_by(id=dashboard_id).first()
|
||||
|
||||
# ❌ Avoid
|
||||
class DashboardDAO:
|
||||
def find_by_id(self, dashboard_id: int) -> Optional[Dashboard]:
|
||||
return db.session.query(Dashboard).filter_by(id=dashboard_id).first()
|
||||
```
|
||||
|
||||
### 2. Use Context Managers for Transactions
|
||||
|
||||
Always use context managers to ensure proper transaction handling:
|
||||
|
||||
```python
|
||||
# ✅ Good - automatic commit/rollback
|
||||
@classmethod
|
||||
def create(cls, properties: Dict[str, Any]) -> Dashboard:
|
||||
with db.session.begin():
|
||||
dashboard = Dashboard(**properties)
|
||||
db.session.add(dashboard)
|
||||
return dashboard
|
||||
|
||||
# ❌ Avoid - manual commit/rollback
|
||||
@classmethod
|
||||
def create(cls, properties: Dict[str, Any]) -> Dashboard:
|
||||
try:
|
||||
dashboard = Dashboard(**properties)
|
||||
db.session.add(dashboard)
|
||||
db.session.commit()
|
||||
return dashboard
|
||||
except Exception:
|
||||
db.session.rollback()
|
||||
raise
|
||||
```
|
||||
|
||||
### 3. Use Descriptive Method Names
|
||||
|
||||
Method names should clearly indicate the operation:
|
||||
|
||||
```python
|
||||
# ✅ Good - clear CRUD operations
|
||||
create()
|
||||
update()
|
||||
delete()
|
||||
find_by_id()
|
||||
find_by_slug()
|
||||
|
||||
# ❌ Avoid - ambiguous names
|
||||
save()
|
||||
remove()
|
||||
get()
|
||||
```
|
||||
|
||||
### 4. Type Hints
|
||||
|
||||
Always include type hints for parameters and return values:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def find_by_ids(cls, dashboard_ids: List[int]) -> List[Dashboard]:
|
||||
"""Find dashboards by list of IDs"""
|
||||
return db.session.query(Dashboard).filter(
|
||||
Dashboard.id.in_(dashboard_ids)
|
||||
).all()
|
||||
```
|
||||
|
||||
### 5. Batch Operations
|
||||
|
||||
Provide efficient batch operations when needed:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def bulk_update_published_status(
|
||||
cls,
|
||||
dashboard_ids: List[int],
|
||||
published: bool
|
||||
) -> int:
|
||||
"""Update published status for multiple dashboards"""
|
||||
with db.session.begin():
|
||||
count = db.session.query(Dashboard).filter(
|
||||
Dashboard.id.in_(dashboard_ids)
|
||||
).update(
|
||||
{Dashboard.published: published},
|
||||
synchronize_session=False
|
||||
)
|
||||
return count
|
||||
```
|
||||
|
||||
## Testing DAOs
|
||||
|
||||
### Unit Tests for DAOs
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from superset.dashboards.dao import DashboardDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
|
||||
def test_dashboard_create(session):
|
||||
"""Test creating a dashboard"""
|
||||
properties = {
|
||||
"dashboard_title": "Test Dashboard",
|
||||
"slug": "test-dashboard"
|
||||
}
|
||||
|
||||
dashboard = DashboardDAO.create(properties)
|
||||
|
||||
assert dashboard.id is not None
|
||||
assert dashboard.dashboard_title == "Test Dashboard"
|
||||
assert dashboard.slug == "test-dashboard"
|
||||
|
||||
def test_dashboard_find_by_slug(session):
|
||||
"""Test finding dashboard by slug"""
|
||||
# Create test data
|
||||
dashboard = Dashboard(
|
||||
dashboard_title="Test Dashboard",
|
||||
slug="test-dashboard"
|
||||
)
|
||||
session.add(dashboard)
|
||||
session.commit()
|
||||
|
||||
# Test the DAO method
|
||||
found_dashboard = DashboardDAO.find_by_slug("test-dashboard")
|
||||
|
||||
assert found_dashboard is not None
|
||||
assert found_dashboard.dashboard_title == "Test Dashboard"
|
||||
|
||||
def test_dashboard_delete(session):
|
||||
"""Test deleting a dashboard"""
|
||||
dashboard = Dashboard(dashboard_title="Test Dashboard")
|
||||
session.add(dashboard)
|
||||
session.commit()
|
||||
dashboard_id = dashboard.id
|
||||
|
||||
DashboardDAO.delete(dashboard)
|
||||
|
||||
deleted_dashboard = DashboardDAO.find_by_id(dashboard_id)
|
||||
assert deleted_dashboard is None
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```python
|
||||
def test_create_dataset_with_columns_atomic(app_context):
|
||||
"""Test that creating dataset with columns is atomic"""
|
||||
dataset_properties = {"table_name": "test_table"}
|
||||
columns = [{"column_name": "col1"}, {"column_name": "col2"}]
|
||||
|
||||
# This should succeed completely or fail completely
|
||||
dataset = DatasetDAO.create_with_columns_and_metrics(
|
||||
dataset_properties, columns, []
|
||||
)
|
||||
|
||||
assert dataset.id is not None
|
||||
assert len(dataset.columns) == 2
|
||||
```
|
||||
235
docs/developer_docs/guidelines/design-guidelines.md
Normal file
235
docs/developer_docs/guidelines/design-guidelines.md
Normal file
@@ -0,0 +1,235 @@
|
||||
---
|
||||
title: Design Guidelines
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Design Guidelines
|
||||
|
||||
This is an area to host resources and documentation supporting the evolution and proper use of Superset design system elements. If content is to be added to this section or requires revisiting, a proposal should be submitted to the `dev@superset.apache.org` email list with either a text proposal or a link to a GitHub issue providing the markdown that will be added to this wiki. The Dev list will have the chance to review the proposal and arrive at lazy consensus. A committer may then copy/paste the markdown to this wiki, and make it public.
|
||||
|
||||
## Capitalization Guidelines
|
||||
|
||||
### Sentence case
|
||||
|
||||
Use sentence-case capitalization for everything in the UI (except these exceptions below).
|
||||
|
||||
Sentence case is predominantly lowercase. Capitalize only the initial character of the first word, and other words that require capitalization, like:
|
||||
|
||||
- **Proper nouns.** Objects in the product _are not_ considered proper nouns e.g. dashboards, charts, saved queries etc. Proprietary feature names eg. SQL Lab, Preset Manager _are_ considered proper nouns
|
||||
- **Acronyms** (e.g. CSS, HTML)
|
||||
- When referring to **UI labels that are themselves capitalized** from sentence case (e.g. page titles - Dashboards page, Charts page, Saved queries page, etc.)
|
||||
- User input that is reflected in the UI. E.g. a user-named a dashboard tab
|
||||
|
||||
**Sentence case vs. Title case:**
|
||||
- Title case: "A Dog Takes a Walk in Paris"
|
||||
- Sentence case: "A dog takes a walk in Paris"
|
||||
|
||||
**Why sentence case?**
|
||||
|
||||
- It's generally accepted as the quickest to read
|
||||
- It's the easiest form to distinguish between common and proper nouns
|
||||
|
||||
### How to refer to UI elements
|
||||
|
||||
When writing about a UI element, use the same capitalization as used in the UI.
|
||||
|
||||
For example, if an input field is labeled "Name" then you refer to this as the "Name input field". Similarly, if a button has the label "Save" in it, then it is correct to refer to the "Save button".
|
||||
|
||||
Where a product page is titled "Settings", you refer to this in writing as follows:
|
||||
"Edit your personal information on the Settings page".
|
||||
|
||||
Often a product page will have the same title as the objects it contains. In this case, refer to the page as it appears in the UI, and the objects as common nouns:
|
||||
|
||||
- Upload a dashboard on the Dashboards page
|
||||
- Go to Dashboards
|
||||
- View dashboard
|
||||
- View all dashboards
|
||||
- Upload CSS templates on the CSS templates page
|
||||
- Queries that you save will appear on the Saved queries page
|
||||
- Create custom queries in SQL Lab then create dashboards
|
||||
|
||||
### Exceptions to sentence case
|
||||
|
||||
1. **Acronyms and abbreviations.**
|
||||
Examples: URL, CSV, XML, CSS, SQL, SSH, URI, NaN, CRON, CC, BCC
|
||||
|
||||
2. **Proper nouns and brand names.**
|
||||
Examples: Apache, Superset, AntD JavaScript, GeoJSON, Slack, Google Sheets, SQLAlchemy
|
||||
|
||||
3. **Technical terms derived from proper nouns.**
|
||||
Examples: Jinja, Gaussian, European (as in European time zone)
|
||||
|
||||
4. **Key names.** Capitalize button labels and UI elements as they appear in the product UI.
|
||||
Examples: Shift (as in the keyboard button), Enter key
|
||||
|
||||
5. **Named queries or specific labeled items.**
|
||||
Examples: Query A, Query B
|
||||
|
||||
6. **Database names.** Always capitalize names of database engines and connectors.
|
||||
Examples: Presto, Trino, Drill, Hive, Google Sheets
|
||||
|
||||
## Button Design Guidelines
|
||||
|
||||
### Overview
|
||||
|
||||
**Button variants:**
|
||||
|
||||
1. Primary
|
||||
2. Secondary
|
||||
3. Tertiary
|
||||
4. Destructive
|
||||
|
||||
**Button styles:** Each button variant has three styles:
|
||||
|
||||
1. Text
|
||||
2. Icon+text
|
||||
3. Icon only
|
||||
|
||||
Primary buttons have a fourth style: dropdown.
|
||||
|
||||
**Usage:** Buttons communicate actions that users can take. Do not use for navigations, instead use links.
|
||||
|
||||
**Purpose:**
|
||||
|
||||
| Button Type | Description |
|
||||
|------------|-------------|
|
||||
| Primary | Main call to action, just 1 per page not including modals or main headers |
|
||||
| Secondary | Secondary actions, always in conjunction with a primary |
|
||||
| Tertiary | For less prominent actions; can be used in isolation or paired with a primary button |
|
||||
| Destructive | For actions that could have destructive effects on the user's data |
|
||||
|
||||
### Format
|
||||
|
||||
#### Anatomy
|
||||
|
||||
Button text is centered using the Label style. Icons appear left of text when combined. If no text label exists, an icon must indicate the button's function.
|
||||
|
||||
#### Button size
|
||||
|
||||
- Default dimensions: 160px width × 32px height
|
||||
- Text: 11px, Inter Medium, all caps
|
||||
- Corners: 4px border radius
|
||||
- Minimum padding: 8px around text
|
||||
- Width can decrease if space is limited, but maintain minimum padding
|
||||
|
||||
#### Button groups
|
||||
|
||||
- Group related buttons to establish visual hierarchy
|
||||
- Avoid overwhelming users with too many actions
|
||||
- Limit calls to action; use tertiary/ghost buttons for layouts with 3+ actions
|
||||
- Maintain consistent styles within groups when possible
|
||||
- Space buttons 8px apart vertically or horizontally
|
||||
|
||||
#### Content guidelines
|
||||
|
||||
Button labels should be clear and predictable. Use the "\{verb\} + \{noun\}" format, except for common actions like "Done," "Close," "Cancel," "Add," or "Delete." This formula provides necessary context and aids translation, though compact UIs or localization needs may warrant exceptions.
|
||||
|
||||
## Error Message Design Guidelines
|
||||
|
||||
### Definition
|
||||
|
||||
Interface errors appear when the application can't do what the user wants, typically because:
|
||||
|
||||
- The app technically fails to complete the request
|
||||
- The app can't understand the user input
|
||||
- The user tries to combine operations that can't work together
|
||||
|
||||
In all cases, encountering errors increases user friction and frustration while trying to use the application. Providing an error experience that helps the user understand what happened and their next steps is key to building user confidence and increasing engagement.
|
||||
|
||||
### General best practices
|
||||
|
||||
**The best error experience is no error at all.** Before implementing error patterns, consider what you might do in the interface before the user would encounter an error to prevent it from happening at all. This might look like:
|
||||
|
||||
- Providing tooltips or microcopy to help users understand how to interact with the interface
|
||||
- Disabling parts of the UI until desired conditions are met (e.g. disabling a Save button until mandatory fields in a form are completed)
|
||||
- Correcting an error automatically (e.g. autocorrecting spelling errors)
|
||||
|
||||
**Only report errors users care about.** The only errors that should appear in the interface are errors that require user acknowledgement and action (even if that action is "try again later" or "contact support").
|
||||
|
||||
**Do not start the user in an error state.** If user inputs are required to display an initial interface (e.g. a chart in Explore), use empty states or field highlighting to drive users to the required action.
|
||||
|
||||
### Patterns
|
||||
|
||||
#### Pattern selection
|
||||
|
||||
Select one pattern per error (e.g. do not implement an inline and banner pattern for the same error).
|
||||
|
||||
| When the error... | Use... |
|
||||
|------------------|--------|
|
||||
| Is directly related to a UI control | Inline error |
|
||||
| Is not directly related to a UI control | Banner error |
|
||||
|
||||
#### Inline
|
||||
|
||||
Inline errors are used when the source of the error is directly related to a UI control (text input, selector, etc.) such as the user not populating a required field or entering a number in a text field.
|
||||
|
||||
##### Anatomy
|
||||
|
||||
Use the `LabeledErrorBoundInput` component for this error pattern.
|
||||
|
||||
1. **Message** Provides direction on how to populate the input correctly.
|
||||
|
||||
##### Implementation details
|
||||
|
||||
- Where and when relevant, scroll the screen to the UI control with the error
|
||||
- When multiple inline errors are present, scroll to the topmost error
|
||||
|
||||
#### Banner
|
||||
|
||||
Banner errors are used when the source of the error is not directly related to a UI control (text input, selector, etc.) such as a technical failure or a loading problem.
|
||||
|
||||
##### Anatomy
|
||||
|
||||
Use the `ErrorAlert` component for this error pattern.
|
||||
|
||||
1. **Headline** (optional): 1-2 word summary of the error
|
||||
2. **Message**: What went wrong and what users should do next
|
||||
3. **Expand option** (optional): "See more"/"See less"
|
||||
4. **Details** (optional): Additional helpful context
|
||||
5. **Modal** (optional): For spatial constraints using `ToastType.DANGER`
|
||||
|
||||
##### Implementation details
|
||||
|
||||
- Place the banner near the content area most relevant to the error
|
||||
- For chart errors in Explore, use the chart area
|
||||
- For modal errors, use the modal footer
|
||||
- For app-wide errors, use the top of the screen
|
||||
|
||||
### Content guidelines
|
||||
|
||||
Effective error messages communicate:
|
||||
|
||||
1. What went wrong
|
||||
2. What users should do next
|
||||
|
||||
Error messages should be:
|
||||
|
||||
- Clear and accurate, leaving no room for misinterpretation
|
||||
- Short and concise
|
||||
- Understandable to non-technical users
|
||||
- Non-blaming and avoiding negative language
|
||||
|
||||
**Example:**
|
||||
|
||||
❌ "Cannot delete a datasource that has slices attached to it."
|
||||
|
||||
✅ "Please delete all charts using this dataset before deleting the dataset."
|
||||
51
docs/developer_docs/guidelines/frontend-style-guidelines.md
Normal file
51
docs/developer_docs/guidelines/frontend-style-guidelines.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: Overview
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Frontend Style Guidelines
|
||||
|
||||
This is a list of statements that describe how we do frontend development in Superset. While they might not be 100% true for all files in the repo, they represent the gold standard we strive towards for frontend quality and style.
|
||||
|
||||
- We develop using TypeScript.
|
||||
- See: [SIP-36](https://github.com/apache/superset/issues/9101)
|
||||
- We use React for building components, and Redux to manage app/global state.
|
||||
- See: [Component Style Guidelines and Best Practices](./frontend/component-style-guidelines)
|
||||
- We prefer functional components to class components and use hooks for local component state.
|
||||
- We use [Ant Design](https://ant.design/) components from our component library whenever possible, only building our own custom components when it's required.
|
||||
- See: [SIP-48](https://github.com/apache/superset/issues/11283)
|
||||
- We use [@emotion](https://emotion.sh/docs/introduction) to provide styling for our components, co-locating styling within component files.
|
||||
- See: [SIP-37](https://github.com/apache/superset/issues/9145)
|
||||
- See: [Emotion Styling Guidelines and Best Practices](./frontend/emotion-styling-guidelines)
|
||||
- We use Jest for unit tests, React Testing Library for component tests, and Cypress for end-to-end tests.
|
||||
- See: [SIP-56](https://github.com/apache/superset/issues/11830)
|
||||
- See: [Testing Guidelines and Best Practices](../testing/testing-guidelines)
|
||||
- We add tests for every new component or file added to the frontend.
|
||||
- We organize our repo so similar files live near each other, and tests are co-located with the files they test.
|
||||
- See: [SIP-61](https://github.com/apache/superset/issues/12098)
|
||||
- We prefer small, easily testable files and components.
|
||||
- We use OXC (oxlint) and Prettier to automatically fix lint errors and format the code.
|
||||
- We do not debate code formatting style in PRs, instead relying on automated tooling to enforce it.
|
||||
- If there's not a linting rule, we don't have a rule!
|
||||
- See: [Linting How-Tos](../contributing/howtos#typescript--javascript)
|
||||
- We use [React Storybook](https://storybook.js.org/) to help preview/test and stabilize our components
|
||||
- A public Storybook with components from the `master` branch is available [here](https://apache-superset.github.io/superset-ui/?path=/story/*)
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: Component Style Guidelines and Best Practices
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Component Style Guidelines and Best Practices
|
||||
|
||||
This documentation illustrates how we approach component development in Superset and provides examples to help you in writing new components or updating existing ones by following our community-approved standards.
|
||||
|
||||
This guide is intended primarily for reusable components. Whenever possible, all new components should be designed with reusability in mind.
|
||||
|
||||
## General Guidelines
|
||||
|
||||
- We use [Ant Design](https://ant.design/) as our component library. Do not build a new component if Ant Design provides one but rather instead extend or customize what the library provides
|
||||
- Always style your component using Emotion and always prefer the theme variables whenever applicable. See: [Emotion Styling Guidelines and Best Practices](./emotion-styling-guidelines)
|
||||
- All components should be made to be reusable whenever possible
|
||||
- All components should follow the structure and best practices as detailed below
|
||||
|
||||
### Directory and component structure
|
||||
|
||||
```
|
||||
superset-frontend/src/components
|
||||
{ComponentName}/
|
||||
index.tsx
|
||||
{ComponentName}.test.tsx
|
||||
{ComponentName}.stories.tsx
|
||||
```
|
||||
|
||||
**Components root directory:** Components that are meant to be re-used across different parts of the application should go in the `superset-frontend/src/components` directory. Components that are meant to be specific for a single part of the application should be located in the nearest directory where the component is used, for example, `superset-frontend/src/Explore/components`
|
||||
|
||||
**Exporting the component:** All components within the `superset-frontend/src/components` directory should be exported from `superset-frontend/src/components/index.ts` to facilitate their imports by other components
|
||||
|
||||
**Component directory name:** Use `PascalCase` for the component directory name
|
||||
|
||||
**Storybook:** Components should come with a storybook file whenever applicable, with the following naming convention `\{ComponentName\}.stories.tsx`. More details about Storybook below
|
||||
|
||||
**Unit and end-to-end tests:** All components should come with unit tests using Jest and React Testing Library. The file name should follow this naming convention `\{ComponentName\}.test.tsx`. Read the [Testing Guidelines and Best Practices](../../testing/testing-guidelines) for more details
|
||||
|
||||
**Reference naming:** Use `PascalCase` for React components and `camelCase` for component instances
|
||||
|
||||
**BAD:**
|
||||
```jsx
|
||||
import mainNav from './MainNav';
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```jsx
|
||||
import MainNav from './MainNav';
|
||||
```
|
||||
|
||||
**BAD:**
|
||||
```jsx
|
||||
const NavItem = <MainNav />;
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```jsx
|
||||
const navItem = <MainNav />;
|
||||
```
|
||||
|
||||
**Component naming:** Use the file name as the component name
|
||||
|
||||
**BAD:**
|
||||
```jsx
|
||||
import MainNav from './MainNav/index';
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```jsx
|
||||
import MainNav from './MainNav';
|
||||
```
|
||||
|
||||
**Props naming:** Do not use DOM related props for different purposes
|
||||
|
||||
**BAD:**
|
||||
```jsx
|
||||
<MainNav style="big" />
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```jsx
|
||||
<MainNav variant="big" />
|
||||
```
|
||||
|
||||
**Importing dependencies:** Only import what you need
|
||||
|
||||
**BAD:**
|
||||
```jsx
|
||||
import * as React from "react";
|
||||
```
|
||||
|
||||
**GOOD:**
|
||||
```jsx
|
||||
import React, { useState } from "react";
|
||||
```
|
||||
|
||||
**Default VS named exports:** As recommended by [TypeScript](https://www.typescriptlang.org/docs/handbook/modules.html), "If a module's primary purpose is to house one specific export, then you should consider exporting it as a default export. This makes both importing and actually using the import a little easier". If you're exporting multiple objects, use named exports instead.
|
||||
|
||||
_As a default export_
|
||||
```jsx
|
||||
import MainNav from './MainNav';
|
||||
```
|
||||
|
||||
_As a named export_
|
||||
```jsx
|
||||
import { MainNav, SecondaryNav } from './Navbars';
|
||||
```
|
||||
|
||||
**ARIA roles:** Always make sure you are writing accessible components by using the official [ARIA roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques)
|
||||
|
||||
## Use TypeScript
|
||||
|
||||
All components should be written in TypeScript and their extensions should be `.ts` or `.tsx`
|
||||
|
||||
### type vs interface
|
||||
|
||||
Validate all props with the correct types. This replaces the need for a run-time validation as provided by the prop-types library.
|
||||
|
||||
```tsx
|
||||
type HeadingProps = {
|
||||
param: string;
|
||||
}
|
||||
|
||||
export default function Heading({ children }: HeadingProps) {
|
||||
return <h2>{children}</h2>
|
||||
}
|
||||
```
|
||||
|
||||
Use `type` for your component props and state. Use `interface` when you want to enable _declaration merging_.
|
||||
|
||||
### Define default values for non-required props
|
||||
|
||||
In order to improve the readability of your code and reduce assumptions, always add default values for non required props, when applicable, for example:
|
||||
|
||||
```tsx
|
||||
const applyDiscount = (price: number, discount = 0.05) => price * (1 - discount);
|
||||
```
|
||||
|
||||
## Functional components and Hooks
|
||||
|
||||
We prefer functional components and the usage of hooks over class components.
|
||||
|
||||
### useState
|
||||
|
||||
Always explicitly declare the type unless the type can easily be assumed by the declaration.
|
||||
|
||||
```tsx
|
||||
const [customer, setCustomer] = useState<ICustomer | null>(null);
|
||||
```
|
||||
|
||||
### useReducer
|
||||
|
||||
Always prefer `useReducer` over `useState` when your state has complex logics.
|
||||
|
||||
### useMemo and useCallback
|
||||
|
||||
Always memoize when your components take functions or complex objects as props to avoid unnecessary rerenders.
|
||||
|
||||
### Custom hooks
|
||||
|
||||
All custom hooks should be located in the directory `/src/hooks`. Before creating a new custom hook, make sure that is not available in the existing custom hooks.
|
||||
|
||||
## Storybook
|
||||
|
||||
Each component should come with its dedicated storybook file.
|
||||
|
||||
**One component per story:** Each storybook file should only contain one component unless substantially different variants are required
|
||||
|
||||
**Component variants:** If the component behavior is substantially different when certain props are used, it is best to separate the story into different types. See the `superset-frontend/src/components/Select/Select.stories.tsx` as an example.
|
||||
|
||||
**Isolated state:** The storybook should show how the component works in an isolated state and with as few dependencies as possible
|
||||
|
||||
**Use args:** It should be possible to test the component with every variant of the available props. We recommend using [args](https://storybook.js.org/docs/react/writing-stories/args)
|
||||
@@ -0,0 +1,277 @@
|
||||
---
|
||||
title: Emotion Styling Guidelines and Best Practices
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Emotion Styling Guidelines and Best Practices
|
||||
|
||||
## Emotion Styling Guidelines
|
||||
|
||||
### DO these things:
|
||||
|
||||
- **DO** use `styled` when you want to include additional (nested) class selectors in your styles
|
||||
- **DO** use `styled` components when you intend to export a styled component for re-use elsewhere
|
||||
- **DO** use `css` when you want to amend/merge sets of styles compositionally
|
||||
- **DO** use `css` when you're making a small, or single-use set of styles for a component
|
||||
- **DO** move your style definitions from direct usage in the `css` prop to an external variable when they get long
|
||||
- **DO** prefer tagged template literals (`css={css`...`}`) over style objects wherever possible for maximum style portability/consistency (note: typescript support may be diminished, but IDE plugins like [this](https://marketplace.visualstudio.com/items?itemName=jpoissonnier.vscode-styled-components) make life easy)
|
||||
- **DO** use `useTheme` to get theme variables. `withTheme` should be only used for wrapping legacy Class-based components.
|
||||
|
||||
### DON'T do these things:
|
||||
|
||||
- **DON'T** use `styled` for small, single-use style tweaks that would be easier to read/review if they were inline
|
||||
- **DON'T** export incomplete AntD components (make sure all their compound components are exported)
|
||||
|
||||
## Emotion Tips and Strategies
|
||||
|
||||
The first thing to consider when adding styles to an element is how much you think a style might be reusable in other areas of Superset. Always err on the side of reusability here. Nobody wants to chase styling inconsistencies, or try to debug little endless overrides scattered around the codebase. The more we can consolidate, the less will have to be figured out by those who follow. Reduce, reuse, recycle.
|
||||
|
||||
## When to use `css` or `styled`
|
||||
|
||||
In short, either works for just about any use case! And you'll see them used somewhat interchangeably in the existing codebase. But we need a way to weigh it when we encounter the choice, so here's one way to think about it:
|
||||
|
||||
A good use of `styled` syntax if you want to re-use a styled component. In other words, if you wanted to export flavors of a component for use, like so:
|
||||
|
||||
```jsx
|
||||
const StatusThing = styled.div`
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
`;
|
||||
|
||||
export const InfoThing = styled(StatusThing)`
|
||||
background: blue;
|
||||
&::before {
|
||||
content: "ℹ️";
|
||||
}
|
||||
`;
|
||||
|
||||
export const WarningThing = styled(StatusThing)`
|
||||
background: orange;
|
||||
&::before {
|
||||
content: "⚠️";
|
||||
}
|
||||
`;
|
||||
|
||||
export const TerribleThing = styled(StatusThing)`
|
||||
background: red;
|
||||
&::before {
|
||||
content: "🔥";
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
You can also use `styled` when you're building a bigger component, and just want to have some custom bits for internal use in your JSX. For example:
|
||||
|
||||
```jsx
|
||||
const SeparatorOnlyUsedInThisComponent = styled.hr`
|
||||
height: 12px;
|
||||
border: 0;
|
||||
box-shadow: inset 0 12px 12px -12px rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
function SuperComplicatedComponent(props) {
|
||||
return (
|
||||
<>
|
||||
Daily standup for {user.name}!
|
||||
<SeparatorOnlyUsedInThisComponent />
|
||||
<h2>Yesterday:</h2>
|
||||
// spit out a list of accomplishments
|
||||
<SeparatorOnlyUsedInThisComponent />
|
||||
<h2>Today:</h2>
|
||||
// spit out a list of plans
|
||||
<SeparatorOnlyUsedInThisComponent />
|
||||
<h2>Tomorrow:</h2>
|
||||
// spit out a list of goals
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The `css` prop, in reality, shares all the same styling capabilities as `styled` but it does have some particular use cases that jump out as sensible. For example, if you just want to style one element in your component, you could add the styles inline like so:
|
||||
|
||||
```jsx
|
||||
function SomeFanciness(props) {
|
||||
return (
|
||||
<>
|
||||
Here's an awesome report card for {user.name}!
|
||||
<div
|
||||
css={css`
|
||||
box-shadow: 5px 5px 10px #ccc;
|
||||
border-radius: 10px;
|
||||
`}
|
||||
>
|
||||
<h2>Yesterday:</h2>
|
||||
// ...some stuff
|
||||
<h2>Today:</h2>
|
||||
// ...some stuff
|
||||
<h2>Tomorrow:</h2>
|
||||
// ...some stuff
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can also define the styles as a variable, external to your JSX. This is handy if the styles get long and you just want it out of the way. This is also handy if you want to apply the same styles to disparate element types, kind of like you might use a CSS class on varied elements. Here's a trumped up example:
|
||||
|
||||
```jsx
|
||||
function FakeGlobalNav(props) {
|
||||
const menuItemStyles = css`
|
||||
display: block;
|
||||
border-bottom: 1px solid cadetblue;
|
||||
font-family: "Comic Sans", cursive;
|
||||
`;
|
||||
|
||||
return (
|
||||
<Nav>
|
||||
<a css={menuItemStyles} href="#">One link</a>
|
||||
<Link css={menuItemStyles} to={url}>Another link</Link>
|
||||
<div css={menuItemStyles} onClick={() => alert('clicked')}>Another link</div>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## CSS tips and tricks
|
||||
|
||||
### `css` lets you write actual CSS
|
||||
|
||||
By default the `css` prop uses the object syntax with JS style definitions, like so:
|
||||
|
||||
```jsx
|
||||
<div css={{
|
||||
borderRadius: 10,
|
||||
marginTop: 10,
|
||||
backgroundColor: '#00FF00'
|
||||
}}>Howdy</div>
|
||||
```
|
||||
|
||||
But you can use the `css` interpolator as well to get away from icky JS styling syntax. Doesn't this look cleaner?
|
||||
|
||||
```jsx
|
||||
<div css={css`
|
||||
border-radius: 10px;
|
||||
margin-top: 10px;
|
||||
background-color: #00FF00;
|
||||
`}>Howdy</div>
|
||||
```
|
||||
|
||||
You might say "whatever… I can read and write JS syntax just fine." Well, that's great. But… let's say you're migrating in some of our legacy LESS styles… now it's copy/paste! Or if you want to migrate to or from `styled` syntax… also copy/paste!
|
||||
|
||||
### You can combine `css` definitions with array syntax
|
||||
|
||||
You can use multiple groupings of styles with the `css` interpolator, and combine/override them in array syntax, like so:
|
||||
|
||||
```jsx
|
||||
function AnotherSillyExample(props) {
|
||||
const shadowedCard = css`
|
||||
box-shadow: 2px 2px 4px #999;
|
||||
padding: 4px;
|
||||
`;
|
||||
const infoCard = css`
|
||||
background-color: #33f;
|
||||
border-radius: 4px;
|
||||
`;
|
||||
const overrideInfoCard = css`
|
||||
background-color: #f33;
|
||||
`;
|
||||
return (
|
||||
<div className="App">
|
||||
Combining two classes:
|
||||
<div css={[shadowedCard, infoCard]}>Hello</div>
|
||||
Combining again, but now with overrides:
|
||||
<div css={[shadowedCard, infoCard, overrideInfoCard]}>Hello</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Style variations with props
|
||||
|
||||
You can give any component a custom prop, and reference that prop in your component styles, effectively using the prop to turn on a "flavor" of that component.
|
||||
|
||||
For example, let's make a styled component that acts as a card. Of course, this could be done with any AntD component, or any component at all. But we'll do this with a humble `div` to illustrate the point:
|
||||
|
||||
```jsx
|
||||
const SuperCard = styled.div`
|
||||
${({ theme, cutout }) => `
|
||||
padding: ${theme.gridUnit * 2}px;
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
box-shadow: 10px 5px 10px #ccc ${cutout && 'inset'};
|
||||
border: 1px solid ${cutout ? 'transparent' : theme.colors.secondary.light3};
|
||||
`}
|
||||
`;
|
||||
```
|
||||
|
||||
Then just use the component as `<SuperCard>Some content</SuperCard>` or with the (potentially dynamic) prop: `<SuperCard cutout>Some content</SuperCard>`
|
||||
|
||||
## Styled component tips
|
||||
|
||||
### No need to use `theme` the hard way
|
||||
|
||||
It's very tempting (and commonly done) to use the `theme` prop inline in the template literal like so:
|
||||
|
||||
```jsx
|
||||
const SomeStyledThing = styled.div`
|
||||
padding: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
border: 1px solid ${({ theme }) => theme.colors.secondary.light3};
|
||||
`;
|
||||
```
|
||||
|
||||
Instead, you can make things a little easier to read/type by writing it like so:
|
||||
|
||||
```jsx
|
||||
const SomeStyledThing = styled.div`
|
||||
${({ theme }) => `
|
||||
padding: ${theme.gridUnit * 2}px;
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
border: 1px solid ${theme.colors.secondary.light3};
|
||||
`}
|
||||
`;
|
||||
```
|
||||
|
||||
## Extend an AntD component with custom styling
|
||||
|
||||
As mentioned, you want to keep your styling as close to the root of your component system as possible, to minimize repetitive styling/overrides, and err on the side of reusability. In some cases, that means you'll want to globally tweak one of our core components to match our design system. In Superset, that's Ant Design (AntD).
|
||||
|
||||
AntD uses a cool trick called compound components. For example, the `Menu` component also lets you use `Menu.Item`, `Menu.SubMenu`, `Menu.ItemGroup`, and `Menu.Divider`.
|
||||
|
||||
### The `Object.assign` trick
|
||||
|
||||
Let's say you want to override an AntD component called `Foo`, and have `Foo.Bar` display some custom CSS for the `Bar` compound component. You can do it effectively like so:
|
||||
|
||||
```jsx
|
||||
import {
|
||||
Foo as AntdFoo,
|
||||
} from 'antd';
|
||||
|
||||
export const StyledBar = styled(AntdFoo.Bar)`
|
||||
border-radius: ${({ theme }) => theme.borderRadius}px;
|
||||
`;
|
||||
|
||||
export const Foo = Object.assign(AntdFoo, {
|
||||
Bar: StyledBar,
|
||||
});
|
||||
```
|
||||
|
||||
You can then import this customized `Foo` and use `Foo.Bar` as expected. You should probably save your creation in `src/components` for maximum reusability, and add a Storybook entry so future engineers can view your creation, and designers can better understand how it fits the Superset Design System.
|
||||
135
docs/developer_docs/index.md
Normal file
135
docs/developer_docs/index.md
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Overview
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Developer Docs
|
||||
|
||||
Welcome to the Apache Superset Developer Docs - your comprehensive resource for contributing to and extending Apache Superset.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### New Contributors
|
||||
- [Contributing Overview](/developer-docs/contributing/overview)
|
||||
- [Development Setup](/developer-docs/contributing/development-setup)
|
||||
- [Your First PR](/developer-docs/contributing/submitting-pr)
|
||||
|
||||
### Extension Development
|
||||
- [Extension Development](/developer-docs/extensions/development)
|
||||
- [Extension Architecture](/developer-docs/extensions/architecture)
|
||||
- [Quick Start](/developer-docs/extensions/quick-start)
|
||||
|
||||
## Documentation Sections
|
||||
|
||||
### Extensions
|
||||
Learn how to build powerful extensions that enhance Superset's capabilities. This section covers the extension architecture, development patterns, and deployment strategies. You'll find comprehensive guides on creating frontend contributions, managing extension lifecycles, and understanding security implications.
|
||||
|
||||
### Testing
|
||||
Comprehensive testing strategies for Superset development. This section covers frontend testing with Jest and React Testing Library, backend testing with pytest, end-to-end testing with Playwright, and CI/CD pipeline best practices.
|
||||
|
||||
### Contributing to Superset
|
||||
Everything you need to contribute to the Apache Superset project. This section includes community guidelines, development environment setup, pull request processes, code review workflows, issue reporting guidelines, and Apache release procedures. You'll also find style guidelines for both frontend and backend development.
|
||||
|
||||
## Development Resources
|
||||
|
||||
### Prerequisites
|
||||
- **Python**: 3.9, 3.10, or 3.11
|
||||
- **Node.js**: 18.x or 20.x
|
||||
- **npm**: 9.x or 10.x
|
||||
- **Git**: Basic understanding
|
||||
- **React/TypeScript**: For frontend development
|
||||
- **Flask/SQLAlchemy**: For backend development
|
||||
|
||||
### Key Technologies
|
||||
- **Frontend**: React, TypeScript, Ant Design, Redux
|
||||
- **Backend**: Flask, SQLAlchemy, Celery, Redis
|
||||
- **Build Tools**: Webpack, Babel, npm/yarn
|
||||
- **Testing**: Jest, Pytest, Playwright
|
||||
- **CI/CD**: GitHub Actions, pre-commit
|
||||
|
||||
## Community
|
||||
|
||||
### Get Help
|
||||
- **[Slack](https://apache-superset.slack.com)** - Join #development, #troubleshooting, or #beginners
|
||||
- **[GitHub Discussions](https://github.com/apache/superset/discussions)** - Ask questions and share ideas
|
||||
- **[Mailing Lists](https://lists.apache.org/list.html?dev@superset.apache.org)** - Development discussions
|
||||
|
||||
### Contribute
|
||||
- **[Good First Issues](https://github.com/apache/superset/labels/good%20first%20issue)** - Start here!
|
||||
- **[Help Wanted](https://github.com/apache/superset/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)** - Issues needing help
|
||||
- **[Roadmap](https://github.com/orgs/apache/projects/180)** - See what's planned
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### External Documentation
|
||||
- **[User Documentation](https://superset.apache.org/docs/intro)** - Using Superset
|
||||
- **[API Documentation](/developer-docs/api)** - REST API reference
|
||||
- **[Configuration Guide](https://superset.apache.org/admin-docs/configuration/configuring-superset)** - Setup and configuration
|
||||
|
||||
### Important Files
|
||||
- **[CLAUDE.md](https://github.com/apache/superset/blob/master/CLAUDE.md)** - LLM development guide
|
||||
- **[UPDATING.md](https://github.com/apache/superset/blob/master/UPDATING.md)** - Breaking changes log
|
||||
|
||||
## Where to Start?
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
|
||||
**I want to contribute code**
|
||||
1. [Set up development environment](/developer-docs/contributing/development-setup)
|
||||
2. [Find a good first issue](https://github.com/apache/superset/labels/good%20first%20issue)
|
||||
3. [Submit your first PR](/developer-docs/contributing/submitting-pr)
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
||||
**I want to build an extension**
|
||||
1. [Start with Quick Start](/developer-docs/extensions/quick-start)
|
||||
2. [Learn extension development](/developer-docs/extensions/development)
|
||||
3. [Explore architecture](/developer-docs/extensions/architecture)
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
**I found a bug**
|
||||
1. [Search existing issues](https://github.com/apache/superset/issues)
|
||||
2. [Report the bug](/developer-docs/contributing/issue-reporting)
|
||||
3. [Submit a fix](/developer-docs/contributing/submitting-pr)
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
**I need help**
|
||||
1. [Check the FAQ](https://superset.apache.org/docs/frequently-asked-questions)
|
||||
2. [Ask in Slack](https://apache-superset.slack.com)
|
||||
3. [Start a discussion](https://github.com/apache/superset/discussions)
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
---
|
||||
|
||||
Welcome to the Apache Superset community! We're excited to have you contribute.
|
||||
84
docs/developer_docs/sidebars.js
Normal file
84
docs/developer_docs/sidebars.js
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
developerPortalSidebar: [
|
||||
'index',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Contributing',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'contributing/overview',
|
||||
'guidelines/design-guidelines',
|
||||
'guidelines/frontend-style-guidelines',
|
||||
'guidelines/backend-style-guidelines',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Extensions',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'extensions/overview',
|
||||
'extensions/quick-start',
|
||||
'extensions/architecture',
|
||||
'extensions/dependencies',
|
||||
'extensions/contribution-types',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Extension Points',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'extensions/extension-points/sqllab',
|
||||
],
|
||||
},
|
||||
'extensions/development',
|
||||
'extensions/deployment',
|
||||
'extensions/mcp',
|
||||
'extensions/security',
|
||||
'extensions/tasks',
|
||||
'extensions/registry',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Testing',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'testing/overview',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'UI Components',
|
||||
collapsed: true,
|
||||
link: {
|
||||
type: 'doc',
|
||||
id: 'components/index',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'autogenerated',
|
||||
dirName: 'components',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
68
docs/developer_docs/testing/backend-testing.md
Normal file
68
docs/developer_docs/testing/backend-testing.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Backend Testing
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Backend Testing
|
||||
|
||||
🚧 **Coming Soon** 🚧
|
||||
|
||||
Complete guide for testing Superset's Python backend, APIs, and database interactions.
|
||||
|
||||
## Topics to be covered:
|
||||
|
||||
- Pytest configuration and fixtures
|
||||
- Unit testing best practices
|
||||
- Integration testing with databases
|
||||
- API endpoint testing
|
||||
- Mocking strategies and patterns
|
||||
- Testing async operations with Celery
|
||||
- Security testing guidelines
|
||||
- Performance and load testing
|
||||
- Test database setup and teardown
|
||||
- Coverage requirements
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Run all backend tests
|
||||
pytest
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/unit_tests/specific_test.py
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=superset
|
||||
|
||||
# Run tests in parallel
|
||||
pytest -n auto
|
||||
|
||||
# Run only unit tests
|
||||
pytest tests/unit_tests/
|
||||
|
||||
# Run only integration tests
|
||||
pytest tests/integration_tests/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*This documentation is under active development. Check back soon for updates!*
|
||||
70
docs/developer_docs/testing/ci-cd.md
Normal file
70
docs/developer_docs/testing/ci-cd.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: CI/CD and Automation
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# CI/CD and Automation
|
||||
|
||||
🚧 **Coming Soon** 🚧
|
||||
|
||||
Understanding Superset's continuous integration and deployment pipelines.
|
||||
|
||||
## Topics to be covered:
|
||||
|
||||
- GitHub Actions workflows
|
||||
- Pre-commit hooks configuration
|
||||
- Automated testing pipelines
|
||||
- Code quality checks (ESLint, Prettier, Black, MyPy)
|
||||
- Security scanning (Dependabot, CodeQL)
|
||||
- Docker image building and publishing
|
||||
- Release automation
|
||||
- Performance benchmarking
|
||||
- Coverage reporting and tracking
|
||||
|
||||
## Pre-commit Hooks
|
||||
|
||||
```bash
|
||||
# Install pre-commit hooks
|
||||
pre-commit install
|
||||
|
||||
# Run all hooks on staged files
|
||||
pre-commit run
|
||||
|
||||
# Run specific hook
|
||||
pre-commit run mypy
|
||||
|
||||
# Run on all files (not just staged)
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
## GitHub Actions
|
||||
|
||||
Key workflows:
|
||||
- `test-frontend.yml` - Frontend tests
|
||||
- `test-backend.yml` - Backend tests
|
||||
- `docker.yml` - Docker image builds
|
||||
- `codeql.yml` - Security analysis
|
||||
- `release.yml` - Release automation
|
||||
|
||||
---
|
||||
|
||||
*This documentation is under active development. Check back soon for updates!*
|
||||
227
docs/developer_docs/testing/e2e-testing.md
Normal file
227
docs/developer_docs/testing/e2e-testing.md
Normal file
@@ -0,0 +1,227 @@
|
||||
---
|
||||
title: End-to-End Testing
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# End-to-End Testing
|
||||
|
||||
Apache Superset uses Playwright for end-to-end testing, migrating from the legacy Cypress tests.
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Playwright (Recommended)
|
||||
|
||||
```bash
|
||||
cd superset-frontend
|
||||
|
||||
# Run all tests
|
||||
npm run playwright:test
|
||||
# or: npx playwright test
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test tests/auth/login.spec.ts
|
||||
|
||||
# Run with UI mode for debugging
|
||||
npm run playwright:ui
|
||||
# or: npx playwright test --ui
|
||||
|
||||
# Run in headed mode (see browser)
|
||||
npm run playwright:headed
|
||||
# or: npx playwright test --headed
|
||||
|
||||
# Debug specific test file
|
||||
npm run playwright:debug tests/auth/login.spec.ts
|
||||
# or: npx playwright test --debug tests/auth/login.spec.ts
|
||||
```
|
||||
|
||||
### Cypress (Deprecated)
|
||||
|
||||
Cypress tests are being migrated to Playwright. For legacy tests:
|
||||
|
||||
```bash
|
||||
cd superset-frontend/cypress-base
|
||||
npm run cypress-run-chrome # Headless
|
||||
npm run cypress-debug # Interactive UI
|
||||
```
|
||||
|
||||
## Project Architecture
|
||||
|
||||
```
|
||||
superset-frontend/playwright/
|
||||
├── components/core/ # Reusable UI components
|
||||
├── pages/ # Page Object Models
|
||||
├── tests/ # Test files organized by feature
|
||||
├── utils/ # Shared constants and utilities
|
||||
└── playwright.config.ts
|
||||
```
|
||||
|
||||
## Design Principles
|
||||
|
||||
We follow **YAGNI** (You Aren't Gonna Need It), **DRY** (Don't Repeat Yourself), and **KISS** (Keep It Simple, Stupid) principles:
|
||||
|
||||
- Build only what's needed now
|
||||
- Reuse existing patterns and components
|
||||
- Keep solutions simple and maintainable
|
||||
|
||||
## Page Object Pattern
|
||||
|
||||
Each page object encapsulates:
|
||||
|
||||
- **Actions**: What you can do on the page
|
||||
- **Queries**: Information you can get from the page
|
||||
- **Selectors**: Centralized in private static SELECTORS constant
|
||||
- **NO Assertions**: Keep assertions in test files
|
||||
|
||||
**Example Page Object:**
|
||||
|
||||
```typescript
|
||||
export class AuthPage {
|
||||
// Selectors centralized in the page object
|
||||
private static readonly SELECTORS = {
|
||||
LOGIN_FORM: '[data-test="login-form"]',
|
||||
USERNAME_INPUT: '[data-test="username-input"]',
|
||||
} as const;
|
||||
|
||||
// Actions - what you can do
|
||||
async loginWithCredentials(username: string, password: string) {}
|
||||
|
||||
// Queries - information you can get
|
||||
async getCurrentUrl(): Promise<string> {}
|
||||
|
||||
// NO assertions - those belong in tests
|
||||
}
|
||||
```
|
||||
|
||||
**Example Test:**
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { AuthPage } from '../../pages/AuthPage';
|
||||
import { LOGIN } from '../../utils/urls';
|
||||
|
||||
test('should login with correct credentials', async ({ page }) => {
|
||||
const authPage = new AuthPage(page);
|
||||
await authPage.goto();
|
||||
await authPage.loginWithCredentials('admin', 'general');
|
||||
|
||||
// Assertions belong in tests, not page objects
|
||||
expect(await authPage.getCurrentUrl()).not.toContain(LOGIN);
|
||||
});
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
Reusable UI interaction classes for common elements (`components/core/`):
|
||||
|
||||
- **Form**: Container with properly scoped child element access
|
||||
- **Input**: Supports `fill()`, `type()`, and `pressSequentially()` methods
|
||||
- **Button**: Standard click, hover, focus interactions
|
||||
|
||||
**Usage Example:**
|
||||
|
||||
```typescript
|
||||
import { Form } from '../components/core';
|
||||
|
||||
const loginForm = new Form(page, '[data-test="login-form"]');
|
||||
const usernameInput = loginForm.getInput('[data-test="username-input"]');
|
||||
await usernameInput.fill('admin');
|
||||
```
|
||||
|
||||
## Test Reports
|
||||
|
||||
Playwright generates multiple reports for better visibility:
|
||||
|
||||
```bash
|
||||
# View interactive HTML report (opens automatically on failure)
|
||||
npm run playwright:report
|
||||
# or: npx playwright show-report
|
||||
|
||||
# View test trace for debugging failures
|
||||
npx playwright show-trace test-results/[test-name]/trace.zip
|
||||
```
|
||||
|
||||
### Report Types
|
||||
|
||||
- **List Reporter**: Shows progress and summary table in terminal
|
||||
- **HTML Report**: Interactive web interface with screenshots, videos, and traces
|
||||
- **JSON Report**: Machine-readable format in `test-results/results.json`
|
||||
- **GitHub Actions**: Annotations in CI for failed tests
|
||||
|
||||
### Debugging Failed Tests
|
||||
|
||||
When tests fail, Playwright automatically captures:
|
||||
|
||||
- **Screenshots** at the point of failure
|
||||
- **Videos** of the entire test run
|
||||
- **Traces** with timeline and network activity
|
||||
- **Error context** with detailed debugging information
|
||||
|
||||
All debugging artifacts are available in the HTML report for easy analysis.
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Config**: `playwright.config.ts` - matches Cypress settings
|
||||
- **Base URL**: `http://localhost:8088` (assumes Superset running)
|
||||
- **Browsers**: Chrome only for Phase 1 (YAGNI)
|
||||
- **Retries**: 2 in CI, 0 locally (matches Cypress)
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
### Adding New Tests
|
||||
|
||||
1. **Check existing components** before creating new ones
|
||||
2. **Use page objects** for page interactions
|
||||
3. **Keep assertions in tests**, not page objects
|
||||
4. **Follow naming conventions**: `feature.spec.ts`
|
||||
|
||||
### Adding New Components
|
||||
|
||||
1. **Follow YAGNI**: Only build what's immediately needed
|
||||
2. **Use Locator-based scoping** for proper element isolation
|
||||
3. **Support both string selectors and Locator objects** via constructor overloads
|
||||
4. **Add to `components/core/index.ts`** for easy importing
|
||||
|
||||
### Adding New Page Objects
|
||||
|
||||
1. **Centralize selectors** in private static SELECTORS constant
|
||||
2. **Import shared constants** from `utils/urls.ts`
|
||||
3. **Actions and queries only** - no assertions
|
||||
4. **Use existing components** for DOM interactions
|
||||
|
||||
## Migration from Cypress
|
||||
|
||||
When porting Cypress tests:
|
||||
|
||||
1. **Port the logic**, not the implementation
|
||||
2. **Use page objects** instead of inline selectors
|
||||
3. **Replace `cy.intercept/cy.wait`** with `page.waitForRequest()`
|
||||
4. **Use shared constants** from `utils/urls.ts`
|
||||
5. **Follow the established patterns** shown in `tests/auth/login.spec.ts`
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Centralize selectors** in page objects
|
||||
- **Centralize URLs** in `utils/urls.ts`
|
||||
- **Use meaningful test descriptions**
|
||||
- **Keep page objects action-focused**
|
||||
- **Put assertions in tests, not page objects**
|
||||
- **Follow the existing patterns** for consistency
|
||||
61
docs/developer_docs/testing/frontend-testing.md
Normal file
61
docs/developer_docs/testing/frontend-testing.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Frontend Testing
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Frontend Testing
|
||||
|
||||
🚧 **Coming Soon** 🚧
|
||||
|
||||
Comprehensive guide for testing Superset's frontend components and features.
|
||||
|
||||
## Topics to be covered:
|
||||
|
||||
- Jest configuration and setup
|
||||
- React Testing Library best practices
|
||||
- Component testing strategies
|
||||
- Redux store testing
|
||||
- Async operations and API mocking
|
||||
- Snapshot testing guidelines
|
||||
- Coverage requirements and reporting
|
||||
- Debugging test failures
|
||||
- Performance testing for UI components
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Run all frontend tests
|
||||
npm run test
|
||||
|
||||
# Run tests in watch mode
|
||||
npm run test -- --watch
|
||||
|
||||
# Run tests with coverage
|
||||
npm run test -- --coverage
|
||||
|
||||
# Run specific test file
|
||||
npm run test -- MyComponent.test.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*This documentation is under active development. Check back soon for updates!*
|
||||
162
docs/developer_docs/testing/overview.md
Normal file
162
docs/developer_docs/testing/overview.md
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: Overview
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Overview
|
||||
|
||||
Apache Superset follows a comprehensive testing strategy that ensures code quality, reliability, and maintainability. This section covers all aspects of testing in the Superset ecosystem, from unit tests to end-to-end testing workflows.
|
||||
|
||||
## Testing Philosophy
|
||||
|
||||
Superset embraces a testing pyramid approach:
|
||||
|
||||
- **Unit Tests**: Fast, isolated tests for individual components and functions
|
||||
- **Integration Tests**: Tests that verify component interactions and API endpoints
|
||||
- **End-to-End Tests**: Full user journey testing in browser environments
|
||||
|
||||
## Testing Documentation
|
||||
|
||||
### Frontend Testing
|
||||
- **[Frontend Testing](./frontend-testing)** - Jest, React Testing Library, and component testing strategies
|
||||
|
||||
### Backend Testing
|
||||
- **[Backend Testing](./backend-testing)** - pytest, database testing, and API testing patterns
|
||||
|
||||
### End-to-End Testing
|
||||
- **[E2E Testing](./e2e-testing)** - Playwright testing for complete user workflows
|
||||
|
||||
### CI/CD Integration
|
||||
- **[CI/CD](./ci-cd)** - Continuous integration, automated testing, and deployment pipelines
|
||||
|
||||
## Testing Tools & Frameworks
|
||||
|
||||
### Frontend
|
||||
- **Jest**: JavaScript testing framework for unit and integration tests
|
||||
- **React Testing Library**: Component testing utilities focused on user behavior
|
||||
- **Playwright**: Modern end-to-end testing for web applications
|
||||
- **Storybook**: Component development and visual testing environment
|
||||
|
||||
### Backend
|
||||
- **pytest**: Python testing framework with powerful fixtures and plugins
|
||||
- **SQLAlchemy Test Utilities**: Database testing and transaction management
|
||||
- **Flask Test Client**: API endpoint testing and request simulation
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Writing Effective Tests
|
||||
1. **Test Behavior, Not Implementation**: Focus on what the code should do, not how it does it
|
||||
2. **Keep Tests Independent**: Each test should be able to run in isolation
|
||||
3. **Use Descriptive Names**: Test names should clearly describe what is being tested
|
||||
4. **Arrange, Act, Assert**: Structure tests with clear setup, execution, and verification phases
|
||||
|
||||
### Test Organization
|
||||
- **Colocation**: Place test files near the code they test
|
||||
- **Naming Conventions**: Use consistent naming patterns for test files and functions
|
||||
- **Test Categories**: Organize tests by type (unit, integration, e2e)
|
||||
- **Test Data Management**: Use factories and fixtures for consistent test data
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Quick Commands
|
||||
```bash
|
||||
# Frontend unit tests
|
||||
npm run test
|
||||
|
||||
# Backend unit tests
|
||||
pytest tests/unit_tests/
|
||||
|
||||
# End-to-end tests
|
||||
npm run playwright:test
|
||||
|
||||
# All tests with coverage
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
### Test Development Workflow
|
||||
1. **Write Failing Test**: Start with a test that describes the desired behavior
|
||||
2. **Implement Feature**: Write the minimum code to make the test pass
|
||||
3. **Refactor**: Improve code quality while keeping tests green
|
||||
4. **Verify Coverage**: Ensure adequate test coverage for new code
|
||||
|
||||
## Testing in Development
|
||||
|
||||
### Test-Driven Development (TDD)
|
||||
- Write tests before implementation
|
||||
- Use tests to guide design decisions
|
||||
- Maintain fast feedback loops
|
||||
|
||||
### Continuous Testing
|
||||
- Run tests automatically on code changes
|
||||
- Integrate testing into development workflow
|
||||
- Use pre-commit hooks for test validation
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **Set up Testing Environment**: Follow the development setup guide
|
||||
2. **Run Existing Tests**: Familiarize yourself with the test suite
|
||||
3. **Write Your First Test**: Start with a simple unit test
|
||||
4. **Learn Testing Patterns**: Study existing tests for patterns and conventions
|
||||
|
||||
## Topics to be covered:
|
||||
|
||||
- Testing strategy and pyramid
|
||||
- Test-driven development (TDD) for plugins
|
||||
- Continuous integration and automated testing
|
||||
- Code coverage and quality metrics
|
||||
- Testing tools and frameworks overview
|
||||
- Mock data and test fixtures
|
||||
- Performance testing and benchmarking
|
||||
- Accessibility testing automation
|
||||
- Cross-browser and device testing
|
||||
- Regression testing strategies
|
||||
|
||||
## Testing Levels
|
||||
|
||||
### Unit Testing
|
||||
- **Component testing** - Individual React components
|
||||
- **Function testing** - Data transformation and utility functions
|
||||
- **Hook testing** - Custom React hooks
|
||||
- **Service testing** - API clients and business logic
|
||||
|
||||
### Integration Testing
|
||||
- **API integration** - Backend service communication
|
||||
- **Component integration** - Multi-component workflows
|
||||
- **Data flow testing** - End-to-end data processing
|
||||
- **Plugin lifecycle testing** - Installation and activation
|
||||
|
||||
### End-to-End Testing
|
||||
- **User workflow testing** - Complete user journeys
|
||||
- **Cross-browser testing** - Browser compatibility
|
||||
- **Performance testing** - Load and stress testing
|
||||
- **Accessibility testing** - Screen reader and keyboard navigation
|
||||
|
||||
## Testing Tools
|
||||
|
||||
- **Jest** - Unit and integration testing framework
|
||||
- **React Testing Library** - Component testing utilities
|
||||
- **Playwright** - End-to-end testing (replacing Cypress)
|
||||
- **Storybook** - Component development and testing
|
||||
|
||||
---
|
||||
|
||||
*This documentation is under active development. Check back soon for updates!*
|
||||
114
docs/developer_docs/testing/storybook.md
Normal file
114
docs/developer_docs/testing/storybook.md
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Storybook
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Storybook
|
||||
|
||||
Superset uses [Storybook](https://storybook.js.org/) for developing and testing UI components in isolation. Storybook provides a sandbox to build components independently, outside of the main application.
|
||||
|
||||
## Public Storybook
|
||||
|
||||
A public Storybook with components from the `master` branch is available at:
|
||||
|
||||
**[apache-superset.github.io/superset-ui](https://apache-superset.github.io/superset-ui/?path=/story/*)**
|
||||
|
||||
## Running Locally
|
||||
|
||||
### Main Superset Storybook
|
||||
|
||||
To run the main Superset Storybook locally:
|
||||
|
||||
```bash
|
||||
cd superset-frontend
|
||||
|
||||
# Start Storybook (opens at http://localhost:6006)
|
||||
npm run storybook
|
||||
|
||||
# Build static Storybook
|
||||
npm run build-storybook
|
||||
```
|
||||
|
||||
### @superset-ui Package Storybook
|
||||
|
||||
The `@superset-ui` packages have a separate Storybook for component library development:
|
||||
|
||||
```bash
|
||||
cd superset-frontend
|
||||
|
||||
# Install dependencies and bootstrap packages
|
||||
npm ci && npm run bootstrap
|
||||
|
||||
# Start the @superset-ui Storybook (opens at http://localhost:9001)
|
||||
cd packages/superset-ui-demo
|
||||
npm run storybook
|
||||
```
|
||||
|
||||
## Adding Stories
|
||||
|
||||
### To an Existing Package
|
||||
|
||||
If stories already exist for the package, extend the `examples` array in the package's story file:
|
||||
|
||||
```
|
||||
storybook/stories/<package>/index.js
|
||||
```
|
||||
|
||||
### To a New Package
|
||||
|
||||
1. Add package dependencies:
|
||||
|
||||
```bash
|
||||
npm install <package>
|
||||
```
|
||||
|
||||
2. Create a story folder matching the package name:
|
||||
|
||||
```bash
|
||||
mkdir storybook/stories/superset-ui-<package>/
|
||||
```
|
||||
|
||||
3. Create an `index.js` file with the story configuration:
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
examples: [
|
||||
{
|
||||
storyPath: '@superset-ui/package',
|
||||
storyName: 'My Story',
|
||||
renderStory: () => <MyComponent />,
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Use the `|` separator for nested stories:
|
||||
```javascript
|
||||
storyPath: '@superset-ui/package|Category|Subcategory'
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Isolate components**: Stories should render components in isolation, without application context
|
||||
- **Show variations**: Create stories for different states, sizes, and configurations
|
||||
- **Document props**: Use Storybook's controls to expose configurable props
|
||||
- **Test edge cases**: Include stories for loading states, error states, and empty states
|
||||
129
docs/developer_docs/testing/testing-guidelines.md
Normal file
129
docs/developer_docs/testing/testing-guidelines.md
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
title: Testing Guidelines and Best Practices
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Testing Guidelines and Best Practices
|
||||
|
||||
We feel that tests are an important part of a feature and not an additional or optional effort. That's why we colocate test files with functionality and sometimes write tests upfront to help validate requirements and shape the API of our components. Every new component or file added should have an associated test file with the `.test` extension.
|
||||
|
||||
We use Jest, React Testing Library (RTL), and Cypress to write our unit, integration, and end-to-end tests. For each type, we have a set of best practices/tips described below:
|
||||
|
||||
## Jest
|
||||
|
||||
### Write simple, standalone tests
|
||||
|
||||
The importance of simplicity is often overlooked in test cases. Clear, dumb code should always be preferred over complex ones. The test cases should be pretty much standalone and should not involve any external logic if not absolutely necessary. That's because you want the corpus of the tests to be easy to read and understandable at first sight.
|
||||
|
||||
### Avoid nesting when you're testing
|
||||
|
||||
Avoid the use of `describe` blocks in favor of inlined tests. If your tests start to grow and you feel the need to group tests, prefer to break them into multiple test files. Check this awesome [article](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) written by [Kent C. Dodds](https://kentcdodds.com/) about this topic.
|
||||
|
||||
### No warnings!
|
||||
|
||||
Your tests shouldn't trigger warnings. This is really common when testing async functionality. It's really difficult to read test results when we have a bunch of warnings.
|
||||
|
||||
### Example
|
||||
|
||||
You can find an example of a test [here](https://github.com/apache/superset/blob/e6c5bf4/superset-frontend/src/common/hooks/useChangeEffect/useChangeEffect.test.ts).
|
||||
|
||||
## React Testing Library (RTL)
|
||||
|
||||
### Keep accessibility in mind when writing your tests
|
||||
|
||||
One of the most important points of RTL is accessibility and this is also a very important point for us. We should try our best to follow the RTL [Priority](https://testing-library.com/docs/queries/about/#priority) when querying for elements in our tests. `getByTestId` is not viewable by the user and should only be used when the element isn't accessible in any other way.
|
||||
|
||||
### Don't use `act` unnecessarily
|
||||
|
||||
`render` and `fireEvent` are already wrapped in `act`, so wrapping them in `act` again is a common mistake. Some solutions to the warnings related to `act` might be found [here](https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning).
|
||||
|
||||
### Be specific when using *ByRole
|
||||
|
||||
By using the `name` option we can point to the items by their accessible name. For example:
|
||||
|
||||
```jsx
|
||||
screen.getByRole('button', { name: /hello world/i })
|
||||
```
|
||||
|
||||
Using the `name` property also avoids breaking the tests in the future if other components with the same role are added.
|
||||
|
||||
### userEvent vs fireEvent
|
||||
|
||||
Prefer the [user-event](https://github.com/testing-library/user-event) library, which provides a more advanced simulation of browser interactions than the built-in [fireEvent](https://testing-library.com/docs/dom-testing-library/api-events/#fireevent) method.
|
||||
|
||||
### Usage of waitFor
|
||||
|
||||
- [Prefer to use `find`](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#using-waitfor-to-wait-for-elements-that-can-be-queried-with-find) over `waitFor` when you're querying for elements. Even though both achieve the same objective, the `find` version is simpler and you'll get better error messages.
|
||||
- Prefer to use only [one assertion at a time](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#having-multiple-assertions-in-a-single-waitfor-callback). If you put multiple assertions inside a `waitFor` we could end up waiting for the whole block timeout before seeing a test failure even if the failure occurred at the first assertion. By putting a single assertion in there, we can improve on test execution time.
|
||||
- Do not perform [side-effects](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#performing-side-effects-in-waitfor) inside `waitFor`. The callback can be called a non-deterministic number of times and frequency (it's called both on an interval as well as when there are DOM mutations). So this means that your side-effect could run multiple times.
|
||||
|
||||
### Example
|
||||
|
||||
You can find an example of a test [here](https://github.com/apache/superset/blob/master/superset-frontend/src/dashboard/components/PublishedStatus/PublishedStatus.test.tsx).
|
||||
|
||||
## Cypress
|
||||
|
||||
### Prefer to use Cypress for e2e testing and RTL for integration testing
|
||||
|
||||
Here it's important to make the distinction between e2e and integration testing. This [article](https://www.onpathtesting.com/blog/end-to-end-vs-integration-testing) gives an excellent definition:
|
||||
|
||||
> End-to-end testing verifies that your software works correctly from the beginning to the end of a particular user flow. It replicates expected user behavior and various usage scenarios to ensure that your software works as whole. End-to-end testing uses a production equivalent environment and data to simulate real-world situations and may also involve the integrations your software has with external applications.
|
||||
|
||||
> A typical software project consists of multiple software units, usually coded by different developers. Integration testing combines those software units logically and tests them as a group.
|
||||
> Essentially, integration testing verifies whether or not the individual modules or services that make up your application work well together. The purpose of this level of testing is to expose defects in the interaction between these software modules when they are integrated.
|
||||
|
||||
Do not use Cypress when RTL can do it better and faster. Many of the Cypress tests that we have right now, fall into the integration testing category and can be ported to RTL which is much faster and gives more immediate feedback. Cypress should be used mainly for end-to-end testing, replicating the user experience, with positive and negative flows.
|
||||
|
||||
### Isolated and standalone tests
|
||||
|
||||
Tests should never rely on other tests to pass. This might be hard when a single user is used for testing as data will be stored in the database. At every new test, we should reset the database.
|
||||
|
||||
### Cleaning state
|
||||
|
||||
Cleaning the state of the application, such as resetting the DB, or in general, any state that might affect consequent tests should always be done in the `beforeEach` hook and never in the `afterEach` one as the `beforeEach` is guaranteed to run, while the test might never reach the point to run the `afterEach` hook. One example would be if you refresh Cypress in the middle of the test. At this point, you will have built up a partial state in the database, and your clean-up function will never get called. You can read more about it [here](https://docs.cypress.io/guides/references/best-practices#Using-after-or-afterEach-hooks).
|
||||
|
||||
### Unnecessary use of `cy.wait`
|
||||
|
||||
- Unnecessary when using `cy.request()` as it will resolve when a response is received from the server
|
||||
- Unnecessary when using `cy.visit()` as it resolves only when the page fires the load event
|
||||
- Unnecessary when using `cy.get()`. When the selector should wait for a request to happen, aliases would come in handy:
|
||||
|
||||
```js
|
||||
cy.intercept('GET', '/users', [{ name: 'Maggy' }, { name: 'Joan' }]).as('getUsers')
|
||||
cy.get('#fetch').click()
|
||||
cy.wait('@getUsers') // <--- wait explicitly for this route to finish
|
||||
cy.get('table tr').should('have.length', 2)
|
||||
```
|
||||
|
||||
### Accessibility and Resilience
|
||||
|
||||
The same accessibility principles in the RTL section apply here. Use accessible selectors when querying for elements. Those principles also help to isolate selectors from eventual CSS and JS changes and improve the resilience of your tests.
|
||||
|
||||
## References
|
||||
|
||||
- [Avoid Nesting when you're Testing](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing)
|
||||
- [RTL - Queries](https://testing-library.com/docs/queries/about/#priority)
|
||||
- [Fix the "not wrapped in act(...)" warning](https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning)
|
||||
- [Common mistakes with React Testing Library](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)
|
||||
- [ARIA Roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles)
|
||||
- [Cypress - Best Practices](https://docs.cypress.io/guides/references/best-practices)
|
||||
- [End-to-end vs integration tests: what's the difference?](https://www.onpathtesting.com/blog/end-to-end-vs-integration-testing)
|
||||
1
docs/developer_docs/versions.json
Normal file
1
docs/developer_docs/versions.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
Reference in New Issue
Block a user