mirror of
https://github.com/apache/superset.git
synced 2026-05-14 12:25:19 +00:00
Compare commits
1 Commits
fix/mcp-ex
...
fix/check-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1851ff3b2 |
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -36,10 +36,6 @@
|
|||||||
**/*.geojson @villebro @rusackas
|
**/*.geojson @villebro @rusackas
|
||||||
/superset-frontend/plugins/legacy-plugin-chart-country-map/ @villebro @rusackas
|
/superset-frontend/plugins/legacy-plugin-chart-country-map/ @villebro @rusackas
|
||||||
|
|
||||||
# Notify translation maintainers of changes to translations
|
|
||||||
|
|
||||||
/superset/translations/ @sfirke
|
|
||||||
|
|
||||||
# Notify PMC members of changes to extension-related files
|
# Notify PMC members of changes to extension-related files
|
||||||
|
|
||||||
/docs/developer_portal/extensions/ @michael-s-molina @villebro @rusackas
|
/docs/developer_portal/extensions/ @michael-s-molina @villebro @rusackas
|
||||||
|
|||||||
4
.github/workflows/ephemeral-env.yml
vendored
4
.github/workflows/ephemeral-env.yml
vendored
@@ -265,7 +265,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Fill in the new image ID in the Amazon ECS task definition
|
- name: Fill in the new image ID in the Amazon ECS task definition
|
||||||
id: task-def
|
id: task-def
|
||||||
uses: aws-actions/amazon-ecs-render-task-definition@6853cfae8c3a7d978fbf68b5a55453395541dfbb # v1
|
uses: aws-actions/amazon-ecs-render-task-definition@77954e213ba1f9f9cb016b86a1d4f6fcdea0d57e # v1
|
||||||
with:
|
with:
|
||||||
task-definition: .github/workflows/ecs-task-definition.json
|
task-definition: .github/workflows/ecs-task-definition.json
|
||||||
container-name: superset-ci
|
container-name: superset-ci
|
||||||
@@ -300,7 +300,7 @@ jobs:
|
|||||||
--tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }}
|
--tags key=pr,value=$PR_NUMBER key=github_user,value=${{ github.actor }}
|
||||||
- name: Deploy Amazon ECS task definition
|
- name: Deploy Amazon ECS task definition
|
||||||
id: deploy-task
|
id: deploy-task
|
||||||
uses: aws-actions/amazon-ecs-deploy-task-definition@a310a830f5c14e583e35d84e4e1ec7dd177c3c9c # v2
|
uses: aws-actions/amazon-ecs-deploy-task-definition@fc8fc60f3a60ffd500fcb13b209c59d221ac8c8c # v2
|
||||||
with:
|
with:
|
||||||
task-definition: ${{ steps.task-def.outputs.task-definition }}
|
task-definition: ${{ steps.task-def.outputs.task-definition }}
|
||||||
service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service
|
service: pr-${{ github.event.inputs.issue_number || github.event.pull_request.number }}-service
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ jobs:
|
|||||||
SUPERSET_SECRET_KEY: not-a-secret
|
SUPERSET_SECRET_KEY: not-a-secret
|
||||||
run: |
|
run: |
|
||||||
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
|
pytest --durations-min=0.5 --cov=superset/sql/ ./tests/unit_tests/sql/ --cache-clear --cov-fail-under=100
|
||||||
pytest --durations-min=0.5 --cov=superset/semantic_layers/ ./tests/unit_tests/semantic_layers/ --cache-clear --cov-fail-under=100
|
|
||||||
- name: Upload code coverage
|
- name: Upload code coverage
|
||||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -46,13 +46,6 @@ The Deck.gl MapBox chart's **Opacity**, **Default longitude**, **Default latitud
|
|||||||
|
|
||||||
**To restore fit-to-data behavior:** Open the chart in Explore, clear the **Default longitude**, **Default latitude**, and **Zoom** fields in the Viewport section, and re-save the chart.
|
**To restore fit-to-data behavior:** Open the chart in Explore, clear the **Default longitude**, **Default latitude**, and **Zoom** fields in the Viewport section, and re-save the chart.
|
||||||
|
|
||||||
### Combined datasource list endpoint
|
|
||||||
|
|
||||||
Added a new combined datasource list endpoint at `GET /api/v1/datasource/` to serve datasets and semantic views in one response.
|
|
||||||
|
|
||||||
- The endpoint is available to users with at least one of `can_read` on `Dataset` or `SemanticView`.
|
|
||||||
- Semantic views are included only when the `SEMANTIC_LAYERS` feature flag is enabled.
|
|
||||||
- The endpoint enforces strict `order_column` validation and returns `400` for invalid sort columns.
|
|
||||||
### ClickHouse minimum driver version bump
|
### ClickHouse minimum driver version bump
|
||||||
|
|
||||||
The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.
|
The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.
|
||||||
|
|||||||
@@ -105,13 +105,7 @@ class CeleryConfig:
|
|||||||
|
|
||||||
CELERY_CONFIG = CeleryConfig
|
CELERY_CONFIG = CeleryConfig
|
||||||
|
|
||||||
FEATURE_FLAGS = {
|
FEATURE_FLAGS = {"ALERT_REPORTS": True, "DATASET_FOLDERS": True}
|
||||||
"ALERT_REPORTS": True,
|
|
||||||
"DATASET_FOLDERS": True,
|
|
||||||
"ENABLE_EXTENSIONS": True,
|
|
||||||
"SEMANTIC_LAYERS": True,
|
|
||||||
}
|
|
||||||
EXTENSIONS_PATH = "/app/docker/extensions"
|
|
||||||
ALERT_REPORTS_NOTIFICATION_DRY_RUN = True
|
ALERT_REPORTS_NOTIFICATION_DRY_RUN = True
|
||||||
WEBDRIVER_BASEURL = f"http://superset_app{os.environ.get('SUPERSET_APP_ROOT', '/')}/" # When using docker compose baseurl should be http://superset_nginx{ENV{BASEPATH}}/ # noqa: E501
|
WEBDRIVER_BASEURL = f"http://superset_app{os.environ.get('SUPERSET_APP_ROOT', '/')}/" # When using docker compose baseurl should be http://superset_nginx{ENV{BASEPATH}}/ # noqa: E501
|
||||||
# The base URL for the email report hyperlinks.
|
# The base URL for the email report hyperlinks.
|
||||||
|
|||||||
@@ -224,52 +224,3 @@ async def analysis_guide(ctx: Context) -> str:
|
|||||||
```
|
```
|
||||||
|
|
||||||
See [MCP Integration](./mcp) for implementation details.
|
See [MCP Integration](./mcp) for implementation details.
|
||||||
|
|
||||||
### Semantic Layers
|
|
||||||
|
|
||||||
Extensions can register custom semantic layer implementations that allow Superset to connect to external data modeling frameworks. Each semantic layer defines how to authenticate, discover semantic views (tables/metrics/dimensions), and execute queries against the external system.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from superset_core.semantic_layers.decorators import semantic_layer
|
|
||||||
from superset_core.semantic_layers.layer import SemanticLayer
|
|
||||||
|
|
||||||
from my_extension.config import MyConfig
|
|
||||||
from my_extension.view import MySemanticView
|
|
||||||
|
|
||||||
|
|
||||||
@semantic_layer(
|
|
||||||
id="my_platform",
|
|
||||||
name="My Data Platform",
|
|
||||||
description="Connect to My Data Platform's semantic layer",
|
|
||||||
)
|
|
||||||
class MySemanticLayer(SemanticLayer[MyConfig, MySemanticView]):
|
|
||||||
configuration_class = MyConfig
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_configuration(cls, configuration: dict) -> "MySemanticLayer":
|
|
||||||
config = MyConfig.model_validate(configuration)
|
|
||||||
return cls(config)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_configuration_schema(cls, configuration=None) -> dict:
|
|
||||||
return MyConfig.model_json_schema()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_runtime_schema(cls, configuration=None, runtime_data=None) -> dict:
|
|
||||||
return {"type": "object", "properties": {}}
|
|
||||||
|
|
||||||
def get_semantic_views(self, runtime_configuration: dict) -> set[MySemanticView]:
|
|
||||||
# Return available views from the external platform
|
|
||||||
...
|
|
||||||
|
|
||||||
def get_semantic_view(self, name: str, additional_configuration: dict) -> MySemanticView:
|
|
||||||
# Return a specific view by name
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: The `@semantic_layer` decorator automatically detects context and applies appropriate ID prefixing:
|
|
||||||
|
|
||||||
- **Extension context**: ID prefixed as `extensions.{publisher}.{name}.{id}`
|
|
||||||
- **Host context**: Original ID used as-is
|
|
||||||
|
|
||||||
The decorator registers the class in the semantic layers registry, making it available in the UI for users to create connections. The `configuration_class` should be a Pydantic model that defines the fields needed to connect (credentials, project, database, etc.). Superset uses the model's JSON schema to render the configuration form dynamically.
|
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
---
|
|
||||||
title: Handlebars Chart
|
|
||||||
hide_title: true
|
|
||||||
sidebar_position: 10
|
|
||||||
version: 1
|
|
||||||
---
|
|
||||||
|
|
||||||
## Handlebars Chart
|
|
||||||
|
|
||||||
The Handlebars chart lets you render query results using a custom [Handlebars](https://handlebarsjs.com/) template. This gives you full control over how your data is displayed — from simple tables to rich HTML layouts.
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
|
|
||||||
In the chart editor, write a Handlebars template in the **Template** field. Your query results are available as `data`, an array of row objects.
|
|
||||||
|
|
||||||
```handlebars
|
|
||||||
{{#each data}}
|
|
||||||
<p>{{this.name}}: {{this.value}}</p>
|
|
||||||
{{/each}}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Built-in Helpers
|
|
||||||
|
|
||||||
Superset registers several custom helpers on top of the standard Handlebars built-ins.
|
|
||||||
|
|
||||||
#### `dateFormat`
|
|
||||||
|
|
||||||
Formats a date value using [Day.js](https://day.js.org/) format strings.
|
|
||||||
|
|
||||||
```handlebars
|
|
||||||
{{dateFormat my_date format="MMMM YYYY"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Option | Default | Description |
|
|
||||||
|--------|---------|-------------|
|
|
||||||
| `format` | `YYYY-MM-DD` | A Day.js-compatible format string |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `stringify`
|
|
||||||
|
|
||||||
Converts an object to a JSON string, or any other value to its string representation.
|
|
||||||
|
|
||||||
```handlebars
|
|
||||||
{{stringify myObj}}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `formatNumber`
|
|
||||||
|
|
||||||
Formats a number using locale-aware formatting.
|
|
||||||
|
|
||||||
```handlebars
|
|
||||||
{{formatNumber myNumber "en-US"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Option | Default | Description |
|
|
||||||
|--------|---------|-------------|
|
|
||||||
| `locale` | `en-US` | A BCP 47 language tag |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `parseJson`
|
|
||||||
|
|
||||||
Parses a JSON string into an object that can be used in your template.
|
|
||||||
|
|
||||||
```handlebars
|
|
||||||
{{parseJson myJsonString}}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `groupBy`
|
|
||||||
|
|
||||||
Groups an array of objects by a key, powered by [handlebars-group-by](https://github.com/nicktindall/handlebars-group-by).
|
|
||||||
|
|
||||||
```handlebars
|
|
||||||
{{#groupBy data "department"}}
|
|
||||||
<h3>{{value}}</h3>
|
|
||||||
{{#each items}}
|
|
||||||
<p>{{this.name}}</p>
|
|
||||||
{{/each}}
|
|
||||||
{{/groupBy}}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Helpers from just-handlebars-helpers
|
|
||||||
|
|
||||||
Superset also registers all helpers from the [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) library. These include a wide range of comparison, math, string, and conditional helpers. Commonly used ones include:
|
|
||||||
|
|
||||||
#### Comparison
|
|
||||||
|
|
||||||
| Helper | Description | Example |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| `eq` | Strict equality | `{{#if (eq status "active")}}` |
|
|
||||||
| `eqw` | Weak equality | `{{#if (eqw count "5")}}` |
|
|
||||||
| `neq` | Strict inequality | `{{#if (neq role "admin")}}` |
|
|
||||||
| `lt` | Less than | `{{#if (lt score 50)}}` |
|
|
||||||
| `lte` | Less than or equal | `{{#if (lte score 100)}}` |
|
|
||||||
| `gt` | Greater than | `{{#if (gt price 0)}}` |
|
|
||||||
| `gte` | Greater than or equal | `{{#if (gte age 18)}}` |
|
|
||||||
|
|
||||||
#### Logical
|
|
||||||
|
|
||||||
| Helper | Description | Example |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| `and` | Logical AND | `{{#if (and isActive isVerified)}}` |
|
|
||||||
| `or` | Logical OR | `{{#if (or isAdmin isMod)}}` |
|
|
||||||
| `not` | Logical NOT | `{{#if (not isDisabled)}}` |
|
|
||||||
| `ifx` | Inline conditional | `{{ifx isActive "Yes" "No"}}` |
|
|
||||||
| `coalesce` | Returns first non-falsy value | `{{coalesce nickname name "Anonymous"}}` |
|
|
||||||
|
|
||||||
#### String
|
|
||||||
|
|
||||||
| Helper | Description | Example |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| `capitalize` | Capitalizes first letter | `{{capitalize name}}` |
|
|
||||||
| `uppercase` | Converts to uppercase | `{{uppercase status}}` |
|
|
||||||
| `lowercase` | Converts to lowercase | `{{lowercase email}}` |
|
|
||||||
| `truncate` | Truncates a string | `{{truncate description 100}}` |
|
|
||||||
| `contains` | Checks if string contains substring | `{{#if (contains tag "urgent")}}` |
|
|
||||||
|
|
||||||
#### Math
|
|
||||||
|
|
||||||
| Helper | Description | Example |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| `add` | Addition | `{{add a b}}` |
|
|
||||||
| `subtract` | Subtraction | `{{subtract total discount}}` |
|
|
||||||
| `multiply` | Multiplication | `{{multiply price quantity}}` |
|
|
||||||
| `divide` | Division | `{{divide total count}}` |
|
|
||||||
| `ceil` | Ceiling | `{{ceil value}}` |
|
|
||||||
| `floor` | Floor | `{{floor value}}` |
|
|
||||||
| `round` | Round | `{{round value}}` |
|
|
||||||
|
|
||||||
For the full list of available helpers, see the [just-handlebars-helpers documentation](https://github.com/leapfrogtechnology/just-handlebars-helpers).
|
|
||||||
|
|
||||||
### Tips
|
|
||||||
|
|
||||||
- Use raw blocks to escape Handlebars syntax if you need to display double curly braces literally.
|
|
||||||
- Comparison helpers like `eq` must be wrapped in a subexpression when used with `#if`: `{{#if (eq myVal "foo")}}`.
|
|
||||||
- HTML output is sanitized by default based on your Superset configuration (`HTML_SANITIZATION`).
|
|
||||||
@@ -41,12 +41,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.2.2",
|
"@ant-design/icons": "^6.2.2",
|
||||||
"@docusaurus/core": "^3.10.1",
|
"@docusaurus/core": "^3.10.0",
|
||||||
"@docusaurus/faster": "^3.10.1",
|
"@docusaurus/faster": "^3.10.0",
|
||||||
"@docusaurus/plugin-client-redirects": "^3.10.1",
|
"@docusaurus/plugin-client-redirects": "^3.10.0",
|
||||||
"@docusaurus/preset-classic": "3.10.1",
|
"@docusaurus/preset-classic": "3.10.0",
|
||||||
"@docusaurus/theme-live-codeblock": "^3.10.1",
|
"@docusaurus/theme-live-codeblock": "^3.10.0",
|
||||||
"@docusaurus/theme-mermaid": "^3.10.1",
|
"@docusaurus/theme-mermaid": "^3.10.0",
|
||||||
"@emotion/core": "^11.0.0",
|
"@emotion/core": "^11.0.0",
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
@@ -67,12 +67,12 @@
|
|||||||
"@storybook/preview-api": "^8.6.18",
|
"@storybook/preview-api": "^8.6.18",
|
||||||
"@storybook/theming": "^8.6.15",
|
"@storybook/theming": "^8.6.15",
|
||||||
"@superset-ui/core": "^0.20.4",
|
"@superset-ui/core": "^0.20.4",
|
||||||
"@swc/core": "^1.15.33",
|
"@swc/core": "^1.15.32",
|
||||||
"antd": "^6.3.7",
|
"antd": "^6.3.7",
|
||||||
"baseline-browser-mapping": "^2.10.27",
|
"baseline-browser-mapping": "^2.10.24",
|
||||||
"caniuse-lite": "^1.0.30001791",
|
"caniuse-lite": "^1.0.30001791",
|
||||||
"docusaurus-plugin-openapi-docs": "^5.0.2",
|
"docusaurus-plugin-openapi-docs": "^5.0.1",
|
||||||
"docusaurus-theme-openapi-docs": "^5.0.2",
|
"docusaurus-theme-openapi-docs": "^5.0.1",
|
||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
"js-yaml-loader": "^1.2.2",
|
"js-yaml-loader": "^1.2.2",
|
||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
@@ -92,8 +92,8 @@
|
|||||||
"unist-util-visit": "^5.1.0"
|
"unist-util-visit": "^5.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "^3.10.1",
|
"@docusaurus/module-type-aliases": "^3.10.0",
|
||||||
"@docusaurus/tsconfig": "^3.10.1",
|
"@docusaurus/tsconfig": "^3.10.0",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.5.5",
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^17.6.0",
|
"globals": "^17.5.0",
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.8.3",
|
||||||
"typescript": "~6.0.3",
|
"typescript": "~6.0.3",
|
||||||
"typescript-eslint": "^8.59.1",
|
"typescript-eslint": "^8.59.1",
|
||||||
@@ -124,7 +124,8 @@
|
|||||||
"resolutions": {
|
"resolutions": {
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"@reduxjs/toolkit": "^2.5.0",
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
"baseline-browser-mapping": "^2.9.19"
|
"baseline-browser-mapping": "^2.9.19",
|
||||||
|
"webpackbar": "^7.0.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
|
||||||
}
|
}
|
||||||
|
|||||||
6
docs/static/feature-flags.json
vendored
6
docs/static/feature-flags.json
vendored
@@ -81,12 +81,6 @@
|
|||||||
"lifecycle": "development",
|
"lifecycle": "development",
|
||||||
"description": "Expand nested types in Presto into extra columns/arrays. Experimental, doesn't work with all nested types."
|
"description": "Expand nested types in Presto into extra columns/arrays. Experimental, doesn't work with all nested types."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "SEMANTIC_LAYERS",
|
|
||||||
"default": false,
|
|
||||||
"lifecycle": "development",
|
|
||||||
"description": "Enable semantic layers and show semantic views alongside datasets"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "TABLE_V2_TIME_COMPARISON_ENABLED",
|
"name": "TABLE_V2_TIME_COMPARISON_ENABLED",
|
||||||
"default": false,
|
"default": false,
|
||||||
|
|||||||
675
docs/yarn.lock
675
docs/yarn.lock
@@ -1570,10 +1570,10 @@
|
|||||||
"@docsearch/core" "4.6.2"
|
"@docsearch/core" "4.6.2"
|
||||||
"@docsearch/css" "4.6.2"
|
"@docsearch/css" "4.6.2"
|
||||||
|
|
||||||
"@docusaurus/babel@3.10.1":
|
"@docusaurus/babel@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.10.1.tgz#2f714f682117658ba43d308e9b35b6a73a105227"
|
resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.10.0.tgz#819819f107233dfcf50b59cd51158f23fb04878a"
|
||||||
integrity sha512-DZzFO1K3v/GoEt1fx1DiYHF4en+PuhtQf1AkQJa5zu3CoeKSpr5cpQRUlz3jr0m44wyzmSXu9bVpfir+N4+8bg==
|
integrity sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.25.9"
|
"@babel/core" "^7.25.9"
|
||||||
"@babel/generator" "^7.25.9"
|
"@babel/generator" "^7.25.9"
|
||||||
@@ -1584,23 +1584,23 @@
|
|||||||
"@babel/preset-typescript" "^7.25.9"
|
"@babel/preset-typescript" "^7.25.9"
|
||||||
"@babel/runtime" "^7.25.9"
|
"@babel/runtime" "^7.25.9"
|
||||||
"@babel/traverse" "^7.25.9"
|
"@babel/traverse" "^7.25.9"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
babel-plugin-dynamic-import-node "^2.3.3"
|
babel-plugin-dynamic-import-node "^2.3.3"
|
||||||
fs-extra "^11.1.1"
|
fs-extra "^11.1.1"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/bundler@3.10.1":
|
"@docusaurus/bundler@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.10.1.tgz#82fa5079f3787a67502e25f82d37d05ec5de0cc3"
|
resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.10.0.tgz#878c4c46bfa3434671ea37a43da184238a6aae26"
|
||||||
integrity sha512-HIqQPvbqnnQRe4NsBd1774KRarjXqS6wHsWELtyuSs1gCfvixJO2jUGH/OEBtr1Gvzpw+ze5CjGMvSJ8UE1KUw==
|
integrity sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.25.9"
|
"@babel/core" "^7.25.9"
|
||||||
"@docusaurus/babel" "3.10.1"
|
"@docusaurus/babel" "3.10.0"
|
||||||
"@docusaurus/cssnano-preset" "3.10.1"
|
"@docusaurus/cssnano-preset" "3.10.0"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
babel-loader "^9.2.1"
|
babel-loader "^9.2.1"
|
||||||
clean-css "^5.3.3"
|
clean-css "^5.3.3"
|
||||||
copy-webpack-plugin "^11.0.0"
|
copy-webpack-plugin "^11.0.0"
|
||||||
@@ -1618,20 +1618,20 @@
|
|||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
url-loader "^4.1.1"
|
url-loader "^4.1.1"
|
||||||
webpack "^5.95.0"
|
webpack "^5.95.0"
|
||||||
webpackbar "^7.0.0"
|
webpackbar "^6.0.1"
|
||||||
|
|
||||||
"@docusaurus/core@3.10.1", "@docusaurus/core@^3.10.1":
|
"@docusaurus/core@3.10.0", "@docusaurus/core@^3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.10.1.tgz#3f8bdb97451b4df14f2a3b39ab0186366fbf8fbe"
|
resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.10.0.tgz#642e71a0209d62c3f5ef275ed9d74a881f40df39"
|
||||||
integrity sha512-3pf2fXXw0eVk8WnC3T4LIigRDupcpvngpKo9Vy7mYyBhuddc0klDUuZAIfzMoK6z05pdlk6EFC/vBSX43+1O5w==
|
integrity sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/babel" "3.10.1"
|
"@docusaurus/babel" "3.10.0"
|
||||||
"@docusaurus/bundler" "3.10.1"
|
"@docusaurus/bundler" "3.10.0"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/mdx-loader" "3.10.1"
|
"@docusaurus/mdx-loader" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
boxen "^6.2.1"
|
boxen "^6.2.1"
|
||||||
chalk "^4.1.2"
|
chalk "^4.1.2"
|
||||||
chokidar "^3.5.3"
|
chokidar "^3.5.3"
|
||||||
@@ -1668,22 +1668,22 @@
|
|||||||
webpack-dev-server "^5.2.2"
|
webpack-dev-server "^5.2.2"
|
||||||
webpack-merge "^6.0.1"
|
webpack-merge "^6.0.1"
|
||||||
|
|
||||||
"@docusaurus/cssnano-preset@3.10.1":
|
"@docusaurus/cssnano-preset@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.1.tgz#4b6bafeca8bb9423364d2fd6683c28e2f85a4665"
|
resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz#be1b435c33df09d743473d3fadda67b4568dfae3"
|
||||||
integrity sha512-eNfHGcTKCSq6xmcavAkX3RRclHaE2xRCMParlDXLdXVP01/a2e/jKXMj/0ULnLFQSNwwuI62L0Ge8J+nZsR7UQ==
|
integrity sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==
|
||||||
dependencies:
|
dependencies:
|
||||||
cssnano-preset-advanced "^6.1.2"
|
cssnano-preset-advanced "^6.1.2"
|
||||||
postcss "^8.5.4"
|
postcss "^8.5.4"
|
||||||
postcss-sort-media-queries "^5.2.0"
|
postcss-sort-media-queries "^5.2.0"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/faster@^3.10.1":
|
"@docusaurus/faster@^3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/faster/-/faster-3.10.1.tgz#a63d89ae980c98e1eeab3ff15ee083f7c20ed353"
|
resolved "https://registry.yarnpkg.com/@docusaurus/faster/-/faster-3.10.0.tgz#0758a93196f685537aa7700bde62faf926e6c817"
|
||||||
integrity sha512-XTZhE5C1gZ/DaYYMlSk02dwP5vhpQON5QHVz1s3892mSESAywgWanURpXEDAvt4GvGuq7s+XP8rTWHZvfaJmdQ==
|
integrity sha512-GNPtVH14ISjHfSwnHu3KiFGf86ICmJSQDeSv/QaanpBgiZGOtgZaslnC5q8WiguxM1EVkwcGxPuD8BXF4eggKw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@rspack/core" "^1.7.10"
|
"@rspack/core" "^1.7.10"
|
||||||
"@swc/core" "^1.7.39"
|
"@swc/core" "^1.7.39"
|
||||||
"@swc/html" "^1.13.5"
|
"@swc/html" "^1.13.5"
|
||||||
@@ -1694,22 +1694,22 @@
|
|||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
webpack "^5.95.0"
|
webpack "^5.95.0"
|
||||||
|
|
||||||
"@docusaurus/logger@3.10.1":
|
"@docusaurus/logger@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.10.1.tgz#34c964e32e18f120e30f80171a38cfefe72cfb4b"
|
resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.10.0.tgz#2bacbd004dd78e3da926dbe8f6fa9a930856575d"
|
||||||
integrity sha512-oPjNFnfJsRCkePVjkGrxWGq4MvJKRQT0r9jOP0eRBTZ7Wr9FAbzdP/Gjs0I2Ss6YRkPoEgygKG112OkE6skvJw==
|
integrity sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^4.1.2"
|
chalk "^4.1.2"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/mdx-loader@3.10.1":
|
"@docusaurus/mdx-loader@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.10.1.tgz#050ae9bc614158a4ec07a628aa75fa9ae90d7e82"
|
resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.10.0.tgz#1d4b050d751389ecf38dee48bcb61e53df8ffb82"
|
||||||
integrity sha512-GRmeb/wQ+iXRrFwcHBfgQhrJxGElgCsoTWZYDhccjsZVne1p8MK/EpQVIloXttz76TCe78kKD5AEG9n1xc1oxQ==
|
integrity sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
"@mdx-js/mdx" "^3.0.0"
|
"@mdx-js/mdx" "^3.0.0"
|
||||||
"@slorber/remark-comment" "^1.0.0"
|
"@slorber/remark-comment" "^1.0.0"
|
||||||
escape-html "^1.0.3"
|
escape-html "^1.0.3"
|
||||||
@@ -1732,12 +1732,12 @@
|
|||||||
vfile "^6.0.1"
|
vfile "^6.0.1"
|
||||||
webpack "^5.88.1"
|
webpack "^5.88.1"
|
||||||
|
|
||||||
"@docusaurus/module-type-aliases@3.10.1", "@docusaurus/module-type-aliases@^3.10.1":
|
"@docusaurus/module-type-aliases@3.10.0", "@docusaurus/module-type-aliases@^3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.1.tgz#22d39177c296786eb6e0d940699cd590cc93ca77"
|
resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.0.tgz#749928f104d563f11f046bf0c9ab6489a470c7c8"
|
||||||
integrity sha512-YoOZKUdGlp8xSYhuAkGdSo5Ydkbq4V4eK3sD8v0a2hloxCWdQbNBhkc+Ko9QyjpESc0BYcIGM5iHVAy5hdFV6w==
|
integrity sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@types/history" "^4.7.11"
|
"@types/history" "^4.7.11"
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
"@types/react-router-config" "*"
|
"@types/react-router-config" "*"
|
||||||
@@ -1745,34 +1745,34 @@
|
|||||||
react-helmet-async "npm:@slorber/react-helmet-async@1.3.0"
|
react-helmet-async "npm:@slorber/react-helmet-async@1.3.0"
|
||||||
react-loadable "npm:@docusaurus/react-loadable@6.0.0"
|
react-loadable "npm:@docusaurus/react-loadable@6.0.0"
|
||||||
|
|
||||||
"@docusaurus/plugin-client-redirects@^3.10.1":
|
"@docusaurus/plugin-client-redirects@^3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.10.1.tgz#e22ed20e5837b7c3a28258e3d1816c4239c82b36"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.10.0.tgz#4dd4619817fd69462d1e6d986580343aeb911111"
|
||||||
integrity sha512-LHgd+YDvkhfOHMAE6XtUng3DQNzVM765RqVRrMJgHtzAvfopQhY6ieprqjxDVBdv21cLma6I0jHr+YCZH8fL9A==
|
integrity sha512-P+VLoLoZTc74so8+IbsaPZ33/mkf2BWL1CYXQpPRkl0v1QVCN2CgfsZY/8QtbYjQnx2upXUnv45abDhNcSggNw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
eta "^2.2.0"
|
eta "^2.2.0"
|
||||||
fs-extra "^11.1.1"
|
fs-extra "^11.1.1"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/plugin-content-blog@3.10.1":
|
"@docusaurus/plugin-content-blog@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.1.tgz#0bd8de700ccbd8e95d920df2613304ef59abe72b"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.0.tgz#10095291b637440847854ecb2c8afcd8746debd7"
|
||||||
integrity sha512-mmkgE6Q2+K74tnkou7tXlpDLvoCU/qkSa2GSQ3XUiHWvcebCoDQzS670RR3tO8PmaWlIyWWISYWzZLuMfxunRA==
|
integrity sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/mdx-loader" "3.10.1"
|
"@docusaurus/mdx-loader" "3.10.0"
|
||||||
"@docusaurus/theme-common" "3.10.1"
|
"@docusaurus/theme-common" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
cheerio "1.0.0-rc.12"
|
cheerio "1.0.0-rc.12"
|
||||||
combine-promises "^1.1.0"
|
combine-promises "^1.1.0"
|
||||||
feed "^4.2.2"
|
feed "^4.2.2"
|
||||||
@@ -1785,20 +1785,20 @@
|
|||||||
utility-types "^3.10.0"
|
utility-types "^3.10.0"
|
||||||
webpack "^5.88.1"
|
webpack "^5.88.1"
|
||||||
|
|
||||||
"@docusaurus/plugin-content-docs@3.10.1":
|
"@docusaurus/plugin-content-docs@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.1.tgz#261e0e982e4a937c05b462e3c5729374f433b752"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.0.tgz#9c4ea1d5a405340f28c281d2e4586c695a7c65a5"
|
||||||
integrity sha512-2jRVrtzjf8LClGTHQlwlwuD3wQXRx3WEoF7XUarJ8Ou+0onV+SLtejsyfY9JLpfUh9hPhXM4pbBGkyAY4Bi3HQ==
|
integrity sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/mdx-loader" "3.10.1"
|
"@docusaurus/mdx-loader" "3.10.0"
|
||||||
"@docusaurus/module-type-aliases" "3.10.1"
|
"@docusaurus/module-type-aliases" "3.10.0"
|
||||||
"@docusaurus/theme-common" "3.10.1"
|
"@docusaurus/theme-common" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
"@types/react-router-config" "^5.0.7"
|
"@types/react-router-config" "^5.0.7"
|
||||||
combine-promises "^1.1.0"
|
combine-promises "^1.1.0"
|
||||||
fs-extra "^11.1.1"
|
fs-extra "^11.1.1"
|
||||||
@@ -1809,142 +1809,142 @@
|
|||||||
utility-types "^3.10.0"
|
utility-types "^3.10.0"
|
||||||
webpack "^5.88.1"
|
webpack "^5.88.1"
|
||||||
|
|
||||||
"@docusaurus/plugin-content-pages@3.10.1":
|
"@docusaurus/plugin-content-pages@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.1.tgz#8c6ffc2079ed0262548ecc4df1dea6add6aa9673"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.0.tgz#7670cbb3c849f434949f542bfdfded1580a13165"
|
||||||
integrity sha512-huJpaRPMl42nsFwuCXvV8bVDj2MazuwRJIUylI/RSlmZeJssVoZXeCjVf1y+1Drtpa9SKcdGn8yoJ76IRJijtw==
|
integrity sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/mdx-loader" "3.10.1"
|
"@docusaurus/mdx-loader" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
fs-extra "^11.1.1"
|
fs-extra "^11.1.1"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
webpack "^5.88.1"
|
webpack "^5.88.1"
|
||||||
|
|
||||||
"@docusaurus/plugin-css-cascade-layers@3.10.1":
|
"@docusaurus/plugin-css-cascade-layers@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.1.tgz#440578d95cbe1a6120936fa83df868d2626cd1d8"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.0.tgz#71e318d842be95f92be6c3dca00ceea4971d0edb"
|
||||||
integrity sha512-r//fn+MNHkE1wCof8T29VAQezt1enGCpsFxoziBbvLgBM4JfXN2P3rxrBaavHmvLvm7lYkpJeitcDthwnmWCTw==
|
integrity sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/plugin-debug@3.10.1":
|
"@docusaurus/plugin-debug@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.10.1.tgz#b8b7b24d9a7d185fd8a56a030f90145d3bfd8239"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.10.0.tgz#e77f924604e1e09d5d90fe0bdf23a3be8ea3307e"
|
||||||
integrity sha512-9KqOpKNfAyqGZykRb9LhIT/vyRF6sm/ykhjj/39JvaJahDS+jZJE0Z1Wfz9q3DUNDTMNN0Q7u/kk4rKKU+IJuA==
|
integrity sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
fs-extra "^11.1.1"
|
fs-extra "^11.1.1"
|
||||||
react-json-view-lite "^2.3.0"
|
react-json-view-lite "^2.3.0"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/plugin-google-analytics@3.10.1":
|
"@docusaurus/plugin-google-analytics@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.1.tgz#ac15afc77386e0352edb8a1698d993aa5de36ffc"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.0.tgz#22c7e976fe4d970c7cd1c73c9723d9a5786c6e37"
|
||||||
integrity sha512-8o0P1KtmgdYQHH+oInitPpRWI0Of5XednAX4+DMhQNSmGSRNrsEEHg1ebv35m9AgRClfAytCJ5jA9KvcASTyuA==
|
integrity sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/plugin-google-gtag@3.10.1":
|
"@docusaurus/plugin-google-gtag@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.1.tgz#0482b83b9bc411aa99a432be2b39d2e53a00e2e0"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.0.tgz#c38a2ba638257851cc845b934506b80c08d47f96"
|
||||||
integrity sha512-pu3xIUo5o/zCMLfUY9BO5KOwSH0zIsAGyFRPvXHayFSA5XIhCU/SFuB0g0ZNjFn9niZLCaNvoeAuOGFJZq0fdw==
|
integrity sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
"@types/gtag.js" "^0.0.20"
|
"@types/gtag.js" "^0.0.20"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/plugin-google-tag-manager@3.10.1":
|
"@docusaurus/plugin-google-tag-manager@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.1.tgz#eaf5765d6f82b4fb661d92a793d1883f9d1ec106"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.0.tgz#5469c923cc1ad4608399d0b17e5fcacd8e030d56"
|
||||||
integrity sha512-f6fyGHiCm7kJHBtAisGQS5oNBnpnMTYQZxDXeVrnw/3zWU+LMA22pr6UHGYkBKDbN+qPC5QHG3NuOfzQLq3+Lw==
|
integrity sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/plugin-sitemap@3.10.1":
|
"@docusaurus/plugin-sitemap@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.1.tgz#66a6974bb2fd1b9d8f5cb0f3c5ecd2201c118565"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.0.tgz#35d59d46803f279f22aa64fc1bd18c048f12662b"
|
||||||
integrity sha512-C26MbmmqgdjkDq1htaZ3aD7LzEDKFWXfpyQpt0EOUThuq5nV77zDaedV20yHcVo9p+3ey9aZ4pbHA0D3QcZTzg==
|
integrity sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
fs-extra "^11.1.1"
|
fs-extra "^11.1.1"
|
||||||
sitemap "^7.1.1"
|
sitemap "^7.1.1"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/plugin-svgr@3.10.1":
|
"@docusaurus/plugin-svgr@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.1.tgz#c217c24d6d23fd2bc6f54d44c040635b49d6b36e"
|
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.0.tgz#8ada2e6dd8318d20206a9b044fc091a5794ba3f0"
|
||||||
integrity sha512-6SFxsmjWFkVLDmBUvFK6i72QjUwqyQFe4Ovz+SUJophJjOyVG3ZZG5IQpBC/kX/Gfv1yWeU9nWauH6F6Q7QX/Q==
|
integrity sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
"@svgr/core" "8.1.0"
|
"@svgr/core" "8.1.0"
|
||||||
"@svgr/webpack" "^8.1.0"
|
"@svgr/webpack" "^8.1.0"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
webpack "^5.88.1"
|
webpack "^5.88.1"
|
||||||
|
|
||||||
"@docusaurus/preset-classic@3.10.1":
|
"@docusaurus/preset-classic@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.10.1.tgz#faf330d96aedc9083a59bec09d966ae4dfc8b2fb"
|
resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.10.0.tgz#74b6facdaf568bcd41ec90cae9aebb7ca0ac8619"
|
||||||
integrity sha512-YO/FL8v1zmbxoTso6mjMz/RDjhaTJxb1UpFFTDdY5847LLDCeyYiYlrhyTbgN1RIN3xnkLKZ9Lj1x8hUzI4JOg==
|
integrity sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/plugin-content-blog" "3.10.1"
|
"@docusaurus/plugin-content-blog" "3.10.0"
|
||||||
"@docusaurus/plugin-content-docs" "3.10.1"
|
"@docusaurus/plugin-content-docs" "3.10.0"
|
||||||
"@docusaurus/plugin-content-pages" "3.10.1"
|
"@docusaurus/plugin-content-pages" "3.10.0"
|
||||||
"@docusaurus/plugin-css-cascade-layers" "3.10.1"
|
"@docusaurus/plugin-css-cascade-layers" "3.10.0"
|
||||||
"@docusaurus/plugin-debug" "3.10.1"
|
"@docusaurus/plugin-debug" "3.10.0"
|
||||||
"@docusaurus/plugin-google-analytics" "3.10.1"
|
"@docusaurus/plugin-google-analytics" "3.10.0"
|
||||||
"@docusaurus/plugin-google-gtag" "3.10.1"
|
"@docusaurus/plugin-google-gtag" "3.10.0"
|
||||||
"@docusaurus/plugin-google-tag-manager" "3.10.1"
|
"@docusaurus/plugin-google-tag-manager" "3.10.0"
|
||||||
"@docusaurus/plugin-sitemap" "3.10.1"
|
"@docusaurus/plugin-sitemap" "3.10.0"
|
||||||
"@docusaurus/plugin-svgr" "3.10.1"
|
"@docusaurus/plugin-svgr" "3.10.0"
|
||||||
"@docusaurus/theme-classic" "3.10.1"
|
"@docusaurus/theme-classic" "3.10.0"
|
||||||
"@docusaurus/theme-common" "3.10.1"
|
"@docusaurus/theme-common" "3.10.0"
|
||||||
"@docusaurus/theme-search-algolia" "3.10.1"
|
"@docusaurus/theme-search-algolia" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
|
|
||||||
"@docusaurus/theme-classic@3.10.1":
|
"@docusaurus/theme-classic@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.10.1.tgz#deed8cf73cc0f56113e53775cbb3b168c3c61566"
|
resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.10.0.tgz#d937915c691189f27ced649c822994d839ea565b"
|
||||||
integrity sha512-VU1RK0qb2pab0si4r7HFK37cYco8VzqLj3u1PspVipSr/z/GPVKHO4/HXbnePqHoWDk8urjyGSeatH0NIMBM1A==
|
integrity sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/mdx-loader" "3.10.1"
|
"@docusaurus/mdx-loader" "3.10.0"
|
||||||
"@docusaurus/module-type-aliases" "3.10.1"
|
"@docusaurus/module-type-aliases" "3.10.0"
|
||||||
"@docusaurus/plugin-content-blog" "3.10.1"
|
"@docusaurus/plugin-content-blog" "3.10.0"
|
||||||
"@docusaurus/plugin-content-docs" "3.10.1"
|
"@docusaurus/plugin-content-docs" "3.10.0"
|
||||||
"@docusaurus/plugin-content-pages" "3.10.1"
|
"@docusaurus/plugin-content-pages" "3.10.0"
|
||||||
"@docusaurus/theme-common" "3.10.1"
|
"@docusaurus/theme-common" "3.10.0"
|
||||||
"@docusaurus/theme-translations" "3.10.1"
|
"@docusaurus/theme-translations" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
"@mdx-js/react" "^3.0.0"
|
"@mdx-js/react" "^3.0.0"
|
||||||
clsx "^2.0.0"
|
clsx "^2.0.0"
|
||||||
copy-text-to-clipboard "^3.2.0"
|
copy-text-to-clipboard "^3.2.0"
|
||||||
@@ -1959,15 +1959,15 @@
|
|||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
utility-types "^3.10.0"
|
utility-types "^3.10.0"
|
||||||
|
|
||||||
"@docusaurus/theme-common@3.10.1":
|
"@docusaurus/theme-common@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.10.1.tgz#cbfec82b1b107be5c229811ed9caae14a501361c"
|
resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.10.0.tgz#70b419ccfdf62f092299354a72d1692e81be597d"
|
||||||
integrity sha512-0YtmIeoNo1fIw65LO8+/1dPgmDV86UmhMkow37gzjytuiCSQm9xob6PJy0L4kuQEMTLfUOGvkXvZr7GPrHquMA==
|
integrity sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/mdx-loader" "3.10.1"
|
"@docusaurus/mdx-loader" "3.10.0"
|
||||||
"@docusaurus/module-type-aliases" "3.10.1"
|
"@docusaurus/module-type-aliases" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
"@types/history" "^4.7.11"
|
"@types/history" "^4.7.11"
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
"@types/react-router-config" "*"
|
"@types/react-router-config" "*"
|
||||||
@@ -1977,48 +1977,48 @@
|
|||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
utility-types "^3.10.0"
|
utility-types "^3.10.0"
|
||||||
|
|
||||||
"@docusaurus/theme-live-codeblock@^3.10.1":
|
"@docusaurus/theme-live-codeblock@^3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-3.10.1.tgz#29e6ddee467d816205ad611fd7bf10f00db5bdef"
|
resolved "https://registry.yarnpkg.com/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-3.10.0.tgz#05a38c6bfac479fd698f18f27ca06ebb126633d9"
|
||||||
integrity sha512-MKG/0zreelS6YlupQAoKmS5nCw9RRKwDHihJg2FinsU1+rqbrOYNYVq//eQy+m649k9b8XCazEw9VUMTFhpCTg==
|
integrity sha512-1Ycxu0dBAhEXzXPQ1dQW01aY1MNi7TCTUOBtIF0GcNrQBFj74XxhDqv/T6GxYBsaN+6QnIDs1T+D43iV2/r2hQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/theme-common" "3.10.1"
|
"@docusaurus/theme-common" "3.10.0"
|
||||||
"@docusaurus/theme-translations" "3.10.1"
|
"@docusaurus/theme-translations" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
"@philpl/buble" "^0.19.7"
|
"@philpl/buble" "^0.19.7"
|
||||||
clsx "^2.0.0"
|
clsx "^2.0.0"
|
||||||
fs-extra "^11.1.1"
|
fs-extra "^11.1.1"
|
||||||
react-live "^4.1.6"
|
react-live "^4.1.6"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/theme-mermaid@^3.10.1":
|
"@docusaurus/theme-mermaid@^3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.1.tgz#dada9c50c780524d246906234ace8a35446f26fc"
|
resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.0.tgz#6581ccf16d27e4c02fe8c7cf15488862f27be9c8"
|
||||||
integrity sha512-2gxpmln8Pc4EN1oWzshQEx2HTs67jk14v7MmgqGs8ZU7Nm8oihg+fTouof2u4vN8DtB3Fln4cDJu4UprSX1S3Q==
|
integrity sha512-Y2xrlwhIJ80oOZIO3PXL6A7J869splfcMI87E3NKpYsy3zJxOyV+BP1QMtGi59ajKgU868HPuyyn6J+6BZGOBg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/module-type-aliases" "3.10.1"
|
"@docusaurus/module-type-aliases" "3.10.0"
|
||||||
"@docusaurus/theme-common" "3.10.1"
|
"@docusaurus/theme-common" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
mermaid ">=11.6.0"
|
mermaid ">=11.6.0"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/theme-search-algolia@3.10.1":
|
"@docusaurus/theme-search-algolia@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.1.tgz#6f422058711629ce8d7c2f17e1e54efa075c626e"
|
resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.0.tgz#0ff57fe58db6abde8f5ad2877e459cd2fa6e7464"
|
||||||
integrity sha512-OTaARARVZj2GvkJQjB+1jOIxntRaXea+G+fMsNqrZBAU1O1vJKDW22R7kECOHW27oJCLFN9HKaZeRrfAUyviug==
|
integrity sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@algolia/autocomplete-core" "^1.19.2"
|
"@algolia/autocomplete-core" "^1.19.2"
|
||||||
"@docsearch/react" "^3.9.0 || ^4.3.2"
|
"@docsearch/react" "^3.9.0 || ^4.3.2"
|
||||||
"@docusaurus/core" "3.10.1"
|
"@docusaurus/core" "3.10.0"
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/plugin-content-docs" "3.10.1"
|
"@docusaurus/plugin-content-docs" "3.10.0"
|
||||||
"@docusaurus/theme-common" "3.10.1"
|
"@docusaurus/theme-common" "3.10.0"
|
||||||
"@docusaurus/theme-translations" "3.10.1"
|
"@docusaurus/theme-translations" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-validation" "3.10.1"
|
"@docusaurus/utils-validation" "3.10.0"
|
||||||
algoliasearch "^5.37.0"
|
algoliasearch "^5.37.0"
|
||||||
algoliasearch-helper "^3.26.0"
|
algoliasearch-helper "^3.26.0"
|
||||||
clsx "^2.0.0"
|
clsx "^2.0.0"
|
||||||
@@ -2028,23 +2028,23 @@
|
|||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
utility-types "^3.10.0"
|
utility-types "^3.10.0"
|
||||||
|
|
||||||
"@docusaurus/theme-translations@3.10.1":
|
"@docusaurus/theme-translations@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.10.1.tgz#c3119a015652290eea560ca45ac775963d6eb75b"
|
resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.10.0.tgz#8fdc23d29bd7f907db49c36cf65e2123d96be300"
|
||||||
integrity sha512-cLMyaKivjBVWKMJuWqyFVVgtqe8DPJNPkog0bn8W1MDVAKcPdxRFycBfC1We1RaNp7Rdk513bmtW78RR6OBxBw==
|
integrity sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==
|
||||||
dependencies:
|
dependencies:
|
||||||
fs-extra "^11.1.1"
|
fs-extra "^11.1.1"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/tsconfig@^3.10.1":
|
"@docusaurus/tsconfig@^3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.10.1.tgz#1db31b4a4a5c914bdffa80070a35b6365d34f2e8"
|
resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.10.0.tgz#f40a57248828f0503a5f355cf30aa59941c9baaa"
|
||||||
integrity sha512-rYvB7yqkdqWIpAbDzQljGfM4cDBkLTbhmagZBEcsyj6oPUsz47lmW2pYdN1j+7sGFgltbAmQH62xfbrij4Eh6Q==
|
integrity sha512-TXdC3WXuPrdQAexLvjUJfnYf3YKEgEqAs5nK0Q88pRBCW7t7oN4ILvWYb3A5Z1wlSXyXGWW/mCUmLEhdWsjnDQ==
|
||||||
|
|
||||||
"@docusaurus/types@3.10.1":
|
"@docusaurus/types@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.10.1.tgz#d42837938ae43ca2be0ca47e63e00476b5eb94be"
|
resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.10.0.tgz#a69232bba74b738fcf4671fd5f0f079366dd3d13"
|
||||||
integrity sha512-XYMK8k1szDCFMw2V+Xyen0g7Kee1sP3dtFnl7vkGkZOkeAJ/oPDQPL8iz4HBKOo/cwU8QeV6onVjMqtP+tFzsw==
|
integrity sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@mdx-js/mdx" "^3.0.0"
|
"@mdx-js/mdx" "^3.0.0"
|
||||||
"@types/history" "^4.7.11"
|
"@types/history" "^4.7.11"
|
||||||
@@ -2057,36 +2057,36 @@
|
|||||||
webpack "^5.95.0"
|
webpack "^5.95.0"
|
||||||
webpack-merge "^5.9.0"
|
webpack-merge "^5.9.0"
|
||||||
|
|
||||||
"@docusaurus/utils-common@3.10.1":
|
"@docusaurus/utils-common@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.10.1.tgz#6350b4898691e765de750f90eade0e0fa7902d99"
|
resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.10.0.tgz#2a6dc76b312664fca7234d33607c085318ff1ae3"
|
||||||
integrity sha512-5mFSgEADtnFxFH7RLw02QA5MpU5JVUCj0MPeIvi/aF4Fi45tQRIuTwXoXDqJ+1VfQJuYJGz3SI63wmGz4HvXzA==
|
integrity sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/utils-validation@3.10.1":
|
"@docusaurus/utils-validation@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.10.1.tgz#ddbcce997a5506424cdd16abf6845cc51692acae"
|
resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.10.0.tgz#a2418d7f31980d991fd3a1f39c8aad8820b36812"
|
||||||
integrity sha512-cRv1X69jwaWv47waglllgZVWzeBFLhl53XT/XED/83BerVBTC5FTP8WTcVl8Z6sZOegDSwitu/wpCSPCDOT6lg==
|
integrity sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/utils" "3.10.1"
|
"@docusaurus/utils" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
fs-extra "^11.2.0"
|
fs-extra "^11.2.0"
|
||||||
joi "^17.9.2"
|
joi "^17.9.2"
|
||||||
js-yaml "^4.1.0"
|
js-yaml "^4.1.0"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
tslib "^2.6.0"
|
tslib "^2.6.0"
|
||||||
|
|
||||||
"@docusaurus/utils@3.10.1":
|
"@docusaurus/utils@3.10.0":
|
||||||
version "3.10.1"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.10.1.tgz#535968caa2c9bff69f997a081b98b95b3c5d3785"
|
resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.10.0.tgz#ea7d7b0d325b60f728decc00bb3908d00ef86faf"
|
||||||
integrity sha512-3ojeJry9xBYdJO6qoyyzqeJFSJBVx2mXhyDzSdjwL2+URFQMf+h25gG38iswGImicK0ELjTd1EL2xzk8hf3QPw==
|
integrity sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@docusaurus/logger" "3.10.1"
|
"@docusaurus/logger" "3.10.0"
|
||||||
"@docusaurus/types" "3.10.1"
|
"@docusaurus/types" "3.10.0"
|
||||||
"@docusaurus/utils-common" "3.10.1"
|
"@docusaurus/utils-common" "3.10.0"
|
||||||
escape-string-regexp "^4.0.0"
|
escape-string-regexp "^4.0.0"
|
||||||
execa "^5.1.1"
|
execa "^5.1.1"
|
||||||
file-loader "^6.2.0"
|
file-loader "^6.2.0"
|
||||||
@@ -4239,86 +4239,86 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
apg-lite "^1.0.4"
|
apg-lite "^1.0.4"
|
||||||
|
|
||||||
"@swc/core-darwin-arm64@1.15.33":
|
"@swc/core-darwin-arm64@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.33.tgz#d84134fb80417d41128739f0b9014542e3ed9dd3"
|
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.32.tgz#3592714588fdbb8b7a869f81ff96c7236fcf1c09"
|
||||||
integrity sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==
|
integrity sha512-/YWMvJDPu+AAwuUsM2G+DNQ/7zhodURGzdQyewEqcvgklAdDHs3LwQmLLnyn6SJl8DT8UOxkbzK+D1PmPeelRg==
|
||||||
|
|
||||||
"@swc/core-darwin-x64@1.15.33":
|
"@swc/core-darwin-x64@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.33.tgz#0badb9834071f1c6005986571d4a96359c1d7cd0"
|
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.32.tgz#965044b632933146e319862ea7e4b717eb9f83dd"
|
||||||
integrity sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==
|
integrity sha512-KOTXJXdAhWL+hZ77MYP3z+4pcMFaQhQ74yqyN1uz093q0YnbxpqMtYpPISbYvMHzVRNNx5kN+9RZAXEaadhWVA==
|
||||||
|
|
||||||
"@swc/core-linux-arm-gnueabihf@1.15.33":
|
"@swc/core-linux-arm-gnueabihf@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.33.tgz#b7577a825b59d98b6a9a5c991d842046efe1c34a"
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.32.tgz#70e70ad6ad961055f4a9be9e4947e455c18239e6"
|
||||||
integrity sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==
|
integrity sha512-oOoxLweljlc0A4X8ybsgxV7cVaYTwBOg2iMDJcFR3Sr48C+lsv9VzSmqdK/IVIXF4W4GjLc3VqTAdSMXlfVLuQ==
|
||||||
|
|
||||||
"@swc/core-linux-arm64-gnu@1.15.33":
|
"@swc/core-linux-arm64-gnu@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.33.tgz#304c48321494a18c67b2913c273b08674ee70d8c"
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.32.tgz#7b82e2cc5995e8f919e29f6ce702285f5f1c3ad1"
|
||||||
integrity sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==
|
integrity sha512-oDzEkdl6D6BAWdMtU5KGO7y3HR5fJcvByNLyEk9+ugj8nP5Ovb7P4kBcStBXc4MPExFGQryehiINMlmY8HlclA==
|
||||||
|
|
||||||
"@swc/core-linux-arm64-musl@1.15.33":
|
"@swc/core-linux-arm64-musl@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.33.tgz#d116cbc04ccb4f4ee810da6bca79d4423605dbcd"
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.32.tgz#16c581b9f859b0175a8bab5cbf694bef7dbf95b8"
|
||||||
integrity sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==
|
integrity sha512-omcqjoZP/b8D8PuczVoRwJieC6ibj7qIxTftNYokz4/aSmKFHvsd7nIFfPk5ZvtzncbH4AY7+Dkr/Lp2gWxYeA==
|
||||||
|
|
||||||
"@swc/core-linux-ppc64-gnu@1.15.33":
|
"@swc/core-linux-ppc64-gnu@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.33.tgz#f5354dba36db9414305bab344c817d57b8b457c2"
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.32.tgz#420f7744dae327c8e4917c87ced5c1b3e0a38f96"
|
||||||
integrity sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==
|
integrity sha512-KGkTMyz/Tbn3PBNu0AVZ4GTDFKnICrYcTiNPZq8DrvK42pnFsf3GNDrIG9E5AtQlTmC0YigkWKmu0eMcfTrmgA==
|
||||||
|
|
||||||
"@swc/core-linux-s390x-gnu@1.15.33":
|
"@swc/core-linux-s390x-gnu@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.33.tgz#016df9f4c9d7fd65b85ca9c558c5aec341f06da0"
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.32.tgz#9b563a3a73c544f29454e53894bfe533b9a27ffe"
|
||||||
integrity sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==
|
integrity sha512-G3Aa4tVS/3OGZBkoNIwUF9F6RAy+Osb4GOlo62SinLmDiErz/ykmM7KH0wkz6l9kM8jJq1HyAM6atJTUEbBk7g==
|
||||||
|
|
||||||
"@swc/core-linux-x64-gnu@1.15.33":
|
"@swc/core-linux-x64-gnu@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.33.tgz#49f36558ede072e71999aa37f123367daed2a662"
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.32.tgz#615c7bcc1890379dffcc74b6780e2277e65f4b61"
|
||||||
integrity sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==
|
integrity sha512-ERsjfGcj6CBmj3vJnGDO8m8rTvw6RqMcWo1dogOtNx3/+/0+NNpJiXDobJrr1GwInI/BHAEkvSFIH6d2LqPcUQ==
|
||||||
|
|
||||||
"@swc/core-linux-x64-musl@1.15.33":
|
"@swc/core-linux-x64-musl@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.33.tgz#b096665f5cfeee2612325f301da5c1590b10d8f3"
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.32.tgz#038604d25bdebb1d1ad780d827a44654fa4b5bdd"
|
||||||
integrity sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==
|
integrity sha512-N4Ggahe/8SUbTX50P6EdhbW9YWcgbZVb52R4cq6MK+zsoMjRq7rGvV5ztA05QnbaCYqMYx8rTY7KAIA3Crdo4Q==
|
||||||
|
|
||||||
"@swc/core-win32-arm64-msvc@1.15.33":
|
"@swc/core-win32-arm64-msvc@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.33.tgz#f3101263a0dbaa173ec47638c9719d0b89838bd2"
|
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.32.tgz#c82006e6ef92a998e96d2160b1657f5334af4d54"
|
||||||
integrity sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==
|
integrity sha512-01yN0o9jvo8xBTP12aPK2wW8b41jmOlGbDDlAnoynotc4pO6xA0zby9f1z6j++qXDpGBttLySq1omgVrlQKYcw==
|
||||||
|
|
||||||
"@swc/core-win32-ia32-msvc@1.15.33":
|
"@swc/core-win32-ia32-msvc@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.33.tgz#eb981ef5613d42c9220559bdb0c8bc58cf6c3eb9"
|
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.32.tgz#e2ae1c95bd6599322bc6e9a82685b7537a193f7b"
|
||||||
integrity sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==
|
integrity sha512-fLagI9XZYNpTcmlqAcp3KBtmj7E19WCmYD80Jxj1Kn5tGNa7yxNLd3NNdWxuZGUPl5iC0/KqZru7g08gF6Fsrw==
|
||||||
|
|
||||||
"@swc/core-win32-x64-msvc@1.15.33":
|
"@swc/core-win32-x64-msvc@1.15.32":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.33.tgz#a2fed9956933027ceb368857bac4bb4ee203d47c"
|
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.32.tgz#2535c791821054072a511dee0d13e5de9c5cd29b"
|
||||||
integrity sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==
|
integrity sha512-gbc2bQ/T2CiR+w0OvcVKwLOFAcPZBvmWmolbwpg1E8UrpeC03DGtyMUApOHNXNYWA3SHFrYXCQtosrcMza1YFg==
|
||||||
|
|
||||||
"@swc/core@^1.15.33", "@swc/core@^1.7.39":
|
"@swc/core@^1.15.32", "@swc/core@^1.7.39":
|
||||||
version "1.15.33"
|
version "1.15.32"
|
||||||
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.33.tgz#2a6571c8aca961925f14beae52b3f43c18370fc6"
|
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.32.tgz#2333d66f4b8e7c4fded087ead13c135ff84ab9d6"
|
||||||
integrity sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==
|
integrity sha512-/eWL0n43D64QWEUHLtTE+jDqjkJhyidjkDhv6f0uJohOUAhywxQ9wXYp845DNNds0JpCdI4Uo0a9bl+vbXf+ew==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@swc/counter" "^0.1.3"
|
"@swc/counter" "^0.1.3"
|
||||||
"@swc/types" "^0.1.26"
|
"@swc/types" "^0.1.26"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@swc/core-darwin-arm64" "1.15.33"
|
"@swc/core-darwin-arm64" "1.15.32"
|
||||||
"@swc/core-darwin-x64" "1.15.33"
|
"@swc/core-darwin-x64" "1.15.32"
|
||||||
"@swc/core-linux-arm-gnueabihf" "1.15.33"
|
"@swc/core-linux-arm-gnueabihf" "1.15.32"
|
||||||
"@swc/core-linux-arm64-gnu" "1.15.33"
|
"@swc/core-linux-arm64-gnu" "1.15.32"
|
||||||
"@swc/core-linux-arm64-musl" "1.15.33"
|
"@swc/core-linux-arm64-musl" "1.15.32"
|
||||||
"@swc/core-linux-ppc64-gnu" "1.15.33"
|
"@swc/core-linux-ppc64-gnu" "1.15.32"
|
||||||
"@swc/core-linux-s390x-gnu" "1.15.33"
|
"@swc/core-linux-s390x-gnu" "1.15.32"
|
||||||
"@swc/core-linux-x64-gnu" "1.15.33"
|
"@swc/core-linux-x64-gnu" "1.15.32"
|
||||||
"@swc/core-linux-x64-musl" "1.15.33"
|
"@swc/core-linux-x64-musl" "1.15.32"
|
||||||
"@swc/core-win32-arm64-msvc" "1.15.33"
|
"@swc/core-win32-arm64-msvc" "1.15.32"
|
||||||
"@swc/core-win32-ia32-msvc" "1.15.33"
|
"@swc/core-win32-ia32-msvc" "1.15.32"
|
||||||
"@swc/core-win32-x64-msvc" "1.15.33"
|
"@swc/core-win32-x64-msvc" "1.15.32"
|
||||||
|
|
||||||
"@swc/counter@^0.1.3":
|
"@swc/counter@^0.1.3":
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
@@ -5794,10 +5794,10 @@ base64-js@^1.3.1, base64-js@^1.5.1:
|
|||||||
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
|
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
|
||||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||||
|
|
||||||
baseline-browser-mapping@^2.10.27, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
|
baseline-browser-mapping@^2.10.24, baseline-browser-mapping@^2.9.0, baseline-browser-mapping@^2.9.19:
|
||||||
version "2.10.27"
|
version "2.10.24"
|
||||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz#fee941c2a0b42cdf83c6427e4c830b1d0bdab2c3"
|
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz#6dc320c7bf53859ec2bf55d54db6d2e5c078df16"
|
||||||
integrity sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==
|
integrity sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==
|
||||||
|
|
||||||
batch@0.6.1:
|
batch@0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
@@ -6062,7 +6062,12 @@ chalk@^4.0.0, chalk@^4.1.2:
|
|||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
chalk@^5.0.1, chalk@^5.2.0, chalk@^5.6.2:
|
chalk@^5.0.1, chalk@^5.2.0:
|
||||||
|
version "5.6.0"
|
||||||
|
resolved "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz"
|
||||||
|
integrity sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==
|
||||||
|
|
||||||
|
chalk@^5.6.2:
|
||||||
version "5.6.2"
|
version "5.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea"
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea"
|
||||||
integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==
|
integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==
|
||||||
@@ -7300,10 +7305,10 @@ doctrine@^2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
|
|
||||||
docusaurus-plugin-openapi-docs@^5.0.2:
|
docusaurus-plugin-openapi-docs@^5.0.1:
|
||||||
version "5.0.2"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.0.2.tgz#f00028621deb9179065fe7d6a541256692ef941b"
|
resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.0.1.tgz#2fe62b58fc1af11e3d947edc2f0d60e04f1aa149"
|
||||||
integrity sha512-WCC2m6PpylXZfNga+ScelTG0a7jUGtbB9+AmbR9lUj93FPryTs8VHTMJ3fKtO0senJTWgOU3MDvZw0v+mE3ztA==
|
integrity sha512-OVfoDovRdiS78DQYWmr2BjuOF2A6kVmJ43mgkQaAEZxASyHbUft4zUIhvfa7gqema6KNL9pVKejDievZdZ3wGQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@apidevtools/json-schema-ref-parser" "^15.3.3"
|
"@apidevtools/json-schema-ref-parser" "^15.3.3"
|
||||||
"@redocly/openapi-core" "^2.25.2"
|
"@redocly/openapi-core" "^2.25.2"
|
||||||
@@ -7321,10 +7326,10 @@ docusaurus-plugin-openapi-docs@^5.0.2:
|
|||||||
swagger2openapi "^7.0.8"
|
swagger2openapi "^7.0.8"
|
||||||
xml-formatter "^3.6.6"
|
xml-formatter "^3.6.6"
|
||||||
|
|
||||||
docusaurus-theme-openapi-docs@^5.0.2:
|
docusaurus-theme-openapi-docs@^5.0.1:
|
||||||
version "5.0.2"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-5.0.2.tgz#2ab6f6b04fc2e494e24971d31432a9187c84a2fe"
|
resolved "https://registry.yarnpkg.com/docusaurus-theme-openapi-docs/-/docusaurus-theme-openapi-docs-5.0.1.tgz#a2c2c91346b6238f6d7862752cdb02611fb5396f"
|
||||||
integrity sha512-BD6WhbunR6kXqtoUUDlhxO4HlCNM2nYENGr/TbiTEknkgXYKQz+FEIhY4Hyz5GSLpuhPih0CDuNl7Xkfpcz0Yw==
|
integrity sha512-bVeb7hOqog9LKVrJzYXdNJ7/0N22lk0VE22QK+naAn5GuAvYo41JmpXW9hqLIPkEp2UbexTHoPW9SYVdUsyvvw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hookform/error-message" "^2.0.1"
|
"@hookform/error-message" "^2.0.1"
|
||||||
"@reduxjs/toolkit" "^2.8.2"
|
"@reduxjs/toolkit" "^2.8.2"
|
||||||
@@ -8469,10 +8474,10 @@ globals@^15.14.0:
|
|||||||
resolved "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz"
|
resolved "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz"
|
||||||
integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==
|
integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==
|
||||||
|
|
||||||
globals@^17.6.0:
|
globals@^17.5.0:
|
||||||
version "17.6.0"
|
version "17.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/globals/-/globals-17.6.0.tgz#0f0be018d5cca8690e6375ead1f65c4bb96191fc"
|
resolved "https://registry.yarnpkg.com/globals/-/globals-17.5.0.tgz#a82c641d898f8dfbe0e81f66fdff7d0de43f88c6"
|
||||||
integrity sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==
|
integrity sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==
|
||||||
|
|
||||||
globalthis@^1.0.4:
|
globalthis@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
@@ -13323,7 +13328,7 @@ renderkid@^3.0.0:
|
|||||||
|
|
||||||
repeat-string@^1.5.2:
|
repeat-string@^1.5.2:
|
||||||
version "1.6.1"
|
version "1.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz"
|
||||||
integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
|
integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
|
||||||
|
|
||||||
require-directory@^2.1.1:
|
require-directory@^2.1.1:
|
||||||
@@ -15363,7 +15368,7 @@ webpack@^5.106.2, webpack@^5.88.1, webpack@^5.95.0:
|
|||||||
watchpack "^2.5.1"
|
watchpack "^2.5.1"
|
||||||
webpack-sources "^3.3.4"
|
webpack-sources "^3.3.4"
|
||||||
|
|
||||||
webpackbar@^7.0.0:
|
webpackbar@^6.0.1, webpackbar@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-7.0.0.tgz#7228d32881af2392381b6514499ddea73cdf218a"
|
resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-7.0.0.tgz#7228d32881af2392381b6514499ddea73cdf218a"
|
||||||
integrity sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q==
|
integrity sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q==
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ maintainers:
|
|||||||
- name: craig-rueda
|
- name: craig-rueda
|
||||||
email: craig@craigrueda.com
|
email: craig@craigrueda.com
|
||||||
url: https://github.com/craig-rueda
|
url: https://github.com/craig-rueda
|
||||||
version: 0.15.5 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
|
version: 0.15.4 # See [README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning) for version details.
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: postgresql
|
- name: postgresql
|
||||||
version: 16.7.27
|
version: 16.7.27
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: https://github.com/norwoodj/helm-docs
|
|||||||
|
|
||||||
# superset
|
# superset
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Apache Superset is a modern, enterprise-ready business intelligence web application
|
Apache Superset is a modern, enterprise-ready business intelligence web application
|
||||||
|
|
||||||
|
|||||||
@@ -844,8 +844,6 @@ postgresql:
|
|||||||
database: superset
|
database: superset
|
||||||
|
|
||||||
image:
|
image:
|
||||||
registry: docker.io
|
|
||||||
repository: bitnamilegacy/postgresql
|
|
||||||
tag: "14.17.0-debian-12-r3"
|
tag: "14.17.0-debian-12-r3"
|
||||||
|
|
||||||
## PostgreSQL Primary parameters
|
## PostgreSQL Primary parameters
|
||||||
@@ -920,11 +918,6 @@ redis:
|
|||||||
accessModes:
|
accessModes:
|
||||||
- ReadWriteOnce
|
- ReadWriteOnce
|
||||||
|
|
||||||
image:
|
|
||||||
registry: docker.io
|
|
||||||
repository: bitnamilegacy/redis
|
|
||||||
tag: 7.0.10-debian-11-r4
|
|
||||||
|
|
||||||
nodeSelector: {}
|
nodeSelector: {}
|
||||||
|
|
||||||
tolerations: []
|
tolerations: []
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ dependencies = [
|
|||||||
"redis>=5.0.0, <6.0",
|
"redis>=5.0.0, <6.0",
|
||||||
"rison>=2.0.0, <3.0",
|
"rison>=2.0.0, <3.0",
|
||||||
"selenium>=4.14.0, <5.0",
|
"selenium>=4.14.0, <5.0",
|
||||||
"shillelagh[gsheetsapi]>=1.4.4, <2.0",
|
"shillelagh[gsheetsapi]>=1.4.3, <2.0",
|
||||||
"sshtunnel>=0.4.0, <0.5",
|
"sshtunnel>=0.4.0, <0.5",
|
||||||
"simplejson>=3.15.0",
|
"simplejson>=3.15.0",
|
||||||
"slack_sdk>=3.19.0, <4",
|
"slack_sdk>=3.19.0, <4",
|
||||||
@@ -114,7 +114,7 @@ dependencies = [
|
|||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|
||||||
athena = ["pyathena[pandas]>=2, <4"]
|
athena = ["pyathena[pandas]>=2, <3"]
|
||||||
aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"]
|
aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"]
|
||||||
bigquery = [
|
bigquery = [
|
||||||
"pandas-gbq>=0.19.1",
|
"pandas-gbq>=0.19.1",
|
||||||
@@ -135,7 +135,7 @@ databricks = [
|
|||||||
"databricks-sqlalchemy==1.0.5",
|
"databricks-sqlalchemy==1.0.5",
|
||||||
]
|
]
|
||||||
db2 = ["ibm-db-sa>0.3.8, <=0.4.0"]
|
db2 = ["ibm-db-sa>0.3.8, <=0.4.0"]
|
||||||
denodo = ["denodo-sqlalchemy>=1.0.6,<2.1.0"]
|
denodo = ["denodo-sqlalchemy~=1.0.6"]
|
||||||
dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
|
dremio = ["sqlalchemy-dremio>=1.2.1, <4"]
|
||||||
drill = ["sqlalchemy-drill>=1.1.4, <2"]
|
drill = ["sqlalchemy-drill>=1.1.4, <2"]
|
||||||
druid = ["pydruid>=0.6.5,<0.7"]
|
druid = ["pydruid>=0.6.5,<0.7"]
|
||||||
@@ -149,7 +149,7 @@ fastmcp = ["fastmcp>=3.2.4,<4.0"]
|
|||||||
firebird = ["sqlalchemy-firebird>=0.7.0, <0.8"]
|
firebird = ["sqlalchemy-firebird>=0.7.0, <0.8"]
|
||||||
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
|
firebolt = ["firebolt-sqlalchemy>=1.0.0, <2"]
|
||||||
gevent = ["gevent>=23.9.1"]
|
gevent = ["gevent>=23.9.1"]
|
||||||
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
|
gsheets = ["shillelagh[gsheetsapi]>=1.4.3, <2"]
|
||||||
hana = ["hdbcli==2.4.162", "sqlalchemy_hana==0.4.0"]
|
hana = ["hdbcli==2.4.162", "sqlalchemy_hana==0.4.0"]
|
||||||
hive = [
|
hive = [
|
||||||
"pyhive[hive]>=0.6.5;python_version<'3.11'",
|
"pyhive[hive]>=0.6.5;python_version<'3.11'",
|
||||||
@@ -158,7 +158,7 @@ hive = [
|
|||||||
"thrift>=0.14.1, <1.0.0",
|
"thrift>=0.14.1, <1.0.0",
|
||||||
"thrift_sasl>=0.4.3, < 1.0.0",
|
"thrift_sasl>=0.4.3, < 1.0.0",
|
||||||
]
|
]
|
||||||
impala = ["impyla>0.16.2, <0.23"]
|
impala = ["impyla>0.16.2, <0.17"]
|
||||||
kusto = ["sqlalchemy-kusto>=3.0.0, <4"]
|
kusto = ["sqlalchemy-kusto>=3.0.0, <4"]
|
||||||
kylin = ["kylinpy>=2.8.1, <2.9"]
|
kylin = ["kylinpy>=2.8.1, <2.9"]
|
||||||
mssql = ["pymssql>=2.2.8, <3"]
|
mssql = ["pymssql>=2.2.8, <3"]
|
||||||
@@ -171,7 +171,7 @@ ocient = [
|
|||||||
"shapely",
|
"shapely",
|
||||||
"geojson",
|
"geojson",
|
||||||
]
|
]
|
||||||
oracle = ["cx-Oracle>8.0.0, <8.4"]
|
oracle = ["cx-Oracle>8.0.0, <8.1"]
|
||||||
parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"]
|
parseable = ["sqlalchemy-parseable>=0.1.3,<0.2.0"]
|
||||||
pinot = ["pinotdb>=5.0.0, <6.0.0"]
|
pinot = ["pinotdb>=5.0.0, <6.0.0"]
|
||||||
playwright = ["playwright>=1.37.0, <2"]
|
playwright = ["playwright>=1.37.0, <2"]
|
||||||
@@ -181,7 +181,7 @@ trino = ["trino>=0.328.0"]
|
|||||||
prophet = ["prophet>=1.1.6, <2"]
|
prophet = ["prophet>=1.1.6, <2"]
|
||||||
redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
|
redshift = ["sqlalchemy-redshift>=0.8.1, <0.9"]
|
||||||
risingwave = ["sqlalchemy-risingwave"]
|
risingwave = ["sqlalchemy-risingwave"]
|
||||||
shillelagh = ["shillelagh[all]>=1.4.4, <2"]
|
shillelagh = ["shillelagh[all]>=1.4.3, <2"]
|
||||||
singlestore = ["sqlalchemy-singlestoredb>=1.1.1, <2"]
|
singlestore = ["sqlalchemy-singlestoredb>=1.1.1, <2"]
|
||||||
snowflake = ["snowflake-sqlalchemy>=1.2.4, <2"]
|
snowflake = ["snowflake-sqlalchemy>=1.2.4, <2"]
|
||||||
sqlite = ["syntaqlite>=0.1.0"]
|
sqlite = ["syntaqlite>=0.1.0"]
|
||||||
@@ -197,7 +197,7 @@ tdengine = [
|
|||||||
]
|
]
|
||||||
teradata = ["teradatasql>=16.20.0.23"]
|
teradata = ["teradatasql>=16.20.0.23"]
|
||||||
thumbnails = [] # deprecated, will be removed in 7.0
|
thumbnails = [] # deprecated, will be removed in 7.0
|
||||||
vertica = ["sqlalchemy-vertica-python>= 0.5.9, < 0.7"]
|
vertica = ["sqlalchemy-vertica-python>=0.5.9, < 0.6"]
|
||||||
netezza = ["nzalchemy>=11.0.2"]
|
netezza = ["nzalchemy>=11.0.2"]
|
||||||
starrocks = ["starrocks>=1.0.0"]
|
starrocks = ["starrocks>=1.0.0"]
|
||||||
doris = ["pydoris>=1.0.0, <2.0.0"]
|
doris = ["pydoris>=1.0.0, <2.0.0"]
|
||||||
@@ -288,7 +288,6 @@ module = [
|
|||||||
"superset.tags.filters",
|
"superset.tags.filters",
|
||||||
"superset.commands.security.update",
|
"superset.commands.security.update",
|
||||||
"superset.commands.security.create",
|
"superset.commands.security.create",
|
||||||
"superset.semantic_layers.api",
|
|
||||||
]
|
]
|
||||||
warn_unused_ignores = false
|
warn_unused_ignores = false
|
||||||
|
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ selenium==4.32.0
|
|||||||
# via apache-superset (pyproject.toml)
|
# via apache-superset (pyproject.toml)
|
||||||
setuptools==80.9.0
|
setuptools==80.9.0
|
||||||
# via -r requirements/base.in
|
# via -r requirements/base.in
|
||||||
shillelagh==1.4.4
|
shillelagh==1.4.3
|
||||||
# via apache-superset (pyproject.toml)
|
# via apache-superset (pyproject.toml)
|
||||||
simplejson==3.20.1
|
simplejson==3.20.1
|
||||||
# via apache-superset (pyproject.toml)
|
# via apache-superset (pyproject.toml)
|
||||||
|
|||||||
@@ -931,7 +931,7 @@ setuptools==80.9.0
|
|||||||
# pydata-google-auth
|
# pydata-google-auth
|
||||||
# zope-event
|
# zope-event
|
||||||
# zope-interface
|
# zope-interface
|
||||||
shillelagh==1.4.4
|
shillelagh==1.4.3
|
||||||
# via
|
# via
|
||||||
# -c requirements/base-constraint.txt
|
# -c requirements/base-constraint.txt
|
||||||
# apache-superset
|
# apache-superset
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "apache-superset-core"
|
name = "apache-superset-core"
|
||||||
version = "0.1.0rc3"
|
version = "0.1.0rc2"
|
||||||
description = "Core Python package for building Apache Superset backend extensions and integrations"
|
description = "Core Python package for building Apache Superset backend extensions and integrations"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
@@ -43,8 +43,6 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flask-appbuilder>=5.0.2,<6",
|
"flask-appbuilder>=5.0.2,<6",
|
||||||
"isodate>=0.7.0",
|
|
||||||
"pyarrow>=16.0.0",
|
|
||||||
"pydantic>=2.8.0",
|
"pydantic>=2.8.0",
|
||||||
"sqlalchemy>=1.4.0,<2.0",
|
"sqlalchemy>=1.4.0,<2.0",
|
||||||
"sqlalchemy-utils>=0.38.0, <0.43", # expanding lowerbound to work with pydoris
|
"sqlalchemy-utils>=0.38.0, <0.43", # expanding lowerbound to work with pydoris
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
def build_configuration_schema(
|
|
||||||
config_class: type[BaseModel],
|
|
||||||
configuration: BaseModel | None = None,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Build a JSON schema from a Pydantic configuration class.
|
|
||||||
|
|
||||||
Handles generic boilerplate that any semantic layer with dynamic fields needs:
|
|
||||||
|
|
||||||
- Reorders properties to match model field order (Pydantic sorts alphabetically)
|
|
||||||
- When ``configuration`` is None, sets ``enum: []`` on all ``x-dynamic`` properties
|
|
||||||
so the frontend renders them as empty dropdowns
|
|
||||||
|
|
||||||
Semantic layer implementations call this instead of
|
|
||||||
``model_json_schema()`` directly,
|
|
||||||
then only need to add their own dynamic population logic.
|
|
||||||
"""
|
|
||||||
schema = config_class.model_json_schema()
|
|
||||||
|
|
||||||
# Pydantic sorts properties alphabetically; restore model field order
|
|
||||||
field_order = [
|
|
||||||
field.alias or name for name, field in config_class.model_fields.items()
|
|
||||||
]
|
|
||||||
schema["properties"] = {
|
|
||||||
key: schema["properties"][key]
|
|
||||||
for key in field_order
|
|
||||||
if key in schema["properties"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if configuration is None:
|
|
||||||
for prop_schema in schema["properties"].values():
|
|
||||||
if prop_schema.get("x-dynamic"):
|
|
||||||
prop_schema["enum"] = []
|
|
||||||
|
|
||||||
return schema
|
|
||||||
|
|
||||||
|
|
||||||
def check_dependencies(
|
|
||||||
prop_schema: dict[str, Any],
|
|
||||||
configuration: BaseModel,
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Check whether a dynamic property's dependencies are satisfied.
|
|
||||||
|
|
||||||
Reads the ``x-dependsOn`` list from the property schema and returns ``True``
|
|
||||||
when every referenced attribute on ``configuration`` is truthy.
|
|
||||||
"""
|
|
||||||
dependencies = prop_schema.get("x-dependsOn", [])
|
|
||||||
return all(getattr(configuration, dep, None) for dep in dependencies)
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Semantic layer DAO interfaces for superset-core.
|
|
||||||
|
|
||||||
Provides abstract DAO classes for semantic layers and views that define the
|
|
||||||
interface contract. Host implementations replace these with concrete classes
|
|
||||||
backed by SQLAlchemy during initialization.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
from superset_core.semantic_layers.daos import (
|
|
||||||
AbstractSemanticLayerDAO,
|
|
||||||
AbstractSemanticViewDAO,
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from abc import abstractmethod
|
|
||||||
from typing import Any, ClassVar
|
|
||||||
|
|
||||||
from superset_core.common.daos import BaseDAO
|
|
||||||
from superset_core.semantic_layers.models import SemanticLayerModel, SemanticViewModel
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractSemanticLayerDAO(BaseDAO[SemanticLayerModel]):
|
|
||||||
"""
|
|
||||||
Abstract DAO interface for SemanticLayer.
|
|
||||||
|
|
||||||
Host implementations will replace this class during initialization
|
|
||||||
with a concrete DAO providing actual database access.
|
|
||||||
"""
|
|
||||||
|
|
||||||
model_cls: ClassVar[type[Any] | None] = None
|
|
||||||
base_filter = None
|
|
||||||
id_column_name = "uuid"
|
|
||||||
uuid_column_name = "uuid"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def validate_uniqueness(cls, name: str) -> bool:
|
|
||||||
"""
|
|
||||||
Validate that a semantic layer name is unique.
|
|
||||||
|
|
||||||
:param name: Semantic layer name to validate
|
|
||||||
:return: True if the name is unique, False otherwise
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def validate_update_uniqueness(cls, layer_uuid: str, name: str) -> bool:
|
|
||||||
"""
|
|
||||||
Validate that a semantic layer name is unique for an update operation,
|
|
||||||
excluding the layer being updated.
|
|
||||||
|
|
||||||
:param layer_uuid: UUID of the semantic layer being updated
|
|
||||||
:param name: New name to validate
|
|
||||||
:return: True if the name is unique, False otherwise
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def find_by_name(cls, name: str) -> SemanticLayerModel | None:
|
|
||||||
"""
|
|
||||||
Find a semantic layer by name.
|
|
||||||
|
|
||||||
:param name: Semantic layer name
|
|
||||||
:return: SemanticLayerModel instance or None
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def get_semantic_views(cls, layer_uuid: str) -> list[SemanticViewModel]:
|
|
||||||
"""
|
|
||||||
Get all semantic views associated with a semantic layer.
|
|
||||||
|
|
||||||
:param layer_uuid: UUID of the semantic layer
|
|
||||||
:return: List of SemanticViewModel instances
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractSemanticViewDAO(BaseDAO[SemanticViewModel]):
|
|
||||||
"""
|
|
||||||
Abstract DAO interface for SemanticView.
|
|
||||||
|
|
||||||
Host implementations will replace this class during initialization
|
|
||||||
with a concrete DAO providing actual database access.
|
|
||||||
"""
|
|
||||||
|
|
||||||
model_cls: ClassVar[type[Any] | None] = None
|
|
||||||
base_filter = None
|
|
||||||
id_column_name = "id"
|
|
||||||
uuid_column_name = "uuid"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def validate_uniqueness(
|
|
||||||
cls,
|
|
||||||
name: str,
|
|
||||||
layer_uuid: str,
|
|
||||||
configuration: dict[str, Any],
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Validate that a semantic view is unique within a semantic layer.
|
|
||||||
|
|
||||||
Uniqueness is determined by the combination of name, layer UUID, and
|
|
||||||
configuration.
|
|
||||||
|
|
||||||
:param name: View name
|
|
||||||
:param layer_uuid: UUID of the parent semantic layer
|
|
||||||
:param configuration: Configuration dict to compare
|
|
||||||
:return: True if unique, False otherwise
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def validate_update_uniqueness(
|
|
||||||
cls,
|
|
||||||
view_uuid: str,
|
|
||||||
name: str,
|
|
||||||
layer_uuid: str,
|
|
||||||
configuration: dict[str, Any],
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Validate that a semantic view is unique within a semantic layer for an
|
|
||||||
update operation, excluding the view being updated.
|
|
||||||
|
|
||||||
:param view_uuid: UUID of the view being updated
|
|
||||||
:param name: New name to validate
|
|
||||||
:param layer_uuid: UUID of the parent semantic layer
|
|
||||||
:param configuration: Configuration dict to compare
|
|
||||||
:return: True if unique, False otherwise
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def find_by_name(cls, name: str, layer_uuid: str) -> SemanticViewModel | None:
|
|
||||||
"""
|
|
||||||
Find a semantic view by name within a semantic layer.
|
|
||||||
|
|
||||||
:param name: View name
|
|
||||||
:param layer_uuid: UUID of the parent semantic layer
|
|
||||||
:return: SemanticViewModel instance or None
|
|
||||||
"""
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["AbstractSemanticLayerDAO", "AbstractSemanticViewDAO"]
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Semantic layer registration decorator for Superset.
|
|
||||||
|
|
||||||
This module provides a decorator interface to register semantic layer
|
|
||||||
implementations with the host application, enabling automatic discovery
|
|
||||||
by the extensions framework.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
from superset_core.semantic_layers.decorators import semantic_layer
|
|
||||||
|
|
||||||
@semantic_layer(
|
|
||||||
id="snowflake",
|
|
||||||
name="Snowflake Cortex",
|
|
||||||
description="Snowflake semantic layer via Cortex Analyst",
|
|
||||||
)
|
|
||||||
class SnowflakeSemanticLayer(SemanticLayer[SnowflakeConfig, SnowflakeView]):
|
|
||||||
...
|
|
||||||
|
|
||||||
# Or with minimal arguments:
|
|
||||||
@semantic_layer(id="dbt", name="dbt Semantic Layer")
|
|
||||||
class DbtSemanticLayer(SemanticLayer[DbtConfig, DbtView]):
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Callable, TypeVar
|
|
||||||
|
|
||||||
# Type variable for decorated semantic layer classes
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
def semantic_layer(
|
|
||||||
id: str,
|
|
||||||
name: str,
|
|
||||||
description: str | None = None,
|
|
||||||
) -> Callable[[T], T]:
|
|
||||||
"""
|
|
||||||
Decorator to register a semantic layer implementation.
|
|
||||||
|
|
||||||
Automatically detects extension context and applies appropriate
|
|
||||||
namespacing to prevent ID conflicts between host and extension
|
|
||||||
semantic layers.
|
|
||||||
|
|
||||||
Host implementations will replace this function during initialization
|
|
||||||
with a concrete implementation providing actual functionality.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id: Unique semantic layer type identifier (e.g., "snowflake",
|
|
||||||
"dbt"). Used as the key in the semantic layers registry and
|
|
||||||
stored in the ``type`` column of the ``SemanticLayer`` model.
|
|
||||||
name: Human-readable display name (e.g., "Snowflake Cortex").
|
|
||||||
Shown in the UI when listing available semantic layer types.
|
|
||||||
description: Optional description for documentation and UI
|
|
||||||
tooltips.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Decorated semantic layer class registered with the host
|
|
||||||
application.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
NotImplementedError: If called before host implementation is
|
|
||||||
initialized.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
from superset_core.semantic_layers.decorators import semantic_layer
|
|
||||||
from superset_core.semantic_layers.layer import SemanticLayer
|
|
||||||
|
|
||||||
@semantic_layer(
|
|
||||||
id="snowflake",
|
|
||||||
name="Snowflake Cortex",
|
|
||||||
description="Connect to Snowflake Cortex Analyst",
|
|
||||||
)
|
|
||||||
class SnowflakeSemanticLayer(
|
|
||||||
SemanticLayer[SnowflakeConfig, SnowflakeView]
|
|
||||||
):
|
|
||||||
...
|
|
||||||
"""
|
|
||||||
raise NotImplementedError(
|
|
||||||
"Semantic layer decorator not initialized. "
|
|
||||||
"This decorator should be replaced during Superset startup."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["semantic_layer"]
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import Any, Generic, TypeVar
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
from superset_core.semantic_layers.view import SemanticView
|
|
||||||
|
|
||||||
ConfigT = TypeVar("ConfigT", bound=BaseModel)
|
|
||||||
SemanticViewT = TypeVar("SemanticViewT", bound="SemanticView")
|
|
||||||
|
|
||||||
|
|
||||||
class SemanticLayer(ABC, Generic[ConfigT, SemanticViewT]):
|
|
||||||
"""
|
|
||||||
Abstract base class for semantic layers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
configuration_class: type[BaseModel]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def from_configuration(
|
|
||||||
cls,
|
|
||||||
configuration: dict[str, Any],
|
|
||||||
) -> SemanticLayer[ConfigT, SemanticViewT]:
|
|
||||||
"""
|
|
||||||
Create a semantic layer from its configuration.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError(
|
|
||||||
"Semantic layers must implement the from_configuration method"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def get_configuration_schema(
|
|
||||||
cls,
|
|
||||||
configuration: ConfigT | None = None,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Get the JSON schema for the configuration needed to add the semantic layer.
|
|
||||||
|
|
||||||
A partial configuration `configuration` can be sent to improve the schema,
|
|
||||||
allowing for progressive validation and better UX. For example, a semantic
|
|
||||||
layer might require:
|
|
||||||
|
|
||||||
- auth information
|
|
||||||
- a database
|
|
||||||
|
|
||||||
If the user provides the auth information, a client can send the partial
|
|
||||||
configuration to this method, and the resulting JSON schema would include
|
|
||||||
the list of databases the user has access to, allowing a dropdown to be
|
|
||||||
populated.
|
|
||||||
|
|
||||||
The Snowflake semantic layer has an example implementation of this method, where
|
|
||||||
database and schema names are populated based on the provided connection info.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError(
|
|
||||||
"Semantic layers must implement the get_configuration_schema method"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@abstractmethod
|
|
||||||
def get_runtime_schema(
|
|
||||||
cls,
|
|
||||||
configuration: ConfigT,
|
|
||||||
runtime_data: dict[str, Any] | None = None,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Get the JSON schema for the runtime parameters needed to load semantic views.
|
|
||||||
|
|
||||||
This returns the schema needed to connect to a semantic view given the
|
|
||||||
configuration for the semantic layer. For example, a semantic layer might
|
|
||||||
be configured by:
|
|
||||||
|
|
||||||
- auth information
|
|
||||||
- an optional database
|
|
||||||
|
|
||||||
If the user does not provide a database when creating the semantic layer, the
|
|
||||||
runtime schema would require the database name to be provided before loading any
|
|
||||||
semantic views. This allows users to create semantic layers that connect to a
|
|
||||||
specific database (or project, account, etc.), or that allow users to select it
|
|
||||||
at query time.
|
|
||||||
|
|
||||||
The Snowflake semantic layer has an example implementation of this method, where
|
|
||||||
database and schema names are required if they were not provided in the initial
|
|
||||||
configuration.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError(
|
|
||||||
"Semantic layers must implement the get_runtime_schema method"
|
|
||||||
)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_semantic_views(
|
|
||||||
self,
|
|
||||||
runtime_configuration: dict[str, Any],
|
|
||||||
) -> set[SemanticViewT]:
|
|
||||||
"""
|
|
||||||
Get the semantic views available in the semantic layer.
|
|
||||||
|
|
||||||
The runtime configuration can provide information like a given project or
|
|
||||||
schema, used to restrict the semantic views returned.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_semantic_view(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
additional_configuration: dict[str, Any],
|
|
||||||
) -> SemanticViewT:
|
|
||||||
"""
|
|
||||||
Get a specific semantic view by its name and additional configuration.
|
|
||||||
"""
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Semantic layer model interfaces for superset-core.
|
|
||||||
|
|
||||||
Provides abstract model classes for semantic layers and views that will be
|
|
||||||
replaced by the host implementation's concrete SQLAlchemy models during
|
|
||||||
initialization.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
from superset_core.semantic_layers.models import (
|
|
||||||
SemanticLayerModel,
|
|
||||||
SemanticViewModel,
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from superset_core.common.models import CoreModel
|
|
||||||
|
|
||||||
|
|
||||||
class SemanticLayerModel(CoreModel):
|
|
||||||
"""
|
|
||||||
Abstract interface for the SemanticLayer database model.
|
|
||||||
|
|
||||||
Host implementations will replace this class during initialization
|
|
||||||
with a concrete SQLAlchemy model providing actual persistence.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__abstract__ = True
|
|
||||||
|
|
||||||
# Type hints for expected column attributes
|
|
||||||
uuid: UUID
|
|
||||||
name: str
|
|
||||||
description: str | None
|
|
||||||
type: str
|
|
||||||
configuration: str
|
|
||||||
configuration_version: int
|
|
||||||
cache_timeout: int | None
|
|
||||||
created_on: datetime | None
|
|
||||||
changed_on: datetime | None
|
|
||||||
|
|
||||||
|
|
||||||
class SemanticViewModel(CoreModel):
|
|
||||||
"""
|
|
||||||
Abstract interface for the SemanticView database model.
|
|
||||||
|
|
||||||
Host implementations will replace this class during initialization
|
|
||||||
with a concrete SQLAlchemy model providing actual persistence.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__abstract__ = True
|
|
||||||
|
|
||||||
# Type hints for expected column attributes
|
|
||||||
id: int
|
|
||||||
uuid: UUID
|
|
||||||
name: str
|
|
||||||
description: str | None
|
|
||||||
configuration: str
|
|
||||||
configuration_version: int
|
|
||||||
cache_timeout: int | None
|
|
||||||
semantic_layer_uuid: UUID
|
|
||||||
created_on: datetime | None
|
|
||||||
changed_on: datetime | None
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["SemanticLayerModel", "SemanticViewModel"]
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import date, datetime, time, timedelta
|
|
||||||
|
|
||||||
import isodate
|
|
||||||
import pyarrow as pa
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Grain:
|
|
||||||
"""
|
|
||||||
Represents a time grain (e.g., day, month, year).
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
name: Human-readable name of the grain (e.g., "Second")
|
|
||||||
representation: ISO 8601 duration (e.g., "PT1S", "P1D", "P1M")
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
representation: str
|
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
|
||||||
isodate.parse_duration(self.representation)
|
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
|
||||||
if isinstance(other, Grain):
|
|
||||||
return self.representation == other.representation
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
|
||||||
return hash(self.representation)
|
|
||||||
|
|
||||||
|
|
||||||
class Grains:
|
|
||||||
"""Pre-defined common grains and factory for custom ones."""
|
|
||||||
|
|
||||||
SECOND = Grain("Second", "PT1S")
|
|
||||||
MINUTE = Grain("Minute", "PT1M")
|
|
||||||
HOUR = Grain("Hour", "PT1H")
|
|
||||||
DAY = Grain("Day", "P1D")
|
|
||||||
WEEK = Grain("Week", "P1W")
|
|
||||||
MONTH = Grain("Month", "P1M")
|
|
||||||
QUARTER = Grain("Quarter", "P3M")
|
|
||||||
YEAR = Grain("Year", "P1Y")
|
|
||||||
|
|
||||||
_REGISTRY: dict[str, Grain] = {
|
|
||||||
"PT1S": SECOND,
|
|
||||||
"PT1M": MINUTE,
|
|
||||||
"PT1H": HOUR,
|
|
||||||
"P1D": DAY,
|
|
||||||
"P1W": WEEK,
|
|
||||||
"P1M": MONTH,
|
|
||||||
"P3M": QUARTER,
|
|
||||||
"P1Y": YEAR,
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, representation: str, name: str | None = None) -> Grain:
|
|
||||||
"""Return a pre-defined grain or create a custom one."""
|
|
||||||
if grain := cls._REGISTRY.get(representation):
|
|
||||||
return grain
|
|
||||||
return Grain(name or representation, representation)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Dimension:
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
type: pa.DataType
|
|
||||||
|
|
||||||
definition: str | None = None
|
|
||||||
description: str | None = None
|
|
||||||
grain: Grain | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Metric:
|
|
||||||
id: str
|
|
||||||
name: str
|
|
||||||
type: pa.DataType
|
|
||||||
|
|
||||||
definition: str
|
|
||||||
description: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AdhocExpression:
|
|
||||||
id: str
|
|
||||||
definition: str
|
|
||||||
|
|
||||||
|
|
||||||
class Operator(str, enum.Enum):
|
|
||||||
EQUALS = "="
|
|
||||||
NOT_EQUALS = "!="
|
|
||||||
GREATER_THAN = ">"
|
|
||||||
LESS_THAN = "<"
|
|
||||||
GREATER_THAN_OR_EQUAL = ">="
|
|
||||||
LESS_THAN_OR_EQUAL = "<="
|
|
||||||
IN = "IN"
|
|
||||||
NOT_IN = "NOT IN"
|
|
||||||
LIKE = "LIKE"
|
|
||||||
NOT_LIKE = "NOT LIKE"
|
|
||||||
IS_NULL = "IS NULL"
|
|
||||||
IS_NOT_NULL = "IS NOT NULL"
|
|
||||||
ADHOC = "ADHOC"
|
|
||||||
|
|
||||||
|
|
||||||
FilterValues = str | int | float | bool | datetime | date | time | timedelta | None
|
|
||||||
|
|
||||||
|
|
||||||
class PredicateType(enum.Enum):
|
|
||||||
WHERE = "WHERE"
|
|
||||||
HAVING = "HAVING"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, order=True)
|
|
||||||
class Filter:
|
|
||||||
type: PredicateType
|
|
||||||
column: Dimension | Metric | None
|
|
||||||
operator: Operator
|
|
||||||
value: FilterValues | frozenset[FilterValues]
|
|
||||||
|
|
||||||
|
|
||||||
class OrderDirection(enum.Enum):
|
|
||||||
ASC = "ASC"
|
|
||||||
DESC = "DESC"
|
|
||||||
|
|
||||||
|
|
||||||
OrderTuple = tuple[Metric | Dimension | AdhocExpression, OrderDirection]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class GroupLimit:
|
|
||||||
"""
|
|
||||||
Limit query to top/bottom N combinations of specified dimensions.
|
|
||||||
|
|
||||||
The `filters` parameter allows specifying separate filter constraints for the
|
|
||||||
group limit subquery. This is useful when you want to determine the top N groups
|
|
||||||
using different criteria (e.g., a different time range) than the main query.
|
|
||||||
|
|
||||||
For example, you might want to find the top 10 products by sales over the last
|
|
||||||
30 days, but then show daily sales for those products over the last 7 days.
|
|
||||||
"""
|
|
||||||
|
|
||||||
dimensions: list[Dimension]
|
|
||||||
top: int
|
|
||||||
metric: Metric | None
|
|
||||||
direction: OrderDirection = OrderDirection.DESC
|
|
||||||
group_others: bool = False
|
|
||||||
filters: set[Filter] | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SemanticRequest:
|
|
||||||
"""
|
|
||||||
Represents a request made to obtain semantic results.
|
|
||||||
|
|
||||||
This could be a SQL query, an HTTP request, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
type: str
|
|
||||||
definition: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SemanticResult:
|
|
||||||
"""
|
|
||||||
Represents the results of a semantic query.
|
|
||||||
|
|
||||||
This includes any requests (SQL queries, HTTP requests) that were performed in order
|
|
||||||
to obtain the results, in order to help troubleshooting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
requests: list[SemanticRequest]
|
|
||||||
results: pa.Table
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class SemanticQuery:
|
|
||||||
"""
|
|
||||||
Represents a semantic query.
|
|
||||||
"""
|
|
||||||
|
|
||||||
metrics: list[Metric]
|
|
||||||
dimensions: list[Dimension]
|
|
||||||
filters: set[Filter] | None = None
|
|
||||||
order: list[OrderTuple] | None = None
|
|
||||||
limit: int | None = None
|
|
||||||
offset: int | None = None
|
|
||||||
group_limit: GroupLimit | None = None
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import enum
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
|
|
||||||
from superset_core.semantic_layers.types import (
|
|
||||||
Dimension,
|
|
||||||
Filter,
|
|
||||||
Metric,
|
|
||||||
SemanticQuery,
|
|
||||||
SemanticResult,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO (betodealmeida): move to the extension JSON
|
|
||||||
class SemanticViewFeature(enum.Enum):
|
|
||||||
"""
|
|
||||||
Custom features supported by semantic layers.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ADHOC_EXPRESSIONS_IN_ORDERBY = "ADHOC_EXPRESSIONS_IN_ORDERBY"
|
|
||||||
GROUP_LIMIT = "GROUP_LIMIT"
|
|
||||||
GROUP_OTHERS = "GROUP_OTHERS"
|
|
||||||
|
|
||||||
|
|
||||||
class SemanticView(ABC):
|
|
||||||
"""
|
|
||||||
Abstract base class for semantic views.
|
|
||||||
"""
|
|
||||||
|
|
||||||
features: frozenset[SemanticViewFeature]
|
|
||||||
|
|
||||||
# Implementations must expose a display name for the view.
|
|
||||||
# Declared here as a type annotation (not abstract) so that existing
|
|
||||||
# implementations are not required to add a formal @abstractmethod.
|
|
||||||
name: str
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def uid(self) -> str:
|
|
||||||
"""
|
|
||||||
Returns a unique identifier for the semantic view.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_dimensions(self) -> set[Dimension]:
|
|
||||||
"""
|
|
||||||
Get the dimensions defined in the semantic view.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_metrics(self) -> set[Metric]:
|
|
||||||
"""
|
|
||||||
Get the metrics defined in the semantic view.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_values(
|
|
||||||
self,
|
|
||||||
dimension: Dimension,
|
|
||||||
filters: set[Filter] | None = None,
|
|
||||||
) -> SemanticResult:
|
|
||||||
"""
|
|
||||||
Return distinct values for a dimension.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_table(self, query: SemanticQuery) -> SemanticResult:
|
|
||||||
"""
|
|
||||||
Execute a semantic query and return the results.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_row_count(self, query: SemanticQuery) -> SemanticResult:
|
|
||||||
"""
|
|
||||||
Execute a query and return the number of rows the result would have.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_compatible_metrics(
|
|
||||||
self,
|
|
||||||
selected_metrics: set[Metric],
|
|
||||||
selected_dimensions: set[Dimension],
|
|
||||||
) -> set[Metric]:
|
|
||||||
"""
|
|
||||||
Return metrics compatible with the selected dimensions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_compatible_dimensions(
|
|
||||||
self,
|
|
||||||
selected_metrics: set[Metric],
|
|
||||||
selected_dimensions: set[Dimension],
|
|
||||||
) -> set[Dimension]:
|
|
||||||
"""
|
|
||||||
Return dimensions compatible with the selected metrics.
|
|
||||||
"""
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "apache-superset-extensions-cli"
|
name = "apache-superset-extensions-cli"
|
||||||
version = "0.1.0rc3"
|
version = "0.1.0rc2"
|
||||||
description = "Official command-line interface for building, bundling, and managing Apache Superset extensions"
|
description = "Official command-line interface for building, bundling, and managing Apache Superset extensions"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
9888
superset-frontend/package-lock.json
generated
9888
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -117,14 +117,7 @@
|
|||||||
"@luma.gl/gltf": "~9.2.5",
|
"@luma.gl/gltf": "~9.2.5",
|
||||||
"@luma.gl/shadertools": "~9.2.5",
|
"@luma.gl/shadertools": "~9.2.5",
|
||||||
"@luma.gl/webgl": "~9.2.5",
|
"@luma.gl/webgl": "~9.2.5",
|
||||||
"@fontsource/fira-code": "^5.2.7",
|
|
||||||
"@fontsource/inter": "^5.2.8",
|
|
||||||
"@great-expectations/jsonforms-antd-renderers": "^2.2.10",
|
|
||||||
"@jsonforms/core": "^3.7.0",
|
|
||||||
"@jsonforms/react": "^3.7.0",
|
|
||||||
"@jsonforms/vanilla-renderers": "^3.7.0",
|
|
||||||
"@reduxjs/toolkit": "^1.9.3",
|
"@reduxjs/toolkit": "^1.9.3",
|
||||||
"@rjsf/antd": "^5.24.13",
|
|
||||||
"@rjsf/core": "^5.24.13",
|
"@rjsf/core": "^5.24.13",
|
||||||
"@rjsf/utils": "^5.24.3",
|
"@rjsf/utils": "^5.24.3",
|
||||||
"@rjsf/validator-ajv8": "^5.24.13",
|
"@rjsf/validator-ajv8": "^5.24.13",
|
||||||
@@ -190,24 +183,24 @@
|
|||||||
"json-bigint": "^1.0.0",
|
"json-bigint": "^1.0.0",
|
||||||
"json-stringify-pretty-compact": "^2.0.0",
|
"json-stringify-pretty-compact": "^2.0.0",
|
||||||
"lodash": "^4.18.1",
|
"lodash": "^4.18.1",
|
||||||
"mapbox-gl": "^3.23.0",
|
"mapbox-gl": "^3.22.0",
|
||||||
"markdown-to-jsx": "^9.7.16",
|
"markdown-to-jsx": "^9.7.16",
|
||||||
"match-sorter": "^8.3.0",
|
"match-sorter": "^8.3.0",
|
||||||
"memoize-one": "^5.2.1",
|
"memoize-one": "^5.2.1",
|
||||||
"mousetrap": "^1.6.5",
|
"mousetrap": "^1.6.5",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"nanoid": "^5.1.11",
|
"nanoid": "^5.1.9",
|
||||||
"ol": "^10.9.0",
|
"ol": "^10.9.0",
|
||||||
"pretty-ms": "^9.3.0",
|
"pretty-ms": "^9.3.0",
|
||||||
"query-string": "9.3.1",
|
"query-string": "9.3.1",
|
||||||
"re-resizable": "^6.11.2",
|
"re-resizable": "^6.11.2",
|
||||||
"react": "^18.2.0",
|
"react": "^17.0.2",
|
||||||
"react-arborist": "^3.5.0",
|
"react-arborist": "^3.5.0",
|
||||||
"react-checkbox-tree": "^1.8.0",
|
"react-checkbox-tree": "^1.8.0",
|
||||||
"react-diff-viewer-continued": "^4.2.2",
|
"react-diff-viewer-continued": "^4.2.2",
|
||||||
"react-dnd": "^11.1.3",
|
"react-dnd": "^11.1.3",
|
||||||
"react-dnd-html5-backend": "^11.1.3",
|
"react-dnd-html5-backend": "^11.1.3",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^17.0.2",
|
||||||
"react-google-recaptcha": "^3.1.0",
|
"react-google-recaptcha": "^3.1.0",
|
||||||
"react-intersection-observer": "^10.0.3",
|
"react-intersection-observer": "^10.0.3",
|
||||||
"react-json-tree": "^0.20.0",
|
"react-json-tree": "^0.20.0",
|
||||||
@@ -218,6 +211,7 @@
|
|||||||
"react-reverse-portal": "^2.3.0",
|
"react-reverse-portal": "^2.3.0",
|
||||||
"react-router-dom": "^5.3.4",
|
"react-router-dom": "^5.3.4",
|
||||||
"react-search-input": "^0.11.3",
|
"react-search-input": "^0.11.3",
|
||||||
|
"react-sortable-hoc": "^2.0.0",
|
||||||
"react-split": "^2.0.9",
|
"react-split": "^2.0.9",
|
||||||
"react-table": "^7.8.0",
|
"react-table": "^7.8.0",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
@@ -250,13 +244,14 @@
|
|||||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
|
||||||
"@babel/plugin-transform-runtime": "^7.29.0",
|
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||||
"@babel/preset-env": "^7.29.3",
|
"@babel/preset-env": "^7.29.2",
|
||||||
"@babel/preset-react": "^7.28.5",
|
"@babel/preset-react": "^7.28.5",
|
||||||
"@babel/preset-typescript": "^7.28.5",
|
"@babel/preset-typescript": "^7.28.5",
|
||||||
"@babel/register": "^7.23.7",
|
"@babel/register": "^7.23.7",
|
||||||
"@babel/runtime": "^7.29.2",
|
"@babel/runtime": "^7.29.2",
|
||||||
"@babel/runtime-corejs3": "^7.29.2",
|
"@babel/runtime-corejs3": "^7.29.2",
|
||||||
"@babel/types": "^7.28.6",
|
"@babel/types": "^7.28.6",
|
||||||
|
"@cypress/react": "^8.0.2",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/jest": "^11.14.2",
|
"@emotion/jest": "^11.14.2",
|
||||||
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
"@istanbuljs/nyc-config-typescript": "^1.0.1",
|
||||||
@@ -278,9 +273,10 @@
|
|||||||
"@swc/core": "^1.15.32",
|
"@swc/core": "^1.15.32",
|
||||||
"@swc/plugin-emotion": "^14.9.0",
|
"@swc/plugin-emotion": "^14.9.0",
|
||||||
"@swc/plugin-transform-imports": "^12.5.0",
|
"@swc/plugin-transform-imports": "^12.5.0",
|
||||||
"@testing-library/dom": "^9.3.4",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^12.1.5",
|
||||||
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
"@testing-library/user-event": "^12.8.3",
|
"@testing-library/user-event": "^12.8.3",
|
||||||
"@types/content-disposition": "^0.5.9",
|
"@types/content-disposition": "^0.5.9",
|
||||||
"@types/dom-to-image": "^2.6.7",
|
"@types/dom-to-image": "^2.6.7",
|
||||||
@@ -290,8 +286,8 @@
|
|||||||
"@types/json-bigint": "^1.0.4",
|
"@types/json-bigint": "^1.0.4",
|
||||||
"@types/mousetrap": "^1.6.15",
|
"@types/mousetrap": "^1.6.15",
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "^17.0.83",
|
||||||
"@types/react-dom": "^18.2.0",
|
"@types/react-dom": "^17.0.26",
|
||||||
"@types/react-loadable": "^5.5.11",
|
"@types/react-loadable": "^5.5.11",
|
||||||
"@types/react-redux": "^7.1.10",
|
"@types/react-redux": "^7.1.10",
|
||||||
"@types/react-resizable": "^3.0.8",
|
"@types/react-resizable": "^3.0.8",
|
||||||
@@ -310,7 +306,7 @@
|
|||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
"baseline-browser-mapping": "^2.10.24",
|
"baseline-browser-mapping": "^2.10.21",
|
||||||
"cheerio": "1.2.0",
|
"cheerio": "1.2.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"copy-webpack-plugin": "^14.0.0",
|
"copy-webpack-plugin": "^14.0.0",
|
||||||
@@ -330,7 +326,7 @@
|
|||||||
"eslint-plugin-no-only-tests": "^3.4.0",
|
"eslint-plugin-no-only-tests": "^3.4.0",
|
||||||
"eslint-plugin-prettier": "^5.5.5",
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
"eslint-plugin-react-prefer-function-component": "^5.0.0",
|
"eslint-plugin-react-prefer-function-component": "^5.0.0",
|
||||||
"eslint-plugin-react-you-might-not-need-an-effect": "^0.10.0",
|
"eslint-plugin-react-you-might-not-need-an-effect": "^0.9.3",
|
||||||
"eslint-plugin-storybook": "^0.8.0",
|
"eslint-plugin-storybook": "^0.8.0",
|
||||||
"eslint-plugin-testing-library": "^7.16.2",
|
"eslint-plugin-testing-library": "^7.16.2",
|
||||||
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
|
||||||
@@ -345,7 +341,7 @@
|
|||||||
"jest-html-reporter": "^4.4.0",
|
"jest-html-reporter": "^4.4.0",
|
||||||
"jest-websocket-mock": "^2.5.0",
|
"jest-websocket-mock": "^2.5.0",
|
||||||
"js-yaml-loader": "^1.2.2",
|
"js-yaml-loader": "^1.2.2",
|
||||||
"jsdom": "^29.1.1",
|
"jsdom": "^29.1.0",
|
||||||
"lerna": "^9.0.4",
|
"lerna": "^9.0.4",
|
||||||
"lightningcss": "^1.32.0",
|
"lightningcss": "^1.32.0",
|
||||||
"mini-css-extract-plugin": "^2.10.2",
|
"mini-css-extract-plugin": "^2.10.2",
|
||||||
@@ -377,7 +373,7 @@
|
|||||||
"webpack-cli": "^6.0.1",
|
"webpack-cli": "^6.0.1",
|
||||||
"webpack-dev-server": "^5.2.3",
|
"webpack-dev-server": "^5.2.3",
|
||||||
"webpack-manifest-plugin": "^5.0.1",
|
"webpack-manifest-plugin": "^5.0.1",
|
||||||
"webpack-sources": "^3.4.1",
|
"webpack-sources": "^3.4.0",
|
||||||
"webpack-visualizer-plugin2": "^2.0.0"
|
"webpack-visualizer-plugin2": "^2.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -30,14 +30,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"lodash-es": "^4.18.1",
|
"lodash-es": "^4.18.1",
|
||||||
"yeoman-generator": "^8.1.2",
|
"yeoman-generator": "^8.2.2",
|
||||||
"yosay": "^3.0.0"
|
"yosay": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"fs-extra": "^11.3.4",
|
"fs-extra": "^11.3.4",
|
||||||
"jest": "^30.3.0",
|
"jest": "^30.3.0",
|
||||||
"yeoman-test": "^11.4.2"
|
"yeoman-test": "^11.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": ">= 4.0.0",
|
"npm": ">= 4.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@apache-superset/core",
|
"name": "@apache-superset/core",
|
||||||
"version": "0.1.0-rc3",
|
"version": "0.1.0-rc2",
|
||||||
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
|
"description": "This package contains UI elements, APIs, and utility functions used by Superset.",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
@@ -75,15 +75,16 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.28.6",
|
"@babel/cli": "^7.28.6",
|
||||||
"@babel/core": "^7.29.0",
|
"@babel/core": "^7.29.0",
|
||||||
"@babel/preset-env": "^7.29.3",
|
"@babel/preset-env": "^7.29.2",
|
||||||
"@babel/preset-react": "^7.28.5",
|
"@babel/preset-react": "^7.28.5",
|
||||||
"@babel/preset-typescript": "^7.28.5",
|
"@babel/preset-typescript": "^7.28.5",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@types/lodash": "^4.17.24",
|
"@types/lodash": "^4.17.24",
|
||||||
"@testing-library/dom": "^9.3.4",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^12.1.5",
|
||||||
|
"@testing-library/react-hooks": "*",
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-loadable": "*",
|
"@types/react-loadable": "*",
|
||||||
@@ -97,8 +98,8 @@
|
|||||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||||
"@fontsource/inter": "^5.2.6",
|
"@fontsource/inter": "^5.2.6",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
"react": "^18.2.0",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^17.0.2",
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"tinycolor2": "*",
|
"tinycolor2": "*",
|
||||||
"lodash": "^4.18.1",
|
"lodash": "^4.18.1",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import { render, RenderOptions, RenderResult } from '@testing-library/react';
|
import { render, RenderOptions } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import { themeObject } from './theme';
|
import { themeObject } from './theme';
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ const Providers = ({ children }: { children: React.ReactNode }) => (
|
|||||||
const customRender = (
|
const customRender = (
|
||||||
ui: ReactElement,
|
ui: ReactElement,
|
||||||
options?: Omit<RenderOptions, 'wrapper'>,
|
options?: Omit<RenderOptions, 'wrapper'>,
|
||||||
): RenderResult => render(ui, { wrapper: Providers, ...options });
|
) => render(ui, { wrapper: Providers, ...options });
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createEvent,
|
createEvent,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { ThemeProvider } from '@emotion/react';
|
import { ThemeProvider } from '@emotion/react';
|
||||||
import { theme as antdTheme } from 'antd';
|
import { theme as antdTheme } from 'antd';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -33,16 +33,17 @@
|
|||||||
"@ant-design/icons": "^5.6.1",
|
"@ant-design/icons": "^5.6.1",
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@testing-library/dom": "^9.3.4",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^12.1.5",
|
||||||
|
"@testing-library/react-hooks": "*",
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"ace-builds": "^1.4.14",
|
"ace-builds": "^1.4.14",
|
||||||
"brace": "^0.11.1",
|
"brace": "^0.11.1",
|
||||||
"memoize-one": "^5.1.1",
|
"memoize-one": "^5.1.1",
|
||||||
"react": "^18.2.0",
|
"react": "^17.0.2",
|
||||||
"react-ace": "^10.1.0",
|
"react-ace": "^10.1.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^17.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { isMatrixifyVisible } from './matrixifyControls';
|
import { isMatrixifyVisible } from './matrixifyControls';
|
||||||
import type { ControlStateMapping } from '../types';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to build a controls object matching the shape used by
|
* Helper to build a controls object matching the shape used by
|
||||||
@@ -26,7 +25,7 @@ import type { ControlStateMapping } from '../types';
|
|||||||
*/
|
*/
|
||||||
function makeControls(
|
function makeControls(
|
||||||
overrides: Record<string, unknown> = {},
|
overrides: Record<string, unknown> = {},
|
||||||
): ControlStateMapping {
|
): Record<string, { value: unknown }> {
|
||||||
const defaults: Record<string, unknown> = {
|
const defaults: Record<string, unknown> = {
|
||||||
matrixify_enable: false,
|
matrixify_enable: false,
|
||||||
matrixify_mode_rows: 'disabled',
|
matrixify_mode_rows: 'disabled',
|
||||||
@@ -37,7 +36,7 @@ function makeControls(
|
|||||||
const merged = { ...defaults, ...overrides };
|
const merged = { ...defaults, ...overrides };
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(merged).map(([k, v]) => [k, { value: v }]),
|
Object.entries(merged).map(([k, v]) => [k, { value: v }]),
|
||||||
) as ControlStateMapping;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── matrixify_enable guard ──────────────────────────────────────────
|
// ── matrixify_enable guard ──────────────────────────────────────────
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
import { t } from '@apache-superset/core/translation';
|
import { t } from '@apache-superset/core/translation';
|
||||||
import { validateNonEmpty } from '@superset-ui/core';
|
import { validateNonEmpty } from '@superset-ui/core';
|
||||||
import { ControlStateMapping, SharedControlConfig } from '../types';
|
import { SharedControlConfig } from '../types';
|
||||||
import { dndAdhocMetricControl } from './dndControls';
|
import { dndAdhocMetricControl } from './dndControls';
|
||||||
import { defineSavedMetrics } from '../utils';
|
import { defineSavedMetrics } from '../utils';
|
||||||
|
|
||||||
@@ -29,12 +29,9 @@ import { defineSavedMetrics } from '../utils';
|
|||||||
* Controls for transforming charts into matrix/grid layouts
|
* Controls for transforming charts into matrix/grid layouts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Utility function to check if matrixify controls should be visible.
|
// Utility function to check if matrixify controls should be visible
|
||||||
// Controls both visibility callbacks and validator injection via mapStateToProps.
|
|
||||||
// The matrixify_enable guard prevents hidden validators from firing on
|
|
||||||
// pre-revamp charts with stale matrixify_mode defaults (fix for #38519).
|
|
||||||
const isMatrixifyVisible = (
|
const isMatrixifyVisible = (
|
||||||
controls: ControlStateMapping | undefined,
|
controls: any,
|
||||||
axis: 'rows' | 'columns',
|
axis: 'rows' | 'columns',
|
||||||
mode?: 'metrics' | 'dimensions',
|
mode?: 'metrics' | 'dimensions',
|
||||||
selectionMode?: 'members' | 'topn' | 'all',
|
selectionMode?: 'members' | 'topn' | 'all',
|
||||||
|
|||||||
@@ -1,238 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for the matrixify_enable guard in isMatrixifyVisible() and
|
|
||||||
* validator injection via mapStateToProps on real matrixify control definitions.
|
|
||||||
*
|
|
||||||
* These are TDD tests for the fix to apache/superset#38519 regression:
|
|
||||||
* isMatrixifyVisible() must check matrixify_enable before evaluating mode,
|
|
||||||
* otherwise pre-revamp charts with stale matrixify_mode defaults trigger
|
|
||||||
* hidden validators that block save.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
matrixifyControls,
|
|
||||||
isMatrixifyVisible,
|
|
||||||
} from '../../src/shared-controls/matrixifyControls';
|
|
||||||
import type { ControlPanelState, ControlStateMapping } from '../../src/types';
|
|
||||||
|
|
||||||
// Helper: build a minimal controls object for ControlPanelState
|
|
||||||
const buildControls = (
|
|
||||||
overrides: Record<string, any> = {},
|
|
||||||
): ControlStateMapping => {
|
|
||||||
const controls: Record<string, { value: any }> = {};
|
|
||||||
Object.entries(overrides).forEach(([key, value]) => {
|
|
||||||
controls[key] = { value };
|
|
||||||
});
|
|
||||||
return controls as ControlStateMapping;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper: build a minimal ControlPanelState for mapStateToProps.
|
|
||||||
// Only provides fields that isMatrixifyVisible and mapStateToProps actually read.
|
|
||||||
const buildState = (
|
|
||||||
controlValues: Record<string, any> = {},
|
|
||||||
formData: Record<string, any> = {},
|
|
||||||
) =>
|
|
||||||
({
|
|
||||||
controls: buildControls(controlValues),
|
|
||||||
datasource: { columns: [], type: 'table' },
|
|
||||||
form_data: formData,
|
|
||||||
common: {},
|
|
||||||
metadata: {},
|
|
||||||
slice: { slice_id: 0 },
|
|
||||||
}) as unknown as ControlPanelState;
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Validator injection tests via real mapStateToProps (rows)
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// --- matrixify_dimension_rows ---
|
|
||||||
|
|
||||||
test('matrixify_dimension_rows: validators empty when matrixify_enable is falsy', () => {
|
|
||||||
const control = matrixifyControls.matrixify_dimension_rows;
|
|
||||||
const state = buildState(
|
|
||||||
{
|
|
||||||
matrixify_enable: undefined,
|
|
||||||
matrixify_mode_rows: 'dimensions',
|
|
||||||
matrixify_dimension_selection_mode_rows: 'members',
|
|
||||||
},
|
|
||||||
{ matrixify_mode_rows: 'dimensions' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = control.mapStateToProps!(state, {} as any);
|
|
||||||
expect(result.validators).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('matrixify_dimension_rows: validators present when matrixify_enable is true', () => {
|
|
||||||
const control = matrixifyControls.matrixify_dimension_rows;
|
|
||||||
const state = buildState(
|
|
||||||
{
|
|
||||||
matrixify_enable: true,
|
|
||||||
matrixify_mode_rows: 'dimensions',
|
|
||||||
matrixify_dimension_selection_mode_rows: 'members',
|
|
||||||
},
|
|
||||||
{ matrixify_mode_rows: 'dimensions' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = control.mapStateToProps!(state, {} as any);
|
|
||||||
expect(result.validators.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- matrixify_topn_value_rows ---
|
|
||||||
|
|
||||||
test('matrixify_topn_value_rows: validators empty when matrixify_enable is falsy', () => {
|
|
||||||
const control = matrixifyControls.matrixify_topn_value_rows;
|
|
||||||
const state = buildState(
|
|
||||||
{
|
|
||||||
matrixify_enable: undefined,
|
|
||||||
matrixify_mode_rows: 'dimensions',
|
|
||||||
matrixify_dimension_selection_mode_rows: 'topn',
|
|
||||||
},
|
|
||||||
{ matrixify_mode_rows: 'dimensions' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = control.mapStateToProps!(state, {} as any);
|
|
||||||
expect(result.validators).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('matrixify_topn_value_rows: validators present when matrixify_enable is true', () => {
|
|
||||||
const control = matrixifyControls.matrixify_topn_value_rows;
|
|
||||||
const state = buildState(
|
|
||||||
{
|
|
||||||
matrixify_enable: true,
|
|
||||||
matrixify_mode_rows: 'dimensions',
|
|
||||||
matrixify_dimension_selection_mode_rows: 'topn',
|
|
||||||
},
|
|
||||||
{ matrixify_mode_rows: 'dimensions' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = control.mapStateToProps!(state, {} as any);
|
|
||||||
expect(result.validators.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- matrixify_topn_metric_rows ---
|
|
||||||
|
|
||||||
test('matrixify_topn_metric_rows: validators empty when matrixify_enable is falsy', () => {
|
|
||||||
const control = matrixifyControls.matrixify_topn_metric_rows;
|
|
||||||
const state = buildState(
|
|
||||||
{
|
|
||||||
matrixify_enable: undefined,
|
|
||||||
matrixify_mode_rows: 'dimensions',
|
|
||||||
matrixify_dimension_selection_mode_rows: 'topn',
|
|
||||||
},
|
|
||||||
{ matrixify_mode_rows: 'dimensions' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = control.mapStateToProps!(state, {} as any);
|
|
||||||
expect(result.validators).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('matrixify_topn_metric_rows: validators present when matrixify_enable is true', () => {
|
|
||||||
const control = matrixifyControls.matrixify_topn_metric_rows;
|
|
||||||
const state = buildState(
|
|
||||||
{
|
|
||||||
matrixify_enable: true,
|
|
||||||
matrixify_mode_rows: 'dimensions',
|
|
||||||
matrixify_dimension_selection_mode_rows: 'topn',
|
|
||||||
},
|
|
||||||
{ matrixify_mode_rows: 'dimensions' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = control.mapStateToProps!(state, {} as any);
|
|
||||||
expect(result.validators.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Validator injection tests via real mapStateToProps (columns)
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
test('matrixify_dimension_columns: validators empty when matrixify_enable is falsy', () => {
|
|
||||||
const control = matrixifyControls.matrixify_dimension_columns;
|
|
||||||
const state = buildState(
|
|
||||||
{
|
|
||||||
matrixify_enable: undefined,
|
|
||||||
matrixify_mode_columns: 'dimensions',
|
|
||||||
matrixify_dimension_selection_mode_columns: 'members',
|
|
||||||
},
|
|
||||||
{ matrixify_mode_columns: 'dimensions' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = control.mapStateToProps!(state, {} as any);
|
|
||||||
expect(result.validators).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('matrixify_dimension_columns: validators present when matrixify_enable is true', () => {
|
|
||||||
const control = matrixifyControls.matrixify_dimension_columns;
|
|
||||||
const state = buildState(
|
|
||||||
{
|
|
||||||
matrixify_enable: true,
|
|
||||||
matrixify_mode_columns: 'dimensions',
|
|
||||||
matrixify_dimension_selection_mode_columns: 'members',
|
|
||||||
},
|
|
||||||
{ matrixify_mode_columns: 'dimensions' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = control.mapStateToProps!(state, {} as any);
|
|
||||||
expect(result.validators.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Direct isMatrixifyVisible guard tests
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
['undefined', undefined],
|
|
||||||
['null', null],
|
|
||||||
['false', false],
|
|
||||||
['0', 0],
|
|
||||||
])(
|
|
||||||
'isMatrixifyVisible returns false when matrixify_enable is %s',
|
|
||||||
(_, value) => {
|
|
||||||
const controls = buildControls({
|
|
||||||
matrixify_enable: value,
|
|
||||||
matrixify_mode_rows: 'dimensions',
|
|
||||||
});
|
|
||||||
expect(isMatrixifyVisible(controls, 'rows')).toBe(false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test('isMatrixifyVisible returns true when matrixify_enable is true and mode matches', () => {
|
|
||||||
const controls = buildControls({
|
|
||||||
matrixify_enable: true,
|
|
||||||
matrixify_mode_rows: 'dimensions',
|
|
||||||
});
|
|
||||||
expect(isMatrixifyVisible(controls, 'rows', 'dimensions')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isMatrixifyVisible returns false when matrixify_enable is true but mode is disabled', () => {
|
|
||||||
const controls = buildControls({
|
|
||||||
matrixify_enable: true,
|
|
||||||
matrixify_mode_rows: 'disabled',
|
|
||||||
});
|
|
||||||
expect(isMatrixifyVisible(controls, 'rows')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isMatrixifyVisible returns true when matrixify_enable is true and any non-disabled mode (no mode filter)', () => {
|
|
||||||
const controls = buildControls({
|
|
||||||
matrixify_enable: true,
|
|
||||||
matrixify_mode_columns: 'metrics',
|
|
||||||
});
|
|
||||||
expect(isMatrixifyVisible(controls, 'columns')).toBe(true);
|
|
||||||
});
|
|
||||||
@@ -91,9 +91,10 @@
|
|||||||
"@emotion/cache": "^11.4.0",
|
"@emotion/cache": "^11.4.0",
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@testing-library/dom": "^9.3.4",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^12.1.5",
|
||||||
|
"@testing-library/react-hooks": "*",
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-loadable": "*",
|
"@types/react-loadable": "*",
|
||||||
@@ -101,8 +102,8 @@
|
|||||||
"@types/tinycolor2": "*",
|
"@types/tinycolor2": "*",
|
||||||
"antd": "^5.26.0",
|
"antd": "^5.26.0",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
"react": "^18.2.0",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^17.0.2",
|
||||||
"react-loadable": "^5.5.0",
|
"react-loadable": "^5.5.0",
|
||||||
"tinycolor2": "*"
|
"tinycolor2": "*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -94,20 +94,11 @@ class CategoricalColorScale extends ExtensibleFunction {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment the color range with analogous colors
|
* Increment the color range with analogous colors
|
||||||
*
|
|
||||||
* @param forceMinimumExpansion When true, expand at least once even if the
|
|
||||||
* ordinal domain is still shorter than the palette. Shared dashboard labels
|
|
||||||
* can resolve from the global map without entering the scale domain, so
|
|
||||||
* domain-based sizing alone would skip expansion while collision resolution
|
|
||||||
* still needs analogous colors.
|
|
||||||
*/
|
*/
|
||||||
incrementColorRange(forceMinimumExpansion = false) {
|
incrementColorRange() {
|
||||||
const domainBasedMultiple = Math.floor(
|
const multiple = Math.floor(
|
||||||
this.domain().length / this.originColors.length,
|
this.domain().length / this.originColors.length,
|
||||||
);
|
);
|
||||||
const multiple = forceMinimumExpansion
|
|
||||||
? Math.max(domainBasedMultiple, 1)
|
|
||||||
: domainBasedMultiple;
|
|
||||||
// the domain has grown larger than the original range
|
// the domain has grown larger than the original range
|
||||||
// increments the range with analogous colors
|
// increments the range with analogous colors
|
||||||
if (multiple > this.multiple) {
|
if (multiple > this.multiple) {
|
||||||
@@ -153,7 +144,6 @@ class CategoricalColorScale extends ExtensibleFunction {
|
|||||||
if (isFeatureEnabled(FeatureFlag.UseAnalogousColors)) {
|
if (isFeatureEnabled(FeatureFlag.UseAnalogousColors)) {
|
||||||
this.incrementColorRange();
|
this.incrementColorRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// feature flag to be deprecated (will become standard behaviour)
|
// feature flag to be deprecated (will become standard behaviour)
|
||||||
isFeatureEnabled(FeatureFlag.AvoidColorsCollision) &&
|
isFeatureEnabled(FeatureFlag.AvoidColorsCollision) &&
|
||||||
@@ -164,39 +154,6 @@ class CategoricalColorScale extends ExtensibleFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
isFeatureEnabled(FeatureFlag.AvoidColorsCollision) &&
|
|
||||||
source === LabelsColorMapSource.Dashboard &&
|
|
||||||
(forcedColor || isExistingLabel)
|
|
||||||
) {
|
|
||||||
const colliding = [...this.chartLabelsColorMap.entries()].filter(
|
|
||||||
([labelKey, c]) => c === color && labelKey !== cleanedValue,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
colliding.length > 0 &&
|
|
||||||
isFeatureEnabled(FeatureFlag.UseAnalogousColors)
|
|
||||||
) {
|
|
||||||
this.incrementColorRange(true);
|
|
||||||
}
|
|
||||||
for (const [otherLabel] of colliding) {
|
|
||||||
if (
|
|
||||||
Object.prototype.hasOwnProperty.call(this.forcedColors, otherLabel)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const newColor = this.getNextAvailableColor(otherLabel, color);
|
|
||||||
this.chartLabelsColorMap.set(otherLabel, newColor);
|
|
||||||
if (sliceId) {
|
|
||||||
this.labelsColorMapInstance.addSlice(
|
|
||||||
otherLabel,
|
|
||||||
newColor,
|
|
||||||
sliceId,
|
|
||||||
appliedColorScheme,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep track of values in this slice
|
// keep track of values in this slice
|
||||||
this.chartLabelsColorMap.set(cleanedValue, color);
|
this.chartLabelsColorMap.set(cleanedValue, color);
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,19 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ReactElement, ReactNode } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
|
import {
|
||||||
|
Tooltip,
|
||||||
|
type TooltipPlacement,
|
||||||
|
type IconType,
|
||||||
|
} from '@superset-ui/core/components';
|
||||||
import { css, useTheme } from '@apache-superset/core/theme';
|
import { css, useTheme } from '@apache-superset/core/theme';
|
||||||
|
|
||||||
export interface ActionProps {
|
export interface ActionProps {
|
||||||
label: string;
|
label: string;
|
||||||
tooltip?: string | ReactElement;
|
tooltip?: string | ReactElement;
|
||||||
placement?: TooltipPlacement;
|
placement?: TooltipPlacement;
|
||||||
icon: ReactNode;
|
icon: IconType;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { useJsonValidation } from './useJsonValidation';
|
import { useJsonValidation } from './useJsonValidation';
|
||||||
|
|
||||||
describe('useJsonValidation', () => {
|
describe('useJsonValidation', () => {
|
||||||
|
|||||||
@@ -16,7 +16,16 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React, { useEffect, useState, forwardRef, ComponentType } from 'react';
|
import {
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
RefObject,
|
||||||
|
forwardRef,
|
||||||
|
ComponentType,
|
||||||
|
ForwardRefExoticComponent,
|
||||||
|
PropsWithoutRef,
|
||||||
|
RefAttributes,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { Loading } from '../Loading';
|
import { Loading } from '../Loading';
|
||||||
import type { PlaceholderProps } from './types';
|
import type { PlaceholderProps } from './types';
|
||||||
@@ -84,16 +93,15 @@ export function AsyncEsmComponent<
|
|||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AsyncComponent = React.ForwardRefExoticComponent<
|
type AsyncComponent = ForwardRefExoticComponent<
|
||||||
React.PropsWithoutRef<FullProps> & React.RefAttributes<unknown>
|
PropsWithoutRef<FullProps> & RefAttributes<ComponentType<FullProps>>
|
||||||
> & {
|
> & {
|
||||||
preload?: typeof waitForPromise;
|
preload?: typeof waitForPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-expect-error -- generic forwardRef has PropsWithoutRef incompatibility with FullProps
|
|
||||||
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
|
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
|
||||||
props: FullProps,
|
props: FullProps,
|
||||||
ref,
|
ref: RefObject<ComponentType<FullProps>>,
|
||||||
) {
|
) {
|
||||||
const [loaded, setLoaded] = useState(component !== undefined);
|
const [loaded, setLoaded] = useState(component !== undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import type {
|
|||||||
ButtonVariantType,
|
ButtonVariantType,
|
||||||
ButtonColorType,
|
ButtonColorType,
|
||||||
} from 'antd/es/button';
|
} from 'antd/es/button';
|
||||||
|
import { IconType } from '@superset-ui/core/components/Icons/types';
|
||||||
import type { TooltipPlacement } from '../Tooltip/types';
|
import type { TooltipPlacement } from '../Tooltip/types';
|
||||||
|
|
||||||
export type { AntdButtonProps, ButtonType, ButtonVariantType, ButtonColorType };
|
export type { AntdButtonProps, ButtonType, ButtonVariantType, ButtonColorType };
|
||||||
@@ -48,5 +49,5 @@ export type ButtonProps = Omit<AntdButtonProps, 'css'> & {
|
|||||||
buttonStyle?: ButtonStyle;
|
buttonStyle?: ButtonStyle;
|
||||||
cta?: boolean;
|
cta?: boolean;
|
||||||
showMarginRight?: boolean;
|
showMarginRight?: boolean;
|
||||||
icon?: ReactNode;
|
icon?: IconType;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const Component = (props: DropdownContainerProps) => {
|
|||||||
const [overflowingState, setOverflowingState] = useState<OverflowingState>();
|
const [overflowingState, setOverflowingState] = useState<OverflowingState>();
|
||||||
const containerRef = useRef<DropdownRef>(null);
|
const containerRef = useRef<DropdownRef>(null);
|
||||||
const onOverflowingStateChange = useCallback(
|
const onOverflowingStateChange = useCallback(
|
||||||
(value: OverflowingState) => {
|
value => {
|
||||||
if (!isEqual(overflowingState, value)) {
|
if (!isEqual(overflowingState, value)) {
|
||||||
setItems(generateItems(value));
|
setItems(generateItems(value));
|
||||||
setOverflowingState(value);
|
setOverflowingState(value);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import type { CSSProperties, ReactElement, RefObject, ReactNode } from 'react';
|
import type { CSSProperties, ReactElement, RefObject, ReactNode } from 'react';
|
||||||
|
import { IconType } from '../Icons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container item.
|
* Container item.
|
||||||
@@ -69,7 +70,7 @@ export interface DropdownContainerProps {
|
|||||||
/**
|
/**
|
||||||
* Icon of the dropdown trigger.
|
* Icon of the dropdown trigger.
|
||||||
*/
|
*/
|
||||||
dropdownTriggerIcon?: ReactNode;
|
dropdownTriggerIcon?: IconType;
|
||||||
/**
|
/**
|
||||||
* Text of the dropdown trigger.
|
* Text of the dropdown trigger.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
import { fireEvent, render, screen, userEvent } from '@superset-ui/core/spec';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { DynamicEditableTitle } from '.';
|
|
||||||
|
|
||||||
const Harness = ({ initialTitle = 'Original' }: { initialTitle?: string }) => {
|
|
||||||
const [title, setTitle] = useState(initialTitle);
|
|
||||||
return (
|
|
||||||
<DynamicEditableTitle
|
|
||||||
title={title}
|
|
||||||
placeholder="placeholder"
|
|
||||||
canEdit
|
|
||||||
label="Title"
|
|
||||||
onSave={setTitle}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
test('rapid typing then backspacing keeps every keystroke', async () => {
|
|
||||||
render(<Harness />);
|
|
||||||
const input = screen.getByRole('textbox') as HTMLInputElement;
|
|
||||||
userEvent.click(input);
|
|
||||||
await userEvent.type(input, 'abc', { delay: 1 });
|
|
||||||
expect(input.value).toBe('Originalabc');
|
|
||||||
await userEvent.type(input, '{backspace}{backspace}{backspace}', {
|
|
||||||
delay: 1,
|
|
||||||
});
|
|
||||||
expect(input.value).toBe('Original');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('a change event that arrives before isEditing flips is not dropped', () => {
|
|
||||||
// Reproduces the regression: the input is focused but `isEditing` is still
|
|
||||||
// false because no click has been registered yet (e.g. focus arrived via
|
|
||||||
// tab, autofocus, or programmatic focus). The pre-fix `handleChange`
|
|
||||||
// bailed out with `!isEditing`, dropping the keystroke. Because the
|
|
||||||
// input is controlled, antd's internal `useMergedState` then resyncs the
|
|
||||||
// DOM value back to the (stale) `props.value`, so the user sees their
|
|
||||||
// typed character disappear. This test fires a raw change event so it
|
|
||||||
// doesn't go through userEvent's implicit click.
|
|
||||||
const onSave = jest.fn();
|
|
||||||
render(
|
|
||||||
<DynamicEditableTitle
|
|
||||||
title="Foo"
|
|
||||||
placeholder="placeholder"
|
|
||||||
canEdit
|
|
||||||
label="Title"
|
|
||||||
onSave={onSave}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const input = screen.getByRole('textbox') as HTMLInputElement;
|
|
||||||
fireEvent.change(input, { target: { value: 'FooX' } });
|
|
||||||
expect(input.value).toBe('FooX');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prop changes mid-edit do not clobber unsaved typing', async () => {
|
|
||||||
const { rerender } = render(<Harness initialTitle="Foo" />);
|
|
||||||
const input = screen.getByRole('textbox') as HTMLInputElement;
|
|
||||||
userEvent.click(input);
|
|
||||||
await userEvent.type(input, 'X', { delay: 1 });
|
|
||||||
expect(input.value).toBe('FooX');
|
|
||||||
rerender(<Harness initialTitle="Foo" />);
|
|
||||||
expect(input.value).toBe('FooX');
|
|
||||||
});
|
|
||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useLayoutEffect,
|
useLayoutEffect,
|
||||||
useRef,
|
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { t } from '@apache-superset/core/translation';
|
import { t } from '@apache-superset/core/translation';
|
||||||
@@ -31,7 +30,6 @@ import { css, SupersetTheme, useTheme } from '@apache-superset/core/theme';
|
|||||||
import { useResizeDetector } from 'react-resize-detector';
|
import { useResizeDetector } from 'react-resize-detector';
|
||||||
import { Tooltip } from '../Tooltip';
|
import { Tooltip } from '../Tooltip';
|
||||||
import { Input } from '../Input';
|
import { Input } from '../Input';
|
||||||
import type { InputRef } from '../Input';
|
|
||||||
import type { DynamicEditableTitleProps } from './types';
|
import type { DynamicEditableTitleProps } from './types';
|
||||||
|
|
||||||
const titleStyles = (theme: SupersetTheme) => css`
|
const titleStyles = (theme: SupersetTheme) => css`
|
||||||
@@ -77,10 +75,8 @@ export const DynamicEditableTitle = memo(
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [showTooltip, setShowTooltip] = useState(false);
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
const [currentTitle, setCurrentTitle] = useState(title || '');
|
const [currentTitle, setCurrentTitle] = useState(title || '');
|
||||||
const [inputWidth, setInputWidth] = useState<number>(0);
|
|
||||||
|
|
||||||
const sizerRef = useRef<HTMLSpanElement>(null);
|
const { width: inputWidth, ref: sizerRef } = useResizeDetector();
|
||||||
const inputRef = useRef<InputRef>(null);
|
|
||||||
const { width: containerWidth, ref: containerRef } = useResizeDetector({
|
const { width: containerWidth, ref: containerRef } = useResizeDetector({
|
||||||
refreshMode: 'debounce',
|
refreshMode: 'debounce',
|
||||||
});
|
});
|
||||||
@@ -89,33 +85,27 @@ export const DynamicEditableTitle = memo(
|
|||||||
setCurrentTitle(title);
|
setCurrentTitle(title);
|
||||||
}, [title]);
|
}, [title]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEditing) {
|
if (isEditing && sizerRef?.current) {
|
||||||
// move cursor and scroll to the end
|
// move cursor and scroll to the end
|
||||||
const inputElement = inputRef.current?.input;
|
if (sizerRef.current.setSelectionRange) {
|
||||||
if (inputElement) {
|
const { length } = sizerRef.current.value;
|
||||||
const { length } = inputElement.value;
|
sizerRef.current.setSelectionRange(length, length);
|
||||||
inputElement.setSelectionRange(length, length);
|
sizerRef.current.scrollLeft = sizerRef.current.scrollWidth;
|
||||||
inputElement.scrollLeft = inputElement.scrollWidth;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isEditing]);
|
}, [isEditing]);
|
||||||
|
|
||||||
// a trick to make the input grow when user types text
|
// a trick to make the input grow when user types text
|
||||||
// we make an additional span component, place it somewhere out of view and
|
// we make additional span component, place it somewhere out of view and copy input
|
||||||
// mirror the input value, then measure the span synchronously (pre-paint)
|
// then we can measure the width of that span to resize the input element
|
||||||
// to resize the input element. Reading offsetWidth in a useLayoutEffect
|
|
||||||
// forces a sync layout, so the input width updates in the same commit as
|
|
||||||
// the value change — preventing a flicker frame where the input is shown
|
|
||||||
// with new value but stale width.
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (sizerRef.current) {
|
if (sizerRef?.current) {
|
||||||
sizerRef.current.textContent = currentTitle || placeholder;
|
sizerRef.current.textContent = currentTitle || placeholder;
|
||||||
setInputWidth(sizerRef.current.offsetWidth);
|
|
||||||
}
|
}
|
||||||
}, [currentTitle, placeholder]);
|
}, [currentTitle, placeholder, sizerRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const inputElement = inputRef.current?.input;
|
const inputElement = sizerRef.current?.input;
|
||||||
|
|
||||||
if (inputElement) {
|
if (inputElement) {
|
||||||
if (inputElement.scrollWidth > inputElement.clientWidth) {
|
if (inputElement.scrollWidth > inputElement.clientWidth) {
|
||||||
@@ -147,17 +137,9 @@ export const DynamicEditableTitle = memo(
|
|||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(ev: ChangeEvent<HTMLInputElement>) => {
|
(ev: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!canEdit) {
|
if (!canEdit || !isEditing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Any change implies the user is editing. Ensure isEditing is true
|
|
||||||
// even if the change event arrives before the click handler has
|
|
||||||
// committed (e.g. focus via tab, autofocus, or batched click+type
|
|
||||||
// events). Otherwise the keystroke would be dropped and the
|
|
||||||
// controlled input would revert to the previous value.
|
|
||||||
if (!isEditing) {
|
|
||||||
setIsEditing(true);
|
|
||||||
}
|
|
||||||
setCurrentTitle(ev.target.value);
|
setCurrentTitle(ev.target.value);
|
||||||
},
|
},
|
||||||
[canEdit, isEditing],
|
[canEdit, isEditing],
|
||||||
@@ -186,7 +168,6 @@ export const DynamicEditableTitle = memo(
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
ref={inputRef}
|
|
||||||
data-test="editable-title-input"
|
data-test="editable-title-input"
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
aria-label={label ?? t('Title')}
|
aria-label={label ?? t('Title')}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import type { ReactNode, SyntheticEvent } from 'react';
|
import type { ReactNode, SyntheticEvent } from 'react';
|
||||||
|
import type { IconType } from '@superset-ui/core/components';
|
||||||
|
|
||||||
export type EmptyStateSize = 'small' | 'medium' | 'large';
|
export type EmptyStateSize = 'small' | 'medium' | 'large';
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ export type EmptyStateProps = {
|
|||||||
description?: ReactNode;
|
description?: ReactNode;
|
||||||
image?: ReactNode | string;
|
image?: ReactNode | string;
|
||||||
buttonText?: ReactNode;
|
buttonText?: ReactNode;
|
||||||
buttonIcon?: ReactNode;
|
buttonIcon?: IconType;
|
||||||
buttonAction?: (event: SyntheticEvent) => void;
|
buttonAction?: (event: SyntheticEvent) => void;
|
||||||
/** Controls image size. Defaults to 'medium'. */
|
/** Controls image size. Defaults to 'medium'. */
|
||||||
size?: EmptyStateSize;
|
size?: EmptyStateSize;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { Form as AntdForm } from 'antd';
|
|||||||
import { FormProps } from './types';
|
import { FormProps } from './types';
|
||||||
|
|
||||||
function CustomForm(props: FormProps) {
|
function CustomForm(props: FormProps) {
|
||||||
return <AntdForm {...(props as any)} />;
|
return <AntdForm {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Form = Object.assign(CustomForm, {
|
export const Form = Object.assign(CustomForm, {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ test('renders with monospace prop', () => {
|
|||||||
|
|
||||||
// test stories from the storybook!
|
// test stories from the storybook!
|
||||||
test('renders all the storybook gallery variants', () => {
|
test('renders all the storybook gallery variants', () => {
|
||||||
|
// @ts-expect-error: Suppress TypeScript error for LabelGallery usage
|
||||||
const { container } = render(<LabelGallery />);
|
const { container } = render(<LabelGallery />);
|
||||||
const nonInteractiveLabelCount = 4;
|
const nonInteractiveLabelCount = 4;
|
||||||
const renderedLabelCount = options.length * 2 + nonInteractiveLabelCount;
|
const renderedLabelCount = options.length * 2 + nonInteractiveLabelCount;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { Label } from '..';
|
|||||||
|
|
||||||
// Define the prop types for DatasetTypeLabel
|
// Define the prop types for DatasetTypeLabel
|
||||||
interface DatasetTypeLabelProps {
|
interface DatasetTypeLabelProps {
|
||||||
datasetType: 'physical' | 'virtual' | 'semantic_view';
|
datasetType: 'physical' | 'virtual'; // Accepts only 'physical' or 'virtual'
|
||||||
}
|
}
|
||||||
|
|
||||||
const SIZE = 's'; // Define the size as a constant
|
const SIZE = 's'; // Define the size as a constant
|
||||||
@@ -32,22 +32,6 @@ export const DatasetTypeLabel: React.FC<DatasetTypeLabelProps> = ({
|
|||||||
datasetType,
|
datasetType,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
if (datasetType === 'semantic_view') {
|
|
||||||
return (
|
|
||||||
<Label
|
|
||||||
icon={
|
|
||||||
<Icons.ApartmentOutlined
|
|
||||||
iconSize={SIZE}
|
|
||||||
iconColor={theme.colorInfo}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
type="info"
|
|
||||||
style={{ color: theme.colorInfo }}
|
|
||||||
>
|
|
||||||
{t('Semantic')}
|
|
||||||
</Label>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const isPhysical = datasetType === 'physical';
|
const isPhysical = datasetType === 'physical';
|
||||||
const label: string = isPhysical ? t('Physical') : t('Virtual');
|
const label: string = isPhysical ? t('Physical') : t('Virtual');
|
||||||
const labelType = isPhysical ? 'primary' : 'default';
|
const labelType = isPhysical ? 'primary' : 'default';
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import type { BackgroundPosition } from './ImageLoader';
|
|||||||
|
|
||||||
export interface LinkProps {
|
export interface LinkProps {
|
||||||
to: string;
|
to: string;
|
||||||
children?: ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListViewCardProps {
|
export interface ListViewCardProps {
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ const MetadataBar = ({ items, tooltipPlacement = 'top' }: MetadataBarProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onResize = useCallback(
|
const onResize = useCallback(
|
||||||
(width: number | undefined) => {
|
width => {
|
||||||
// Calculates the breakpoint width to collapse the bar.
|
// Calculates the breakpoint width to collapse the bar.
|
||||||
// The last item does not have a space, so we subtract SPACE_BETWEEN_ITEMS from the total.
|
// The last item does not have a space, so we subtract SPACE_BETWEEN_ITEMS from the total.
|
||||||
const breakpoint =
|
const breakpoint =
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function FormModal({
|
|||||||
}, [onSave, resetForm]);
|
}, [onSave, resetForm]);
|
||||||
|
|
||||||
const handleFormSubmit = useCallback(
|
const handleFormSubmit = useCallback(
|
||||||
async (values: object) => {
|
async values => {
|
||||||
try {
|
try {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
await formSubmitHandler(values);
|
await formSubmitHandler(values);
|
||||||
|
|||||||
@@ -104,9 +104,6 @@ export const StyledModal = styled(BaseModal)<StyledModalProps>`
|
|||||||
right: 0;
|
right: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
// Keep the close button clickable when modal body content uses
|
|
||||||
// position: sticky with elevated z-index (e.g. DatabaseModal header).
|
|
||||||
z-index: ${theme.zIndexPopupBase + 1};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-modal-close:hover {
|
.ant-modal-close:hover {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import type { CSSProperties, ReactNode } from 'react';
|
import type { CSSProperties, ReactNode } from 'react';
|
||||||
import type { FormInstance, ModalFuncProps } from 'antd';
|
import type { ModalFuncProps } from 'antd';
|
||||||
import type { ResizableProps } from 're-resizable';
|
import type { ResizableProps } from 're-resizable';
|
||||||
import type { DraggableProps } from 'react-draggable';
|
import type { DraggableProps } from 'react-draggable';
|
||||||
import { ButtonStyle } from '../Button/types';
|
import { ButtonStyle } from '../Button/types';
|
||||||
@@ -68,8 +68,7 @@ export interface StyledModalProps {
|
|||||||
|
|
||||||
export type { ModalFuncProps };
|
export type { ModalFuncProps };
|
||||||
|
|
||||||
export interface FormModalProps extends Omit<ModalProps, 'children'> {
|
export interface FormModalProps extends ModalProps {
|
||||||
children: ReactNode | ((form: FormInstance) => ReactNode);
|
|
||||||
initialValues?: object;
|
initialValues?: object;
|
||||||
formSubmitHandler: (values: object) => Promise<void>;
|
formSubmitHandler: (values: object) => Promise<void>;
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { ReactNode, ReactElement, memo } from 'react';
|
import { ReactNode, ReactElement } from 'react';
|
||||||
import { t } from '@apache-superset/core/translation';
|
import { t } from '@apache-superset/core/translation';
|
||||||
import { css, SupersetTheme, useTheme } from '@apache-superset/core/theme';
|
import { css, SupersetTheme, useTheme } from '@apache-superset/core/theme';
|
||||||
import { Icons } from '@superset-ui/core/components/Icons';
|
import { Icons } from '@superset-ui/core/components/Icons';
|
||||||
@@ -118,64 +118,62 @@ export type PageHeaderWithActionsProps = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PageHeaderWithActions = memo(
|
export const PageHeaderWithActions = ({
|
||||||
({
|
editableTitleProps,
|
||||||
editableTitleProps,
|
showTitlePanelItems,
|
||||||
showTitlePanelItems,
|
certificatiedBadgeProps,
|
||||||
certificatiedBadgeProps,
|
showFaveStar,
|
||||||
showFaveStar,
|
faveStarProps,
|
||||||
faveStarProps,
|
titlePanelAdditionalItems,
|
||||||
titlePanelAdditionalItems,
|
rightPanelAdditionalItems,
|
||||||
rightPanelAdditionalItems,
|
additionalActionsMenu,
|
||||||
additionalActionsMenu,
|
menuDropdownProps,
|
||||||
menuDropdownProps,
|
showMenuDropdown = true,
|
||||||
showMenuDropdown = true,
|
tooltipProps,
|
||||||
tooltipProps,
|
}: PageHeaderWithActionsProps) => {
|
||||||
}: PageHeaderWithActionsProps) => {
|
const theme = useTheme();
|
||||||
const theme = useTheme();
|
return (
|
||||||
return (
|
<div css={headerStyles} className="header-with-actions">
|
||||||
<div css={headerStyles} className="header-with-actions">
|
<div className="title-panel">
|
||||||
<div className="title-panel">
|
<DynamicEditableTitle {...editableTitleProps} />
|
||||||
<DynamicEditableTitle {...editableTitleProps} />
|
{showTitlePanelItems && (
|
||||||
{showTitlePanelItems && (
|
<div css={buttonsStyles}>
|
||||||
<div css={buttonsStyles}>
|
{certificatiedBadgeProps?.certifiedBy && (
|
||||||
{certificatiedBadgeProps?.certifiedBy && (
|
<CertifiedBadge {...certificatiedBadgeProps} />
|
||||||
<CertifiedBadge {...certificatiedBadgeProps} />
|
)}
|
||||||
)}
|
{showFaveStar && <FaveStar {...faveStarProps} />}
|
||||||
{showFaveStar && <FaveStar {...faveStarProps} />}
|
{titlePanelAdditionalItems}
|
||||||
{titlePanelAdditionalItems}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="right-button-panel">
|
||||||
|
{rightPanelAdditionalItems}
|
||||||
|
<div css={additionalActionsContainerStyles}>
|
||||||
|
{showMenuDropdown && (
|
||||||
|
<Dropdown
|
||||||
|
trigger={['click']}
|
||||||
|
popupRender={() => additionalActionsMenu}
|
||||||
|
{...menuDropdownProps}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
css={menuTriggerStyles}
|
||||||
|
buttonStyle="tertiary"
|
||||||
|
aria-label={t('Menu actions trigger')}
|
||||||
|
tooltip={tooltipProps?.text}
|
||||||
|
placement={tooltipProps?.placement}
|
||||||
|
data-test="actions-trigger"
|
||||||
|
>
|
||||||
|
<Icons.EllipsisOutlined
|
||||||
|
iconColor={theme.colorPrimary}
|
||||||
|
iconSize="l"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="right-button-panel">
|
|
||||||
{rightPanelAdditionalItems}
|
|
||||||
<div css={additionalActionsContainerStyles}>
|
|
||||||
{showMenuDropdown && (
|
|
||||||
<Dropdown
|
|
||||||
trigger={['click']}
|
|
||||||
popupRender={() => additionalActionsMenu}
|
|
||||||
{...menuDropdownProps}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<Button
|
|
||||||
css={menuTriggerStyles}
|
|
||||||
buttonStyle="tertiary"
|
|
||||||
aria-label={t('Menu actions trigger')}
|
|
||||||
tooltip={tooltipProps?.text}
|
|
||||||
placement={tooltipProps?.placement}
|
|
||||||
data-test="actions-trigger"
|
|
||||||
>
|
|
||||||
<Icons.EllipsisOutlined
|
|
||||||
iconColor={theme.colorPrimary}
|
|
||||||
iconSize="l"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
},
|
);
|
||||||
);
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { render, screen, fireEvent } from '@superset-ui/core/spec';
|
import { render, screen, fireEvent } from '@superset-ui/core/spec';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { TableInstance, useTable } from 'react-table';
|
import { TableInstance, useTable } from 'react-table';
|
||||||
import TableCollection from '.';
|
import TableCollection from '.';
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function mapColumns<T extends object>(
|
|||||||
return columns.map(column => {
|
return columns.map(column => {
|
||||||
const { isSorted, isSortedDesc } = getSortingInfo(headerGroups, column.id);
|
const { isSorted, isSortedDesc } = getSortingInfo(headerGroups, column.id);
|
||||||
return {
|
return {
|
||||||
title: column.Header as ReactNode,
|
title: column.Header,
|
||||||
dataIndex: column.id?.includes('.') ? column.id.split('.') : column.id,
|
dataIndex: column.id?.includes('.') ? column.id.split('.') : column.id,
|
||||||
hidden: column.hidden,
|
hidden: column.hidden,
|
||||||
key: column.id,
|
key: column.id,
|
||||||
@@ -121,7 +121,7 @@ export function mapColumns<T extends object>(
|
|||||||
column,
|
column,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return val as ReactNode;
|
return val;
|
||||||
},
|
},
|
||||||
className: column.className,
|
className: column.className,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,14 +19,6 @@
|
|||||||
import { render, screen, userEvent, waitFor } from '@superset-ui/core/spec';
|
import { render, screen, userEvent, waitFor } from '@superset-ui/core/spec';
|
||||||
import { TableView, TableViewProps } from '.';
|
import { TableView, TableViewProps } from '.';
|
||||||
|
|
||||||
// Mock window.scrollTo to prevent jsdom "Not implemented" errors
|
|
||||||
beforeAll(() => {
|
|
||||||
window.scrollTo = jest.fn();
|
|
||||||
});
|
|
||||||
afterAll(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
const mockedProps: TableViewProps = {
|
const mockedProps: TableViewProps = {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
@@ -133,25 +125,27 @@ test('should change page when pagination is clicked', async () => {
|
|||||||
expect(screen.getByText('Emily')).toBeInTheDocument();
|
expect(screen.getByText('Emily')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
|
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(screen.getByTitle('Next Page'));
|
const page2 = screen.getByRole('listitem', { name: '2' });
|
||||||
|
await userEvent.click(page2);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
expect(screen.getAllByRole('cell')).toHaveLength(3);
|
||||||
|
expect(screen.getByText('321')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('10')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Kate')).toBeInTheDocument();
|
expect(screen.getByText('Kate')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Emily')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
expect(screen.getAllByRole('cell')).toHaveLength(3);
|
|
||||||
expect(screen.getByText('321')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('10')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText('Emily')).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
await userEvent.click(screen.getByTitle('Previous Page'));
|
const page1 = screen.getByRole('listitem', { name: '1' });
|
||||||
|
await userEvent.click(page1);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
expect(screen.getAllByRole('cell')).toHaveLength(3);
|
||||||
|
expect(screen.getByText('123')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('27')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Emily')).toBeInTheDocument();
|
expect(screen.getByText('Emily')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
expect(screen.getAllByRole('cell')).toHaveLength(3);
|
|
||||||
expect(screen.getByText('123')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('27')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should sort by age', async () => {
|
test('should sort by age', async () => {
|
||||||
@@ -246,7 +240,8 @@ test('should handle server-side pagination', async () => {
|
|||||||
render(<TableView {...serverPaginationProps} />);
|
render(<TableView {...serverPaginationProps} />);
|
||||||
|
|
||||||
// Click next page
|
// Click next page
|
||||||
await userEvent.click(screen.getByTitle('Next Page'));
|
const page2 = screen.getByRole('listitem', { name: '2' });
|
||||||
|
await userEvent.click(page2);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(onServerPagination).toHaveBeenCalledWith({
|
expect(onServerPagination).toHaveBeenCalledWith({
|
||||||
@@ -306,7 +301,9 @@ test('should scroll to top when scrollTopOnPagination is true', async () => {
|
|||||||
};
|
};
|
||||||
render(<TableView {...scrollProps} />);
|
render(<TableView {...scrollProps} />);
|
||||||
|
|
||||||
await userEvent.click(screen.getByTitle('Next Page'));
|
// Click next page
|
||||||
|
const page2 = screen.getByRole('listitem', { name: '2' });
|
||||||
|
await userEvent.click(page2);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(scrollToSpy).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
|
expect(scrollToSpy).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
|
||||||
@@ -327,7 +324,9 @@ test('should NOT scroll to top when scrollTopOnPagination is false', async () =>
|
|||||||
};
|
};
|
||||||
render(<TableView {...scrollProps} />);
|
render(<TableView {...scrollProps} />);
|
||||||
|
|
||||||
await userEvent.click(screen.getByTitle('Next Page'));
|
// Click next page
|
||||||
|
const page2 = screen.getByRole('listitem', { name: '2' });
|
||||||
|
await userEvent.click(page2);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('321')).toBeInTheDocument();
|
expect(screen.getByText('321')).toBeInTheDocument();
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { memo, useEffect, useRef, useMemo, useCallback, useState } from 'react';
|
import { memo, useEffect, useRef, useMemo, useCallback } from 'react';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { styled } from '@apache-superset/core/theme';
|
import { styled } from '@apache-superset/core/theme';
|
||||||
import { useFilters, useSortBy, useTable } from 'react-table';
|
import { useFilters, usePagination, useSortBy, useTable } from 'react-table';
|
||||||
import { Empty } from '@superset-ui/core/components';
|
import { Empty } from '@superset-ui/core/components';
|
||||||
import TableCollection from '@superset-ui/core/components/TableCollection';
|
import TableCollection from '@superset-ui/core/components/TableCollection';
|
||||||
import { TableSize } from '@superset-ui/core/components/Table';
|
import { TableSize } from '@superset-ui/core/components/Table';
|
||||||
@@ -117,45 +117,43 @@ const RawTableView = ({
|
|||||||
...props
|
...props
|
||||||
}: TableViewProps) => {
|
}: TableViewProps) => {
|
||||||
const tableRef = useRef<HTMLTableElement>(null);
|
const tableRef = useRef<HTMLTableElement>(null);
|
||||||
const effectivePageSize = initialPageSize ?? DEFAULT_PAGE_SIZE;
|
|
||||||
const [pageIndex, setPageIndex] = useState(initialPageIndex ?? 0);
|
|
||||||
|
|
||||||
const initialState = useMemo(
|
const initialState = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
pageSize: effectivePageSize,
|
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
|
||||||
pageIndex: 0,
|
pageIndex: initialPageIndex ?? 0,
|
||||||
sortBy: initialSortBy,
|
sortBy: initialSortBy,
|
||||||
}),
|
}),
|
||||||
[effectivePageSize, initialSortBy],
|
[initialPageSize, initialPageIndex, initialSortBy],
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
getTableBodyProps,
|
getTableBodyProps,
|
||||||
headerGroups,
|
headerGroups,
|
||||||
|
page,
|
||||||
rows,
|
rows,
|
||||||
prepareRow,
|
prepareRow,
|
||||||
|
gotoPage,
|
||||||
setSortBy,
|
setSortBy,
|
||||||
state: { sortBy },
|
state: { pageIndex, sortBy },
|
||||||
} = useTable(
|
} = useTable(
|
||||||
{
|
{
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
initialState,
|
initialState,
|
||||||
manualPagination: true,
|
manualPagination: serverPagination,
|
||||||
manualSortBy: serverPagination,
|
manualSortBy: serverPagination,
|
||||||
|
pageCount: serverPagination
|
||||||
|
? Math.ceil(totalCount / initialState.pageSize)
|
||||||
|
: undefined,
|
||||||
autoResetSortBy: false,
|
autoResetSortBy: false,
|
||||||
},
|
},
|
||||||
useFilters,
|
useFilters,
|
||||||
useSortBy,
|
useSortBy,
|
||||||
|
...(withPagination ? [usePagination] : []),
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = useMemo(() => {
|
|
||||||
if (!withPagination || serverPagination) return rows;
|
|
||||||
const start = pageIndex * effectivePageSize;
|
|
||||||
return rows.slice(start, start + effectivePageSize);
|
|
||||||
}, [withPagination, serverPagination, rows, pageIndex, effectivePageSize]);
|
|
||||||
|
|
||||||
const EmptyWrapperComponent = useMemo(() => {
|
const EmptyWrapperComponent = useMemo(() => {
|
||||||
switch (emptyWrapperType) {
|
switch (emptyWrapperType) {
|
||||||
case EmptyWrapperType.Small:
|
case EmptyWrapperType.Small:
|
||||||
@@ -166,6 +164,11 @@ const RawTableView = ({
|
|||||||
}
|
}
|
||||||
}, [emptyWrapperType]);
|
}, [emptyWrapperType]);
|
||||||
|
|
||||||
|
const content = useMemo(
|
||||||
|
() => (withPagination ? page : rows),
|
||||||
|
[withPagination, page, rows],
|
||||||
|
);
|
||||||
|
|
||||||
const isEmpty = useMemo(
|
const isEmpty = useMemo(
|
||||||
() => !loading && content.length === 0,
|
() => !loading && content.length === 0,
|
||||||
[loading, content.length],
|
[loading, content.length],
|
||||||
@@ -189,9 +192,10 @@ const RawTableView = ({
|
|||||||
const handlePageChange = useCallback(
|
const handlePageChange = useCallback(
|
||||||
(p: number) => {
|
(p: number) => {
|
||||||
if (scrollTopOnPagination) handleScrollToTop();
|
if (scrollTopOnPagination) handleScrollToTop();
|
||||||
setPageIndex(p);
|
|
||||||
|
gotoPage(p);
|
||||||
},
|
},
|
||||||
[scrollTopOnPagination, handleScrollToTop],
|
[scrollTopOnPagination, handleScrollToTop, gotoPage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const paginationProps = useMemo(() => {
|
const paginationProps = useMemo(() => {
|
||||||
@@ -207,7 +211,7 @@ const RawTableView = ({
|
|||||||
if (serverPagination) {
|
if (serverPagination) {
|
||||||
return {
|
return {
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize: effectivePageSize,
|
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
|
||||||
totalCount,
|
totalCount,
|
||||||
onPageChange: handlePageChange,
|
onPageChange: handlePageChange,
|
||||||
};
|
};
|
||||||
@@ -215,7 +219,7 @@ const RawTableView = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageSize: effectivePageSize,
|
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
|
||||||
totalCount: data.length,
|
totalCount: data.length,
|
||||||
onPageChange: handlePageChange,
|
onPageChange: handlePageChange,
|
||||||
};
|
};
|
||||||
@@ -223,28 +227,28 @@ const RawTableView = ({
|
|||||||
withPagination,
|
withPagination,
|
||||||
serverPagination,
|
serverPagination,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
effectivePageSize,
|
initialPageSize,
|
||||||
totalCount,
|
totalCount,
|
||||||
data.length,
|
data.length,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serverPagination && pageIndex !== (initialPageIndex ?? 0)) {
|
if (serverPagination && pageIndex !== initialState.pageIndex) {
|
||||||
onServerPagination({
|
onServerPagination({
|
||||||
pageIndex,
|
pageIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [initialPageIndex, onServerPagination, pageIndex, serverPagination]);
|
}, [initialState.pageIndex, onServerPagination, pageIndex, serverPagination]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serverPagination && !isEqual(sortBy, initialSortBy)) {
|
if (serverPagination && !isEqual(sortBy, initialState.sortBy)) {
|
||||||
onServerPagination({
|
onServerPagination({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
sortBy,
|
sortBy,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [initialSortBy, onServerPagination, serverPagination, sortBy]);
|
}, [initialState.sortBy, onServerPagination, serverPagination, sortBy]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableViewStyles {...props} ref={tableRef}>
|
<TableViewStyles {...props} ref={tableRef}>
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ const StyledPlus = styled.span`
|
|||||||
|
|
||||||
export default function TruncatedList<ListItemType>({
|
export default function TruncatedList<ListItemType>({
|
||||||
items,
|
items,
|
||||||
renderVisibleItem = item => item as ReactNode,
|
renderVisibleItem = item => item,
|
||||||
renderTooltipItem = item => item as ReactNode,
|
renderTooltipItem = item => item,
|
||||||
getKey = item => item as unknown as Key,
|
getKey = item => item as unknown as Key,
|
||||||
maxLinks = 20,
|
maxLinks = 20,
|
||||||
}: TruncatedListProps<ListItemType>) {
|
}: TruncatedListProps<ListItemType>) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { useChangeEffect } from './useChangeEffect';
|
import { useChangeEffect } from './useChangeEffect';
|
||||||
|
|
||||||
test('call callback the first time with undefined and value', () => {
|
test('call callback the first time with undefined and value', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { useComponentDidMount } from './useComponentDidMount';
|
import { useComponentDidMount } from './useComponentDidMount';
|
||||||
|
|
||||||
test('the effect should only be executed on the first render', () => {
|
test('the effect should only be executed on the first render', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { useComponentDidUpdate } from './useComponentDidUpdate';
|
import { useComponentDidUpdate } from './useComponentDidUpdate';
|
||||||
|
|
||||||
test('the effect should not be executed on the first render', () => {
|
test('the effect should not be executed on the first render', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook, act } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { useElementOnScreen } from './useElementOnScreen';
|
import { useElementOnScreen } from './useElementOnScreen';
|
||||||
|
|
||||||
const observeMock = jest.fn();
|
const observeMock = jest.fn();
|
||||||
@@ -46,9 +46,10 @@ test('should return isSticky as true when intersectionRatio < 1', async () => {
|
|||||||
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
|
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
|
||||||
);
|
);
|
||||||
const callback = IntersectionObserverMock.mock.calls[0][0];
|
const callback = IntersectionObserverMock.mock.calls[0][0];
|
||||||
act(() => {
|
const callBack = callback([{ isIntersecting: true, intersectionRatio: 0.5 }]);
|
||||||
callback([{ isIntersecting: true, intersectionRatio: 0.5 }]);
|
const observer = new IntersectionObserverMock(callBack, {});
|
||||||
});
|
const newDiv = document.createElement('div');
|
||||||
|
observer.observe(newDiv);
|
||||||
expect(hook.result.current[1]).toEqual(true);
|
expect(hook.result.current[1]).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,9 +58,10 @@ test('should return isSticky as false when intersectionRatio >= 1', async () =>
|
|||||||
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
|
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
|
||||||
);
|
);
|
||||||
const callback = IntersectionObserverMock.mock.calls[0][0];
|
const callback = IntersectionObserverMock.mock.calls[0][0];
|
||||||
act(() => {
|
const callBack = callback([{ isIntersecting: true, intersectionRatio: 1 }]);
|
||||||
callback([{ isIntersecting: true, intersectionRatio: 1 }]);
|
const observer = new IntersectionObserverMock(callBack, {});
|
||||||
});
|
const newDiv = document.createElement('div');
|
||||||
|
observer.observe(newDiv);
|
||||||
expect(hook.result.current[1]).toEqual(false);
|
expect(hook.result.current[1]).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { usePrevious } from './usePrevious';
|
import { usePrevious } from './usePrevious';
|
||||||
|
|
||||||
test('get undefined on the first render when initialValue is not defined', () => {
|
test('get undefined on the first render when initialValue is not defined', () => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import useCSSTextTruncation from './useCSSTextTruncation';
|
import useCSSTextTruncation from './useCSSTextTruncation';
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
import useChildElementTruncation from './useChildElementTruncation';
|
import useChildElementTruncation from './useChildElementTruncation';
|
||||||
|
|
||||||
|
|||||||
@@ -19,15 +19,6 @@
|
|||||||
|
|
||||||
import { DatasourceType } from './types/Datasource';
|
import { DatasourceType } from './types/Datasource';
|
||||||
|
|
||||||
const DATASOURCE_TYPE_MAP: Record<string, DatasourceType> = {
|
|
||||||
table: DatasourceType.Table,
|
|
||||||
query: DatasourceType.Query,
|
|
||||||
dataset: DatasourceType.Dataset,
|
|
||||||
sl_table: DatasourceType.SlTable,
|
|
||||||
saved_query: DatasourceType.SavedQuery,
|
|
||||||
semantic_view: DatasourceType.SemanticView,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class DatasourceKey {
|
export default class DatasourceKey {
|
||||||
readonly id: number;
|
readonly id: number;
|
||||||
|
|
||||||
@@ -36,7 +27,8 @@ export default class DatasourceKey {
|
|||||||
constructor(key: string) {
|
constructor(key: string) {
|
||||||
const [idStr, typeStr] = key.split('__');
|
const [idStr, typeStr] = key.split('__');
|
||||||
this.id = parseInt(idStr, 10);
|
this.id = parseInt(idStr, 10);
|
||||||
this.type = DATASOURCE_TYPE_MAP[typeStr] ?? DatasourceType.Table;
|
this.type = DatasourceType.Table; // default to SqlaTable model
|
||||||
|
this.type = typeStr === 'query' ? DatasourceType.Query : this.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public toString() {
|
public toString() {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export enum DatasourceType {
|
|||||||
Dataset = 'dataset',
|
Dataset = 'dataset',
|
||||||
SlTable = 'sl_table',
|
SlTable = 'sl_table',
|
||||||
SavedQuery = 'saved_query',
|
SavedQuery = 'saved_query',
|
||||||
SemanticView = 'semantic_view',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Currency {
|
export interface Currency {
|
||||||
@@ -41,13 +40,6 @@ export interface Datasource {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
type: DatasourceType;
|
type: DatasourceType;
|
||||||
/**
|
|
||||||
* The parent resource that owns this datasource.
|
|
||||||
* For SQL-based datasets this is the database; for semantic views it is the
|
|
||||||
* semantic layer. Use this field instead of the legacy `database` field when
|
|
||||||
* you only need the display name.
|
|
||||||
*/
|
|
||||||
parent?: { name: string };
|
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
metrics: Metric[];
|
metrics: Metric[];
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|||||||
@@ -249,8 +249,7 @@ export type Extensions = Partial<{
|
|||||||
'navbar.right-menu.item.icon': ComponentType<RightMenuItemIconProps>;
|
'navbar.right-menu.item.icon': ComponentType<RightMenuItemIconProps>;
|
||||||
'navbar.right': ComponentType;
|
'navbar.right': ComponentType;
|
||||||
'report-modal.dropdown.item.icon': ComponentType;
|
'report-modal.dropdown.item.icon': ComponentType;
|
||||||
'root.context.provider': ComponentType<{ children?: ReactNode }>;
|
'root.context.provider': ComponentType;
|
||||||
|
|
||||||
'welcome.message': ComponentType;
|
'welcome.message': ComponentType;
|
||||||
'welcome.banner': ComponentType;
|
'welcome.banner': ComponentType;
|
||||||
'welcome.main.replacement': ComponentType;
|
'welcome.main.replacement': ComponentType;
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ export enum FeatureFlag {
|
|||||||
ListviewsDefaultCardView = 'LISTVIEWS_DEFAULT_CARD_VIEW',
|
ListviewsDefaultCardView = 'LISTVIEWS_DEFAULT_CARD_VIEW',
|
||||||
Matrixify = 'MATRIXIFY',
|
Matrixify = 'MATRIXIFY',
|
||||||
ScheduledQueries = 'SCHEDULED_QUERIES',
|
ScheduledQueries = 'SCHEDULED_QUERIES',
|
||||||
SemanticLayers = 'SEMANTIC_LAYERS',
|
|
||||||
SqllabBackendPersistence = 'SQLLAB_BACKEND_PERSISTENCE',
|
SqllabBackendPersistence = 'SQLLAB_BACKEND_PERSISTENCE',
|
||||||
SqlValidatorsByEngine = 'SQL_VALIDATORS_BY_ENGINE',
|
SqlValidatorsByEngine = 'SQL_VALIDATORS_BY_ENGINE',
|
||||||
SshTunneling = 'SSH_TUNNELING',
|
SshTunneling = 'SSH_TUNNELING',
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ describe('SuperChart', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(await screen.findByText('Custom Fallback!')).toBeInTheDocument();
|
expect(await screen.findByText('Custom Fallback!')).toBeInTheDocument();
|
||||||
expect(CustomFallbackComponent).toHaveBeenCalled();
|
expect(CustomFallbackComponent).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
test('call onErrorBoundary', async () => {
|
test('call onErrorBoundary', async () => {
|
||||||
expectedErrors = 1;
|
expectedErrors = 1;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { ScaleOrdinal } from 'd3-scale';
|
|||||||
import {
|
import {
|
||||||
CategoricalColorScale,
|
CategoricalColorScale,
|
||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
getLabelsColorMap,
|
|
||||||
LabelsColorMapSource,
|
LabelsColorMapSource,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
@@ -200,42 +199,10 @@ describe('CategoricalColorScale', () => {
|
|||||||
const returnedColor = scale.getColor(value, sliceId);
|
const returnedColor = scale.getColor(value, sliceId);
|
||||||
expect(returnedColor).toBe(expectedColor);
|
expect(returnedColor).toBe(expectedColor);
|
||||||
});
|
});
|
||||||
test('reassigns colliding colors when no sliceId is provided', () => {
|
|
||||||
window.featureFlags = {
|
|
||||||
[FeatureFlag.AvoidColorsCollision]: true,
|
|
||||||
};
|
|
||||||
const PALETTE = ['red', 'blue', 'green'];
|
|
||||||
|
|
||||||
const chartAScale = new CategoricalColorScale(PALETTE);
|
|
||||||
const labelsColorMap = chartAScale.labelsColorMapInstance;
|
|
||||||
labelsColorMap.reset();
|
|
||||||
labelsColorMap.source = LabelsColorMapSource.Dashboard;
|
|
||||||
|
|
||||||
try {
|
|
||||||
chartAScale.getColor('Trains', 101, 'testScheme');
|
|
||||||
|
|
||||||
const chartBScale = new CategoricalColorScale(PALETTE);
|
|
||||||
// Call getColor without sliceId (or with undefined)
|
|
||||||
chartBScale.getColor('Classic Cars', undefined, 'testScheme');
|
|
||||||
chartBScale.getColor('Trains', undefined, 'testScheme');
|
|
||||||
|
|
||||||
const classicCarsColor =
|
|
||||||
chartBScale.chartLabelsColorMap.get('Classic Cars');
|
|
||||||
const trainsColor = chartBScale.chartLabelsColorMap.get('Trains');
|
|
||||||
|
|
||||||
expect(trainsColor).toBe('red');
|
|
||||||
expect(classicCarsColor).toBeDefined();
|
|
||||||
expect(classicCarsColor).not.toBe('red');
|
|
||||||
} finally {
|
|
||||||
labelsColorMap.reset();
|
|
||||||
labelsColorMap.source = LabelsColorMapSource.Dashboard;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
test('conditionally calls getNextAvailableColor', () => {
|
test('conditionally calls getNextAvailableColor', () => {
|
||||||
window.featureFlags = {
|
window.featureFlags = {
|
||||||
[FeatureFlag.AvoidColorsCollision]: true,
|
[FeatureFlag.AvoidColorsCollision]: true,
|
||||||
};
|
};
|
||||||
scale.labelsColorMapInstance.source = LabelsColorMapSource.Explore;
|
|
||||||
|
|
||||||
scale.getColor('testValue1');
|
scale.getColor('testValue1');
|
||||||
scale.getColor('testValue2');
|
scale.getColor('testValue2');
|
||||||
@@ -258,27 +225,6 @@ describe('CategoricalColorScale', () => {
|
|||||||
|
|
||||||
expect(getNextAvailableColorSpy).not.toHaveBeenCalled();
|
expect(getNextAvailableColorSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
test('reassigns non-forced labels when a dashboard-synced label would duplicate their color', () => {
|
|
||||||
window.featureFlags = {
|
|
||||||
[FeatureFlag.AvoidColorsCollision]: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dashScale = new CategoricalColorScale(['red', 'blue', 'green']);
|
|
||||||
const sliceId = 501;
|
|
||||||
const colorScheme = 'preset';
|
|
||||||
|
|
||||||
dashScale.labelsColorMapInstance.source = LabelsColorMapSource.Dashboard;
|
|
||||||
jest
|
|
||||||
.spyOn(dashScale.labelsColorMapInstance, 'getColorMap')
|
|
||||||
.mockReturnValue(new Map([['Trains', 'red']]));
|
|
||||||
|
|
||||||
dashScale.getColor('Classic Cars', sliceId, colorScheme);
|
|
||||||
dashScale.getColor('Trains', sliceId, colorScheme);
|
|
||||||
|
|
||||||
expect(dashScale.chartLabelsColorMap.get('Trains')).toBe('red');
|
|
||||||
expect(dashScale.chartLabelsColorMap.get('Classic Cars')).not.toBe('red');
|
|
||||||
expect(dashScale.chartLabelsColorMap.get('Classic Cars')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.setColor(value, forcedColor)', () => {
|
describe('.setColor(value, forcedColor)', () => {
|
||||||
@@ -533,131 +479,6 @@ describe('CategoricalColorScale', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('dashboard shared-dimension color collision', () => {
|
|
||||||
let labelsColorMap: ReturnType<typeof getLabelsColorMap>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
window.featureFlags = {
|
|
||||||
[FeatureFlag.AvoidColorsCollision]: true,
|
|
||||||
};
|
|
||||||
const sentinel = new CategoricalColorScale(['red', 'blue', 'green']);
|
|
||||||
labelsColorMap = sentinel.labelsColorMapInstance;
|
|
||||||
labelsColorMap.reset();
|
|
||||||
labelsColorMap.source = LabelsColorMapSource.Dashboard;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.restoreAllMocks();
|
|
||||||
labelsColorMap.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('reproduces the bug without the fix: Classic Cars and Trains would both be red', () => {
|
|
||||||
window.featureFlags = {
|
|
||||||
[FeatureFlag.AvoidColorsCollision]: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PALETTE = ['red', 'blue', 'green'];
|
|
||||||
|
|
||||||
const chartAScale = new CategoricalColorScale(PALETTE);
|
|
||||||
chartAScale.getColor('Trains', 101, 'testScheme');
|
|
||||||
expect(labelsColorMap.getColorMap().get('Trains')).toBe('red');
|
|
||||||
|
|
||||||
const chartBScale = new CategoricalColorScale(PALETTE);
|
|
||||||
chartBScale.getColor('Classic Cars', 102, 'testScheme');
|
|
||||||
chartBScale.getColor('Trains', 102, 'testScheme');
|
|
||||||
|
|
||||||
const classicCarsColor =
|
|
||||||
chartBScale.chartLabelsColorMap.get('Classic Cars');
|
|
||||||
const trainsColor = chartBScale.chartLabelsColorMap.get('Trains');
|
|
||||||
|
|
||||||
expect(trainsColor).toBe('red');
|
|
||||||
expect(classicCarsColor).toBe('red');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fix: Classic Cars is reassigned when Trains locks red from the dashboard', () => {
|
|
||||||
const PALETTE = ['red', 'blue', 'green'];
|
|
||||||
|
|
||||||
const chartAScale = new CategoricalColorScale(PALETTE);
|
|
||||||
chartAScale.getColor('Trains', 101, 'testScheme');
|
|
||||||
expect(labelsColorMap.getColorMap().get('Trains')).toBe('red');
|
|
||||||
|
|
||||||
const chartBScale = new CategoricalColorScale(PALETTE);
|
|
||||||
chartBScale.getColor('Classic Cars', 102, 'testScheme');
|
|
||||||
chartBScale.getColor('Trains', 102, 'testScheme');
|
|
||||||
|
|
||||||
const classicCarsColor =
|
|
||||||
chartBScale.chartLabelsColorMap.get('Classic Cars');
|
|
||||||
const trainsColor = chartBScale.chartLabelsColorMap.get('Trains');
|
|
||||||
|
|
||||||
expect(trainsColor).toBe('red');
|
|
||||||
expect(classicCarsColor).toBeDefined();
|
|
||||||
expect(classicCarsColor).not.toBe('red');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fix: no series in Chart B share a color when palette has enough colors', () => {
|
|
||||||
const PALETTE = ['red', 'blue', 'green'];
|
|
||||||
|
|
||||||
const chartAScale = new CategoricalColorScale(PALETTE);
|
|
||||||
chartAScale.getColor('Trains', 101, 'testScheme');
|
|
||||||
|
|
||||||
const chartBScale = new CategoricalColorScale(PALETTE);
|
|
||||||
chartBScale.getColor('Classic Cars', 102, 'testScheme');
|
|
||||||
chartBScale.getColor('Trains', 102, 'testScheme');
|
|
||||||
|
|
||||||
const colors = Array.from(chartBScale.chartLabelsColorMap.values());
|
|
||||||
const uniqueColors = new Set(colors);
|
|
||||||
|
|
||||||
expect(uniqueColors.size).toBe(colors.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fix: increments analogous color range for dashboard collisions when UseAnalogousColors is enabled', () => {
|
|
||||||
window.featureFlags = {
|
|
||||||
[FeatureFlag.AvoidColorsCollision]: true,
|
|
||||||
[FeatureFlag.UseAnalogousColors]: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PALETTE = ['red', 'blue', 'green'];
|
|
||||||
|
|
||||||
const chartAScale = new CategoricalColorScale(PALETTE);
|
|
||||||
chartAScale.getColor('Trains', 101, 'testScheme');
|
|
||||||
|
|
||||||
const chartBScale = new CategoricalColorScale(PALETTE);
|
|
||||||
const addSliceSpy = jest.spyOn(
|
|
||||||
chartBScale.labelsColorMapInstance,
|
|
||||||
'addSlice',
|
|
||||||
);
|
|
||||||
chartBScale.getColor('Classic Cars', 102, 'testScheme');
|
|
||||||
chartBScale.getColor('Model T', 102, 'testScheme');
|
|
||||||
chartBScale.getColor('Trains', 102, 'testScheme');
|
|
||||||
|
|
||||||
expect(chartBScale.chartLabelsColorMap.get('Trains')).toBe('red');
|
|
||||||
expect(chartBScale.chartLabelsColorMap.get('Classic Cars')).toBeDefined();
|
|
||||||
expect(chartBScale.chartLabelsColorMap.get('Classic Cars')).not.toBe(
|
|
||||||
'red',
|
|
||||||
);
|
|
||||||
expect(chartBScale.range()).toHaveLength(6);
|
|
||||||
expect(
|
|
||||||
addSliceSpy.mock.calls.some(
|
|
||||||
([label, color]) => label === 'Classic Cars' && color !== 'red',
|
|
||||||
),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fix: forced colors (user-set in dashboard JSON) are never reassigned', () => {
|
|
||||||
const PALETTE = ['red', 'blue', 'green'];
|
|
||||||
const forcedColors = { 'Classic Cars': 'red' };
|
|
||||||
|
|
||||||
const chartAScale = new CategoricalColorScale(PALETTE);
|
|
||||||
chartAScale.getColor('Trains', 101, 'testScheme');
|
|
||||||
|
|
||||||
const chartBScale = new CategoricalColorScale(PALETTE, forcedColors);
|
|
||||||
chartBScale.getColor('Classic Cars', 102, 'testScheme');
|
|
||||||
chartBScale.getColor('Trains', 102, 'testScheme');
|
|
||||||
|
|
||||||
expect(chartBScale.chartLabelsColorMap.get('Classic Cars')).toBe('red');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("is compatible with D3's ScaleOrdinal", () => {
|
describe("is compatible with D3's ScaleOrdinal", () => {
|
||||||
test('passes type check', () => {
|
test('passes type check', () => {
|
||||||
const scale: ScaleOrdinal<{ toString(): string }, string> =
|
const scale: ScaleOrdinal<{ toString(): string }, string> =
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
import fetchMock from 'fetch-mock';
|
import fetchMock from 'fetch-mock';
|
||||||
|
|
||||||
import { SupersetClient, SupersetClientClass } from '@superset-ui/core';
|
import { SupersetClient, SupersetClientClass } from '@superset-ui/core';
|
||||||
import type { SupersetClientInterface } from '@superset-ui/core';
|
|
||||||
import { LOGIN_GLOB } from './fixtures/constants';
|
import { LOGIN_GLOB } from './fixtures/constants';
|
||||||
|
|
||||||
beforeAll(() => fetchMock.mockGlobal());
|
beforeAll(() => fetchMock.mockGlobal());
|
||||||
@@ -32,10 +31,6 @@ describe('SupersetClient', () => {
|
|||||||
|
|
||||||
afterEach(() => SupersetClient.reset());
|
afterEach(() => SupersetClient.reset());
|
||||||
|
|
||||||
const clientWithGetUrl = SupersetClient as SupersetClientInterface & {
|
|
||||||
getUrl: (...args: unknown[]) => string;
|
|
||||||
};
|
|
||||||
|
|
||||||
test('exposes configure, init, get, post, postForm, delete, put, request, reset, getGuestToken, getCSRFToken, getUrl, isAuthenticated, and reAuthenticate methods', () => {
|
test('exposes configure, init, get, post, postForm, delete, put, request, reset, getGuestToken, getCSRFToken, getUrl, isAuthenticated, and reAuthenticate methods', () => {
|
||||||
expect(typeof SupersetClient.configure).toBe('function');
|
expect(typeof SupersetClient.configure).toBe('function');
|
||||||
expect(typeof SupersetClient.init).toBe('function');
|
expect(typeof SupersetClient.init).toBe('function');
|
||||||
@@ -48,7 +43,7 @@ describe('SupersetClient', () => {
|
|||||||
expect(typeof SupersetClient.reset).toBe('function');
|
expect(typeof SupersetClient.reset).toBe('function');
|
||||||
expect(typeof SupersetClient.getGuestToken).toBe('function');
|
expect(typeof SupersetClient.getGuestToken).toBe('function');
|
||||||
expect(typeof SupersetClient.getCSRFToken).toBe('function');
|
expect(typeof SupersetClient.getCSRFToken).toBe('function');
|
||||||
expect(typeof clientWithGetUrl.getUrl).toBe('function');
|
expect(typeof SupersetClient.getUrl).toBe('function');
|
||||||
expect(typeof SupersetClient.isAuthenticated).toBe('function');
|
expect(typeof SupersetClient.isAuthenticated).toBe('function');
|
||||||
expect(typeof SupersetClient.reAuthenticate).toBe('function');
|
expect(typeof SupersetClient.reAuthenticate).toBe('function');
|
||||||
});
|
});
|
||||||
@@ -63,7 +58,7 @@ describe('SupersetClient', () => {
|
|||||||
expect(SupersetClient.request).toThrow();
|
expect(SupersetClient.request).toThrow();
|
||||||
expect(SupersetClient.getGuestToken).toThrow();
|
expect(SupersetClient.getGuestToken).toThrow();
|
||||||
expect(SupersetClient.getCSRFToken).toThrow();
|
expect(SupersetClient.getCSRFToken).toThrow();
|
||||||
expect(clientWithGetUrl.getUrl).toThrow();
|
expect(SupersetClient.getUrl).toThrow();
|
||||||
expect(SupersetClient.isAuthenticated).toThrow();
|
expect(SupersetClient.isAuthenticated).toThrow();
|
||||||
expect(SupersetClient.reAuthenticate).toThrow();
|
expect(SupersetClient.reAuthenticate).toThrow();
|
||||||
expect(SupersetClient.configure).not.toThrow();
|
expect(SupersetClient.configure).not.toThrow();
|
||||||
@@ -105,7 +100,7 @@ describe('SupersetClient', () => {
|
|||||||
const getUrlSpy = jest.spyOn(SupersetClientClass.prototype, 'getUrl');
|
const getUrlSpy = jest.spyOn(SupersetClientClass.prototype, 'getUrl');
|
||||||
|
|
||||||
SupersetClient.configure({ appRoot: '/app' });
|
SupersetClient.configure({ appRoot: '/app' });
|
||||||
expect(clientWithGetUrl.getUrl({ endpoint: '/some/path' })).toContain(
|
expect(SupersetClient.getUrl({ endpoint: '/some/path' })).toContain(
|
||||||
'/app/some/path',
|
'/app/some/path',
|
||||||
);
|
);
|
||||||
expect(getUrlSpy).toHaveBeenCalledTimes(1);
|
expect(getUrlSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -28,11 +28,10 @@ test('DEFAULT_METRICS', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('DatasourceType', () => {
|
test('DatasourceType', () => {
|
||||||
expect(Object.keys(DatasourceType).length).toBe(6);
|
expect(Object.keys(DatasourceType).length).toBe(5);
|
||||||
expect(DatasourceType.Table).toBe('table');
|
expect(DatasourceType.Table).toBe('table');
|
||||||
expect(DatasourceType.Query).toBe('query');
|
expect(DatasourceType.Query).toBe('query');
|
||||||
expect(DatasourceType.Dataset).toBe('dataset');
|
expect(DatasourceType.Dataset).toBe('dataset');
|
||||||
expect(DatasourceType.SlTable).toBe('sl_table');
|
expect(DatasourceType.SlTable).toBe('sl_table');
|
||||||
expect(DatasourceType.SavedQuery).toBe('saved_query');
|
expect(DatasourceType.SavedQuery).toBe('saved_query');
|
||||||
expect(DatasourceType.SemanticView).toBe('semantic_view');
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -71,16 +71,10 @@ describe('TimeFormatter', () => {
|
|||||||
// PivotData.processRecord coerces values with String(), turning numeric
|
// PivotData.processRecord coerces values with String(), turning numeric
|
||||||
// timestamps into strings.
|
// timestamps into strings.
|
||||||
const timestamp = PREVIEW_TIME.getTime().toString();
|
const timestamp = PREVIEW_TIME.getTime().toString();
|
||||||
expect(formatter.format(timestamp as unknown as number | Date)).toEqual(
|
expect(formatter.format(timestamp)).toEqual('2017');
|
||||||
'2017',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
test('handles ISO-8601 string without misinterpreting it as a number', () => {
|
test('handles ISO-8601 string without misinterpreting it as a number', () => {
|
||||||
expect(
|
expect(formatter.format('2017-02-14T11:22:33.000Z')).toEqual('2017');
|
||||||
formatter.format(
|
|
||||||
'2017-02-14T11:22:33.000Z' as unknown as number | Date,
|
|
||||||
),
|
|
||||||
).toEqual('2017');
|
|
||||||
});
|
});
|
||||||
test('otherwise returns formatted value', () => {
|
test('otherwise returns formatted value', () => {
|
||||||
expect(formatter.format(PREVIEW_TIME)).toEqual('2017');
|
expect(formatter.format(PREVIEW_TIME)).toEqual('2017');
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -34,6 +34,6 @@
|
|||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -36,6 +36,6 @@
|
|||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^12.1.5",
|
||||||
"react": "^18.2.0",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^17.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -39,6 +39,6 @@
|
|||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,6 @@
|
|||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"d3-array": "^3.2.4",
|
"d3-array": "^3.2.4",
|
||||||
"lodash": "^4.18.1",
|
"lodash": "^4.18.1",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^5.2.1",
|
||||||
"react-table": "^7.8.0",
|
"react-table": "^7.8.0",
|
||||||
"regenerator-runtime": "^0.14.1",
|
"regenerator-runtime": "^0.14.1",
|
||||||
"xss": "^1.0.15"
|
"xss": "^1.0.15"
|
||||||
@@ -39,13 +39,14 @@
|
|||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
"@superset-ui/chart-controls": "*",
|
"@superset-ui/chart-controls": "*",
|
||||||
"@superset-ui/core": "*",
|
"@superset-ui/core": "*",
|
||||||
"@testing-library/dom": "^9.3.4",
|
"@testing-library/dom": "^8.20.1",
|
||||||
"@testing-library/jest-dom": "*",
|
"@testing-library/jest-dom": "*",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^12.1.5",
|
||||||
|
"@testing-library/react-hooks": "*",
|
||||||
"@testing-library/user-event": "*",
|
"@testing-library/user-event": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"react": "^18.2.0",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^17.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
|||||||
[serverPagination, debouncedSearch, searchId],
|
[serverPagination, debouncedSearch, searchId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleColSort = (colId: string, sortDir: string | null) => {
|
const handleColSort = (colId: string, sortDir: string) => {
|
||||||
const isSortable = shouldSort({
|
const isSortable = shouldSort({
|
||||||
colId,
|
colId,
|
||||||
sortDir,
|
sortDir,
|
||||||
@@ -301,12 +301,10 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleColumnHeaderClick = useCallback(
|
const handleColumnHeaderClick = useCallback(
|
||||||
(params: { column?: { colId?: string; sort?: string | null } }) => {
|
params => {
|
||||||
const colId = params?.column?.colId;
|
const colId = params?.column?.colId;
|
||||||
const sortDir = params?.column?.sort;
|
const sortDir = params?.column?.sort;
|
||||||
if (colId && sortDir !== undefined) {
|
handleColSort(colId, sortDir);
|
||||||
handleColSort(colId, sortDir);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[serverPagination, gridInitialState, percentMetrics, onSortChange],
|
[serverPagination, gridInitialState, percentMetrics, onSortChange],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const handleColumnStateChange = useCallback(
|
const handleColumnStateChange = useCallback(
|
||||||
(agGridState: Record<string, unknown>) => {
|
agGridState => {
|
||||||
if (onChartStateChange) {
|
if (onChartStateChange) {
|
||||||
onChartStateChange(agGridState);
|
onChartStateChange(agGridState);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,9 +70,5 @@ export const TextCellRenderer = (params: CellRendererProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <div>{valueFormatted ?? value}</div>;
|
||||||
<div>
|
|
||||||
{valueFormatted ?? (value instanceof Date ? value.toISOString() : value)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const shouldSort = ({
|
|||||||
gridInitialState,
|
gridInitialState,
|
||||||
}: {
|
}: {
|
||||||
colId: string;
|
colId: string;
|
||||||
sortDir: string | null;
|
sortDir: string;
|
||||||
percentMetrics: string[];
|
percentMetrics: string[];
|
||||||
serverPagination: boolean;
|
serverPagination: boolean;
|
||||||
gridInitialState: GridState;
|
gridInitialState: GridState;
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regression coverage for memoize-one v6 adoption.
|
|
||||||
*
|
|
||||||
* memoize-one v6 changed the signature of the (optional) custom `isEqual`
|
|
||||||
* callback from per-argument `(a, b) => bool` to arg-array
|
|
||||||
* `(newArgs, lastArgs) => bool`. Of the four memoizeOne callsites in
|
|
||||||
* `src/transformProps.ts` (`processComparisonDataRecords`,
|
|
||||||
* `processDataRecords`, `processColumns`, `getBasicColorFormatter`), only
|
|
||||||
* `processColumns` passes a custom comparator (`isEqualColumns`); its
|
|
||||||
* signature already takes arg-arrays and is compatible with v6. The other
|
|
||||||
* three rely on memoize-one's default referential-equality comparator, which
|
|
||||||
* is unchanged between v5 and v6.
|
|
||||||
*
|
|
||||||
* These tests lock those assumptions in by observing the memoization
|
|
||||||
* behavior through the public `transformProps` API: identical chart-props
|
|
||||||
* input references should produce referentially-equal `data` and `columns`
|
|
||||||
* arrays (cache hit), while inputs that differ on the sub-fields each
|
|
||||||
* memoizer actually compares should produce fresh arrays (cache miss).
|
|
||||||
*/
|
|
||||||
import transformProps from '../src/transformProps';
|
|
||||||
import testData from '../../plugin-chart-table/test/testData';
|
|
||||||
|
|
||||||
test('transformProps returns referentially-equal data/columns on identical input (cache hit)', () => {
|
|
||||||
// processColumns and processDataRecords are both wrapped by memoizeOne at
|
|
||||||
// module scope. Two consecutive calls with the same chartProps reference
|
|
||||||
// should hit both caches and yield the same output references.
|
|
||||||
const first = transformProps(testData.basic);
|
|
||||||
const second = transformProps(testData.basic);
|
|
||||||
|
|
||||||
expect(second.columns).toBe(first.columns);
|
|
||||||
expect(second.data).toBe(first.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('transformProps busts its memoization caches when sub-field inputs change (cache miss)', () => {
|
|
||||||
const first = transformProps(testData.basic);
|
|
||||||
|
|
||||||
// `processColumns` is wrapped with a custom equality (`isEqualColumns`) that
|
|
||||||
// compares specific chartProps sub-fields by identity — mutating only the
|
|
||||||
// top-level props reference is NOT enough to bust it. Here we supply a fresh
|
|
||||||
// `datasource.columnFormats` reference, which `isEqualColumns` compares with
|
|
||||||
// `===`, forcing `processColumns` to recompute and return a new `columns`
|
|
||||||
// array.
|
|
||||||
//
|
|
||||||
// `processDataRecords` uses memoize-one's default referential equality on
|
|
||||||
// `(data, columns)`. We also hand it a fresh `queriesData[0].data` array, so
|
|
||||||
// together with the recomputed `columns` reference it too cache-misses.
|
|
||||||
const freshProps = {
|
|
||||||
...testData.basic,
|
|
||||||
datasource: {
|
|
||||||
...testData.basic.datasource,
|
|
||||||
columnFormats: {},
|
|
||||||
},
|
|
||||||
queriesData: [
|
|
||||||
{
|
|
||||||
...testData.basic.queriesData[0],
|
|
||||||
data: [...(testData.basic.queriesData[0].data || [])],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const second = transformProps(freshProps);
|
|
||||||
|
|
||||||
expect(second.columns).not.toBe(first.columns);
|
|
||||||
expect(second.data).not.toBe(first.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('transformProps memoizes the comparison-mode data pipeline on identical input', () => {
|
|
||||||
// Exercises `processComparisonDataRecords` (the third of four memoizeOne
|
|
||||||
// callsites in transformProps.ts) via the `comparison` fixture, which has
|
|
||||||
// `time_compare` set and therefore flows through the comparison branch
|
|
||||||
// where `passedData = comparisonData`.
|
|
||||||
//
|
|
||||||
// Note: we don't assert reference equality on `columns` here because the
|
|
||||||
// comparison branch runs `comparisonColumns` through the non-memoized
|
|
||||||
// `processComparisonColumns` helper, which returns a fresh array on each
|
|
||||||
// call by design.
|
|
||||||
const first = transformProps(testData.comparison);
|
|
||||||
const second = transformProps(testData.comparison);
|
|
||||||
|
|
||||||
expect(second.data).toBe(first.data);
|
|
||||||
});
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import { GenericDataType } from '@apache-superset/core/common';
|
import { GenericDataType } from '@apache-superset/core/common';
|
||||||
import {
|
import {
|
||||||
supersetTheme,
|
supersetTheme,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
"geostyler-wfs-parser": "^3.0.1",
|
"geostyler-wfs-parser": "^3.0.1",
|
||||||
"ol": "^10.8.0",
|
"ol": "^10.8.0",
|
||||||
"polished": "*",
|
"polished": "*",
|
||||||
"react": "^18.2.0",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^17.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
import Layer from 'ol/layer/Layer';
|
import Layer from 'ol/layer/Layer';
|
||||||
import { FrameState } from 'ol/Map';
|
import { FrameState } from 'ol/Map';
|
||||||
import { apply as applyTransform } from 'ol/transform';
|
import { apply as applyTransform } from 'ol/transform';
|
||||||
import { createRoot, Root } from 'react-dom/client';
|
import ReactDOM from 'react-dom';
|
||||||
import { SupersetTheme } from '@apache-superset/core/theme';
|
import { SupersetTheme } from '@apache-superset/core/theme';
|
||||||
import { ChartConfig, ChartLayerOptions, ChartSizeValues } from '../types';
|
import { ChartConfig, ChartLayerOptions, ChartSizeValues } from '../types';
|
||||||
import { createChartComponent } from '../util/chartUtil';
|
import { createChartComponent } from '../util/chartUtil';
|
||||||
@@ -31,14 +31,7 @@ import Loader from '../images/loading.gif';
|
|||||||
* Custom OpenLayers layer that displays charts on given locations.
|
* Custom OpenLayers layer that displays charts on given locations.
|
||||||
*/
|
*/
|
||||||
export class ChartLayer extends Layer {
|
export class ChartLayer extends Layer {
|
||||||
charts: {
|
charts: any[] = [];
|
||||||
htmlElement: HTMLDivElement;
|
|
||||||
root: Root;
|
|
||||||
coordinate: number[];
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
feature: any;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
chartConfigs: ChartConfig = {
|
chartConfigs: ChartConfig = {
|
||||||
type: 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
@@ -173,7 +166,7 @@ export class ChartLayer extends Layer {
|
|||||||
*/
|
*/
|
||||||
removeAllChartElements() {
|
removeAllChartElements() {
|
||||||
this.charts.forEach(chart => {
|
this.charts.forEach(chart => {
|
||||||
chart.root.unmount();
|
ReactDOM.unmountComponentAtNode(chart.htmlElement);
|
||||||
chart.htmlElement.remove();
|
chart.htmlElement.remove();
|
||||||
});
|
});
|
||||||
this.charts = [];
|
this.charts = [];
|
||||||
@@ -198,12 +191,10 @@ export class ChartLayer extends Layer {
|
|||||||
this.theme,
|
this.theme,
|
||||||
this.locale,
|
this.locale,
|
||||||
);
|
);
|
||||||
const root = createRoot(container);
|
ReactDOM.render(chartComponent, container);
|
||||||
root.render(chartComponent);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
htmlElement: container,
|
htmlElement: container,
|
||||||
root,
|
|
||||||
coordinate: getProjectedCoordinateFromPointGeoJson(feature.geometry),
|
coordinate: getProjectedCoordinateFromPointGeoJson(feature.geometry),
|
||||||
width: chartWidth,
|
width: chartWidth,
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
@@ -236,7 +227,7 @@ export class ChartLayer extends Layer {
|
|||||||
this.theme,
|
this.theme,
|
||||||
this.locale,
|
this.locale,
|
||||||
);
|
);
|
||||||
chart.root.render(chartComponent);
|
ReactDOM.render(chartComponent, chart.htmlElement);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...chart,
|
...chart,
|
||||||
|
|||||||
@@ -41,11 +41,6 @@ describe('ChartLayer', () => {
|
|||||||
chartLayer.charts = [
|
chartLayer.charts = [
|
||||||
{
|
{
|
||||||
htmlElement: document.createElement('div'),
|
htmlElement: document.createElement('div'),
|
||||||
root: { render: jest.fn(), unmount: jest.fn() } as any,
|
|
||||||
coordinate: [0, 0],
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
feature: {},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"acorn": "^8.16.0",
|
"acorn": "^8.16.0",
|
||||||
"d3-array": "^3.2.4",
|
"d3-array": "^3.2.4",
|
||||||
"lodash": "^4.18.1",
|
"lodash": "^4.18.1",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@apache-superset/core": "*",
|
"@apache-superset/core": "*",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"echarts": "*",
|
"echarts": "*",
|
||||||
"memoize-one": "*",
|
"memoize-one": "*",
|
||||||
"react": "^18.2.0"
|
"react": "^17.0.2"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function EchartsMixedTimeseries({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getCrossFilterDataMask = useCallback(
|
const getCrossFilterDataMask = useCallback(
|
||||||
(seriesName: string, seriesIndex: number) => {
|
(seriesName, seriesIndex) => {
|
||||||
const selected: string[] = Object.values(selectedValues || {});
|
const selected: string[] = Object.values(selectedValues || {});
|
||||||
let values: string[];
|
let values: string[];
|
||||||
if (selected.includes(seriesName)) {
|
if (selected.includes(seriesName)) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import Echart from '../components/Echart';
|
import Echart from '../components/Echart';
|
||||||
import { NULL_STRING } from '../constants';
|
import { NULL_STRING } from '../constants';
|
||||||
import { EventHandlers, TreePathInfo } from '../types';
|
import { EventHandlers } from '../types';
|
||||||
import { extractTreePathInfo } from './constants';
|
import { extractTreePathInfo } from './constants';
|
||||||
import { TreemapTransformedProps } from './types';
|
import { TreemapTransformedProps } from './types';
|
||||||
import { formatSeriesName } from '../utils/series';
|
import { formatSeriesName } from '../utils/series';
|
||||||
@@ -46,7 +46,7 @@ export default function EchartsTreemap({
|
|||||||
coltypeMapping,
|
coltypeMapping,
|
||||||
}: TreemapTransformedProps) {
|
}: TreemapTransformedProps) {
|
||||||
const getCrossFilterDataMask = useCallback(
|
const getCrossFilterDataMask = useCallback(
|
||||||
(data: Record<string, unknown>, treePathInfo: TreePathInfo[]) => {
|
(data, treePathInfo) => {
|
||||||
if (data?.children) {
|
if (data?.children) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ export default function EchartsTreemap({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(data: Record<string, unknown>, treePathInfo: TreePathInfo[]) => {
|
(data, treePathInfo) => {
|
||||||
if (!emitCrossFilters || groupby.length === 0) {
|
if (!emitCrossFilters || groupby.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,12 @@ import {
|
|||||||
AnnotationStyle,
|
AnnotationStyle,
|
||||||
AnnotationType,
|
AnnotationType,
|
||||||
AnnotationSourceType,
|
AnnotationSourceType,
|
||||||
AxisType,
|
|
||||||
DataRecord,
|
DataRecord,
|
||||||
FormulaAnnotationLayer,
|
FormulaAnnotationLayer,
|
||||||
IntervalAnnotationLayer,
|
IntervalAnnotationLayer,
|
||||||
VizType,
|
VizType,
|
||||||
ChartDataResponseResult,
|
ChartDataResponseResult,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import { GenericDataType } from '@apache-superset/core/common';
|
|
||||||
import {
|
import {
|
||||||
LegendOrientation,
|
LegendOrientation,
|
||||||
LegendType,
|
LegendType,
|
||||||
@@ -498,133 +496,3 @@ test('should add a formula annotation when X-axis column has dataset-level label
|
|||||||
expect(Array.isArray(formulaSeries?.data)).toBe(true);
|
expect(Array.isArray(formulaSeries?.data)).toBe(true);
|
||||||
expect((formulaSeries!.data as unknown[]).length).toBeGreaterThan(0);
|
expect((formulaSeries!.data as unknown[]).length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('numeric x coltype never gets silently coerced to the Time axis', () => {
|
|
||||||
// Regression guard for echarts-timeseries-epoch-x-axis-labels investigation.
|
|
||||||
// Mixed Timeseries must follow the reported coltype: Numeric values stay
|
|
||||||
// off the Time axis and are not silently reinterpreted as Date instances.
|
|
||||||
// A future change that coerces Numeric → Time would bring back the "NaN"
|
|
||||||
// label symptom we were investigating. We also assert that whichever
|
|
||||||
// formatter is picked, it produces a string and does not emit "NaN".
|
|
||||||
const ts1 = 1745784000000;
|
|
||||||
const ts2 = 1745870400000;
|
|
||||||
const epochRows = [
|
|
||||||
{ __timestamp: ts1, metric: 10 },
|
|
||||||
{ __timestamp: ts2, metric: 20 },
|
|
||||||
];
|
|
||||||
const epochQueryData = createTestQueryData(epochRows, {
|
|
||||||
colnames: ['__timestamp', 'metric'],
|
|
||||||
coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
|
|
||||||
label_map: { __timestamp: ['__timestamp'], metric: ['metric'] },
|
|
||||||
});
|
|
||||||
|
|
||||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
|
||||||
EchartsMixedTimeseriesFormData,
|
|
||||||
EchartsMixedTimeseriesProps
|
|
||||||
>({
|
|
||||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
|
||||||
defaultQueriesData: [epochQueryData, epochQueryData],
|
|
||||||
formData: {
|
|
||||||
...formData,
|
|
||||||
x_axis: '__timestamp',
|
|
||||||
metrics: ['metric'],
|
|
||||||
metricsB: ['metric'],
|
|
||||||
groupby: [],
|
|
||||||
groupbyB: [],
|
|
||||||
},
|
|
||||||
queriesData: [epochQueryData, epochQueryData],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { echartOptions } = transformProps(chartProps);
|
|
||||||
const xAxis = echartOptions.xAxis as {
|
|
||||||
type: string;
|
|
||||||
axisLabel: { formatter: (v: number) => string };
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(xAxis.type).not.toBe(AxisType.Time);
|
|
||||||
const label = xAxis.axisLabel.formatter(ts1);
|
|
||||||
expect(typeof label).toBe('string');
|
|
||||||
expect(label).not.toMatch(/NaN/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('xAxisForceCategorical forces Category axis regardless of Numeric coltype', () => {
|
|
||||||
const ts1 = 1745784000000;
|
|
||||||
const ts2 = 1745870400000;
|
|
||||||
const epochRows = [
|
|
||||||
{ __timestamp: ts1, metric: 10 },
|
|
||||||
{ __timestamp: ts2, metric: 20 },
|
|
||||||
];
|
|
||||||
const epochQueryData = createTestQueryData(epochRows, {
|
|
||||||
colnames: ['__timestamp', 'metric'],
|
|
||||||
coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
|
|
||||||
label_map: { __timestamp: ['__timestamp'], metric: ['metric'] },
|
|
||||||
});
|
|
||||||
|
|
||||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
|
||||||
EchartsMixedTimeseriesFormData,
|
|
||||||
EchartsMixedTimeseriesProps
|
|
||||||
>({
|
|
||||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
|
||||||
defaultQueriesData: [epochQueryData, epochQueryData],
|
|
||||||
formData: {
|
|
||||||
...formData,
|
|
||||||
x_axis: '__timestamp',
|
|
||||||
metrics: ['metric'],
|
|
||||||
metricsB: ['metric'],
|
|
||||||
groupby: [],
|
|
||||||
groupbyB: [],
|
|
||||||
xAxisForceCategorical: true,
|
|
||||||
},
|
|
||||||
queriesData: [epochQueryData, epochQueryData],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { echartOptions } = transformProps(chartProps);
|
|
||||||
const xAxis = echartOptions.xAxis as { type: string };
|
|
||||||
|
|
||||||
expect(xAxis.type).toBe(AxisType.Category);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('temporal x coltype wires the time formatter and Time axis', () => {
|
|
||||||
// Regression guard: the happy path for mixed-timeseries charts. Ensures
|
|
||||||
// Temporal coltype still routes through the TimeFormatter so the time axis
|
|
||||||
// rendering path is exercised by the test suite.
|
|
||||||
const ts1 = 1745784000000;
|
|
||||||
const ts2 = 1745870400000;
|
|
||||||
const temporalRows = [
|
|
||||||
{ __timestamp: ts1, metric: 10 },
|
|
||||||
{ __timestamp: ts2, metric: 20 },
|
|
||||||
];
|
|
||||||
const temporalQueryData = createTestQueryData(temporalRows, {
|
|
||||||
colnames: ['__timestamp', 'metric'],
|
|
||||||
coltypes: [GenericDataType.Temporal, GenericDataType.Numeric],
|
|
||||||
label_map: { __timestamp: ['__timestamp'], metric: ['metric'] },
|
|
||||||
});
|
|
||||||
|
|
||||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
|
||||||
EchartsMixedTimeseriesFormData,
|
|
||||||
EchartsMixedTimeseriesProps
|
|
||||||
>({
|
|
||||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
|
||||||
defaultQueriesData: [temporalQueryData, temporalQueryData],
|
|
||||||
formData: {
|
|
||||||
...formData,
|
|
||||||
x_axis: '__timestamp',
|
|
||||||
metrics: ['metric'],
|
|
||||||
metricsB: ['metric'],
|
|
||||||
groupby: [],
|
|
||||||
groupbyB: [],
|
|
||||||
},
|
|
||||||
queriesData: [temporalQueryData, temporalQueryData],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { echartOptions } = transformProps(chartProps);
|
|
||||||
const xAxis = echartOptions.xAxis as {
|
|
||||||
type: string;
|
|
||||||
axisLabel: { formatter: (v: Date) => string };
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(xAxis.type).toBe(AxisType.Time);
|
|
||||||
const label = xAxis.axisLabel.formatter(new Date(ts1));
|
|
||||||
expect(typeof label).toBe('string');
|
|
||||||
expect(label).not.toMatch(/NaN/);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
AnnotationSourceType,
|
AnnotationSourceType,
|
||||||
AnnotationStyle,
|
AnnotationStyle,
|
||||||
AnnotationType,
|
AnnotationType,
|
||||||
AxisType,
|
|
||||||
ComparisonType,
|
ComparisonType,
|
||||||
DataRecord,
|
DataRecord,
|
||||||
EventAnnotationLayer,
|
EventAnnotationLayer,
|
||||||
@@ -1473,118 +1472,6 @@ test('x-axis formatter deduplicates consecutive identical labels for coarse time
|
|||||||
expect(label4).toBe('');
|
expect(label4).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('numeric x coltype routes through the number formatter (not the time formatter)', () => {
|
|
||||||
// Regression guard for echarts-timeseries-epoch-x-axis-labels investigation.
|
|
||||||
// When the query reports a Numeric x-axis coltype (including epoch-ms-like
|
|
||||||
// values), Timeseries transformProps must pick the Value axis and run the
|
|
||||||
// label through getNumberFormatter, not the time formatter. If this ever
|
|
||||||
// changes, epoch-ms values that arrive as Numeric would suddenly be treated
|
|
||||||
// as Date instances and could render "NaN" — the symptom that prompted this
|
|
||||||
// investigation.
|
|
||||||
const ts1 = 1745784000000;
|
|
||||||
const ts2 = 1745870400000;
|
|
||||||
const chartProps = createTestChartProps({
|
|
||||||
formData: {
|
|
||||||
metrics: ['metric'],
|
|
||||||
granularity_sqla: 'ds',
|
|
||||||
x_axis: '__timestamp',
|
|
||||||
},
|
|
||||||
queriesData: [
|
|
||||||
createTestQueryData(
|
|
||||||
[
|
|
||||||
{ __timestamp: ts1, metric: 10 },
|
|
||||||
{ __timestamp: ts2, metric: 20 },
|
|
||||||
],
|
|
||||||
{
|
|
||||||
colnames: ['__timestamp', 'metric'],
|
|
||||||
coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { echartOptions } = transformProps(chartProps);
|
|
||||||
const xAxis = echartOptions.xAxis as {
|
|
||||||
type: string;
|
|
||||||
axisLabel: { formatter: (v: number) => string };
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(xAxis.type).toBe(AxisType.Value);
|
|
||||||
const label = xAxis.axisLabel.formatter(ts1);
|
|
||||||
expect(typeof label).toBe('string');
|
|
||||||
expect(label).not.toMatch(/NaN/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('xAxisForceCategorical forces Category axis regardless of Numeric coltype', () => {
|
|
||||||
const ts1 = 1745784000000;
|
|
||||||
const ts2 = 1745870400000;
|
|
||||||
const chartProps = createTestChartProps({
|
|
||||||
formData: {
|
|
||||||
metrics: ['metric'],
|
|
||||||
granularity_sqla: 'ds',
|
|
||||||
x_axis: '__timestamp',
|
|
||||||
xAxisForceCategorical: true,
|
|
||||||
},
|
|
||||||
queriesData: [
|
|
||||||
createTestQueryData(
|
|
||||||
[
|
|
||||||
{ __timestamp: ts1, metric: 10 },
|
|
||||||
{ __timestamp: ts2, metric: 20 },
|
|
||||||
],
|
|
||||||
{
|
|
||||||
colnames: ['__timestamp', 'metric'],
|
|
||||||
coltypes: [GenericDataType.Numeric, GenericDataType.Numeric],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { echartOptions } = transformProps(chartProps);
|
|
||||||
const xAxis = echartOptions.xAxis as { type: string };
|
|
||||||
|
|
||||||
expect(xAxis.type).toBe(AxisType.Category);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('temporal x coltype wires the time formatter and Time axis', () => {
|
|
||||||
// Regression guard: the happy path for time-series charts. Ensures that
|
|
||||||
// Temporal coltype keeps routing through the TimeFormatter so a refactor
|
|
||||||
// does not accidentally drop Date handling (the feared regression that
|
|
||||||
// sparked this investigation).
|
|
||||||
const ts1 = 1745784000000;
|
|
||||||
const ts2 = 1745870400000;
|
|
||||||
const chartProps = createTestChartProps({
|
|
||||||
formData: {
|
|
||||||
metrics: ['metric'],
|
|
||||||
granularity_sqla: 'ds',
|
|
||||||
x_axis: '__timestamp',
|
|
||||||
},
|
|
||||||
queriesData: [
|
|
||||||
createTestQueryData(
|
|
||||||
[
|
|
||||||
{ __timestamp: ts1, metric: 10 },
|
|
||||||
{ __timestamp: ts2, metric: 20 },
|
|
||||||
],
|
|
||||||
{
|
|
||||||
colnames: ['__timestamp', 'metric'],
|
|
||||||
coltypes: [GenericDataType.Temporal, GenericDataType.Numeric],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const { echartOptions } = transformProps(chartProps);
|
|
||||||
const xAxis = echartOptions.xAxis as {
|
|
||||||
type: string;
|
|
||||||
axisLabel: { formatter: (v: Date) => string };
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(xAxis.type).toBe(AxisType.Time);
|
|
||||||
const label = xAxis.axisLabel.formatter(new Date(ts1));
|
|
||||||
expect(typeof label).toBe('string');
|
|
||||||
expect(label).not.toMatch(/NaN/);
|
|
||||||
expect(label).not.toBe(String(ts1));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should assign distinct dash patterns for multiple time offsets consistently', () => {
|
test('should assign distinct dash patterns for multiple time offsets consistently', () => {
|
||||||
const queriesDataWithMultipleOffsets = [
|
const queriesDataWithMultipleOffsets = [
|
||||||
createTestQueryData([
|
createTestQueryData([
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user