Compare commits

..

7 Commits

Author SHA1 Message Date
Beto Dealmeida
285d7b1a08 Set query_context on chart creation 2026-05-05 10:33:24 -04:00
Beto Dealmeida
77016b3e89 Fix lint 2026-05-02 09:02:05 -04:00
Beto Dealmeida
32563ffb1d Add coverage 2026-05-02 00:05:49 -04:00
Beto Dealmeida
f79c7aca9d Add license 2026-05-01 23:24:44 -04:00
Beto Dealmeida
80cf2648f2 Fix lint 2026-05-01 22:58:12 -04:00
Beto Dealmeida
230c903e6b Fix lint 2026-05-01 19:28:31 -04:00
Beto Dealmeida
229917b9b0 feat: nodejs sidecar 2026-05-01 19:16:27 -04:00
235 changed files with 11376 additions and 6553 deletions

View File

@@ -104,6 +104,8 @@ services:
depends_on:
superset-init:
condition: service_completed_successfully
query-context-sidecar:
condition: service_started
volumes: *superset-volumes
superset-websocket:
@@ -138,6 +140,19 @@ services:
- REDIS_PORT=6379
- REDIS_SSL=false
query-context-sidecar:
build:
context: .
dockerfile: query-context-sidecar/Dockerfile
restart: unless-stopped
ports:
- "127.0.0.1:${QUERY_CONTEXT_SIDECAR_PORT:-3030}:3030"
environment:
- PORT=3030
- QUERY_CONTEXT_MAX_BODY_BYTES=10485760
depends_on:
- superset-node
superset-init:
build:
<<: *common-build
@@ -152,6 +167,8 @@ services:
condition: service_started
redis:
condition: service_started
query-context-sidecar:
condition: service_started
user: *superset-user
volumes: *superset-volumes
healthcheck:

View File

@@ -26,6 +26,7 @@ DEV_MODE=true
# SUPERSET_PORT=8088
# NODE_PORT=9000
# WEBSOCKET_PORT=8080
# QUERY_CONTEXT_SIDECAR_PORT=3030
# CYPRESS_PORT=8081
# DATABASE_PORT=5432
# REDIS_PORT=6379
@@ -74,6 +75,7 @@ SUPERSET_LOAD_EXAMPLES=yes
CYPRESS_CONFIG=false
SUPERSET_PORT=8088
MAPBOX_API_KEY=''
QUERY_CONTEXT_SIDECAR_URL=http://query-context-sidecar:3030
# Make sure you set this to a unique secure random value on production
SUPERSET_SECRET_KEY=TEST_NON_DEV_SECRET

View File

@@ -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`).

View File

@@ -41,12 +41,12 @@
},
"dependencies": {
"@ant-design/icons": "^6.2.2",
"@docusaurus/core": "^3.10.1",
"@docusaurus/faster": "^3.10.1",
"@docusaurus/plugin-client-redirects": "^3.10.1",
"@docusaurus/preset-classic": "3.10.1",
"@docusaurus/theme-live-codeblock": "^3.10.1",
"@docusaurus/theme-mermaid": "^3.10.1",
"@docusaurus/core": "^3.10.0",
"@docusaurus/faster": "^3.10.0",
"@docusaurus/plugin-client-redirects": "^3.10.0",
"@docusaurus/preset-classic": "3.10.0",
"@docusaurus/theme-live-codeblock": "^3.10.0",
"@docusaurus/theme-mermaid": "^3.10.0",
"@emotion/core": "^11.0.0",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.14.1",
@@ -92,7 +92,7 @@
"unist-util-visit": "^5.1.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.10.1",
"@docusaurus/module-type-aliases": "^3.10.0",
"@docusaurus/tsconfig": "^3.10.1",
"@eslint/js": "^9.39.2",
"@types/js-yaml": "^4.0.9",
@@ -124,7 +124,8 @@
"resolutions": {
"react-redux": "^9.2.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"
}

View File

@@ -1570,10 +1570,10 @@
"@docsearch/core" "4.6.2"
"@docsearch/css" "4.6.2"
"@docusaurus/babel@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.10.1.tgz#2f714f682117658ba43d308e9b35b6a73a105227"
integrity sha512-DZzFO1K3v/GoEt1fx1DiYHF4en+PuhtQf1AkQJa5zu3CoeKSpr5cpQRUlz3jr0m44wyzmSXu9bVpfir+N4+8bg==
"@docusaurus/babel@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/babel/-/babel-3.10.0.tgz#819819f107233dfcf50b59cd51158f23fb04878a"
integrity sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==
dependencies:
"@babel/core" "^7.25.9"
"@babel/generator" "^7.25.9"
@@ -1584,23 +1584,23 @@
"@babel/preset-typescript" "^7.25.9"
"@babel/runtime" "^7.25.9"
"@babel/traverse" "^7.25.9"
"@docusaurus/logger" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/logger" "3.10.0"
"@docusaurus/utils" "3.10.0"
babel-plugin-dynamic-import-node "^2.3.3"
fs-extra "^11.1.1"
tslib "^2.6.0"
"@docusaurus/bundler@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.10.1.tgz#82fa5079f3787a67502e25f82d37d05ec5de0cc3"
integrity sha512-HIqQPvbqnnQRe4NsBd1774KRarjXqS6wHsWELtyuSs1gCfvixJO2jUGH/OEBtr1Gvzpw+ze5CjGMvSJ8UE1KUw==
"@docusaurus/bundler@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/bundler/-/bundler-3.10.0.tgz#878c4c46bfa3434671ea37a43da184238a6aae26"
integrity sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==
dependencies:
"@babel/core" "^7.25.9"
"@docusaurus/babel" "3.10.1"
"@docusaurus/cssnano-preset" "3.10.1"
"@docusaurus/logger" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/babel" "3.10.0"
"@docusaurus/cssnano-preset" "3.10.0"
"@docusaurus/logger" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
babel-loader "^9.2.1"
clean-css "^5.3.3"
copy-webpack-plugin "^11.0.0"
@@ -1618,20 +1618,20 @@
tslib "^2.6.0"
url-loader "^4.1.1"
webpack "^5.95.0"
webpackbar "^7.0.0"
webpackbar "^6.0.1"
"@docusaurus/core@3.10.1", "@docusaurus/core@^3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.10.1.tgz#3f8bdb97451b4df14f2a3b39ab0186366fbf8fbe"
integrity sha512-3pf2fXXw0eVk8WnC3T4LIigRDupcpvngpKo9Vy7mYyBhuddc0klDUuZAIfzMoK6z05pdlk6EFC/vBSX43+1O5w==
"@docusaurus/core@3.10.0", "@docusaurus/core@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.10.0.tgz#642e71a0209d62c3f5ef275ed9d74a881f40df39"
integrity sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==
dependencies:
"@docusaurus/babel" "3.10.1"
"@docusaurus/bundler" "3.10.1"
"@docusaurus/logger" "3.10.1"
"@docusaurus/mdx-loader" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/babel" "3.10.0"
"@docusaurus/bundler" "3.10.0"
"@docusaurus/logger" "3.10.0"
"@docusaurus/mdx-loader" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
boxen "^6.2.1"
chalk "^4.1.2"
chokidar "^3.5.3"
@@ -1668,22 +1668,22 @@
webpack-dev-server "^5.2.2"
webpack-merge "^6.0.1"
"@docusaurus/cssnano-preset@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.1.tgz#4b6bafeca8bb9423364d2fd6683c28e2f85a4665"
integrity sha512-eNfHGcTKCSq6xmcavAkX3RRclHaE2xRCMParlDXLdXVP01/a2e/jKXMj/0ULnLFQSNwwuI62L0Ge8J+nZsR7UQ==
"@docusaurus/cssnano-preset@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz#be1b435c33df09d743473d3fadda67b4568dfae3"
integrity sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==
dependencies:
cssnano-preset-advanced "^6.1.2"
postcss "^8.5.4"
postcss-sort-media-queries "^5.2.0"
tslib "^2.6.0"
"@docusaurus/faster@^3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/faster/-/faster-3.10.1.tgz#a63d89ae980c98e1eeab3ff15ee083f7c20ed353"
integrity sha512-XTZhE5C1gZ/DaYYMlSk02dwP5vhpQON5QHVz1s3892mSESAywgWanURpXEDAvt4GvGuq7s+XP8rTWHZvfaJmdQ==
"@docusaurus/faster@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/faster/-/faster-3.10.0.tgz#0758a93196f685537aa7700bde62faf926e6c817"
integrity sha512-GNPtVH14ISjHfSwnHu3KiFGf86ICmJSQDeSv/QaanpBgiZGOtgZaslnC5q8WiguxM1EVkwcGxPuD8BXF4eggKw==
dependencies:
"@docusaurus/types" "3.10.1"
"@docusaurus/types" "3.10.0"
"@rspack/core" "^1.7.10"
"@swc/core" "^1.7.39"
"@swc/html" "^1.13.5"
@@ -1694,22 +1694,22 @@
tslib "^2.6.0"
webpack "^5.95.0"
"@docusaurus/logger@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.10.1.tgz#34c964e32e18f120e30f80171a38cfefe72cfb4b"
integrity sha512-oPjNFnfJsRCkePVjkGrxWGq4MvJKRQT0r9jOP0eRBTZ7Wr9FAbzdP/Gjs0I2Ss6YRkPoEgygKG112OkE6skvJw==
"@docusaurus/logger@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.10.0.tgz#2bacbd004dd78e3da926dbe8f6fa9a930856575d"
integrity sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==
dependencies:
chalk "^4.1.2"
tslib "^2.6.0"
"@docusaurus/mdx-loader@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.10.1.tgz#050ae9bc614158a4ec07a628aa75fa9ae90d7e82"
integrity sha512-GRmeb/wQ+iXRrFwcHBfgQhrJxGElgCsoTWZYDhccjsZVne1p8MK/EpQVIloXttz76TCe78kKD5AEG9n1xc1oxQ==
"@docusaurus/mdx-loader@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.10.0.tgz#1d4b050d751389ecf38dee48bcb61e53df8ffb82"
integrity sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==
dependencies:
"@docusaurus/logger" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/logger" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@mdx-js/mdx" "^3.0.0"
"@slorber/remark-comment" "^1.0.0"
escape-html "^1.0.3"
@@ -1732,12 +1732,12 @@
vfile "^6.0.1"
webpack "^5.88.1"
"@docusaurus/module-type-aliases@3.10.1", "@docusaurus/module-type-aliases@^3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.1.tgz#22d39177c296786eb6e0d940699cd590cc93ca77"
integrity sha512-YoOZKUdGlp8xSYhuAkGdSo5Ydkbq4V4eK3sD8v0a2hloxCWdQbNBhkc+Ko9QyjpESc0BYcIGM5iHVAy5hdFV6w==
"@docusaurus/module-type-aliases@3.10.0", "@docusaurus/module-type-aliases@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.0.tgz#749928f104d563f11f046bf0c9ab6489a470c7c8"
integrity sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==
dependencies:
"@docusaurus/types" "3.10.1"
"@docusaurus/types" "3.10.0"
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router-config" "*"
@@ -1745,34 +1745,34 @@
react-helmet-async "npm:@slorber/react-helmet-async@1.3.0"
react-loadable "npm:@docusaurus/react-loadable@6.0.0"
"@docusaurus/plugin-client-redirects@^3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.10.1.tgz#e22ed20e5837b7c3a28258e3d1816c4239c82b36"
integrity sha512-LHgd+YDvkhfOHMAE6XtUng3DQNzVM765RqVRrMJgHtzAvfopQhY6ieprqjxDVBdv21cLma6I0jHr+YCZH8fL9A==
"@docusaurus/plugin-client-redirects@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.10.0.tgz#4dd4619817fd69462d1e6d986580343aeb911111"
integrity sha512-P+VLoLoZTc74so8+IbsaPZ33/mkf2BWL1CYXQpPRkl0v1QVCN2CgfsZY/8QtbYjQnx2upXUnv45abDhNcSggNw==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/logger" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/logger" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
eta "^2.2.0"
fs-extra "^11.1.1"
lodash "^4.17.21"
tslib "^2.6.0"
"@docusaurus/plugin-content-blog@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.1.tgz#0bd8de700ccbd8e95d920df2613304ef59abe72b"
integrity sha512-mmkgE6Q2+K74tnkou7tXlpDLvoCU/qkSa2GSQ3XUiHWvcebCoDQzS670RR3tO8PmaWlIyWWISYWzZLuMfxunRA==
"@docusaurus/plugin-content-blog@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.0.tgz#10095291b637440847854ecb2c8afcd8746debd7"
integrity sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/logger" "3.10.1"
"@docusaurus/mdx-loader" "3.10.1"
"@docusaurus/theme-common" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/logger" "3.10.0"
"@docusaurus/mdx-loader" "3.10.0"
"@docusaurus/theme-common" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
cheerio "1.0.0-rc.12"
combine-promises "^1.1.0"
feed "^4.2.2"
@@ -1785,20 +1785,20 @@
utility-types "^3.10.0"
webpack "^5.88.1"
"@docusaurus/plugin-content-docs@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.1.tgz#261e0e982e4a937c05b462e3c5729374f433b752"
integrity sha512-2jRVrtzjf8LClGTHQlwlwuD3wQXRx3WEoF7XUarJ8Ou+0onV+SLtejsyfY9JLpfUh9hPhXM4pbBGkyAY4Bi3HQ==
"@docusaurus/plugin-content-docs@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.0.tgz#9c4ea1d5a405340f28c281d2e4586c695a7c65a5"
integrity sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/logger" "3.10.1"
"@docusaurus/mdx-loader" "3.10.1"
"@docusaurus/module-type-aliases" "3.10.1"
"@docusaurus/theme-common" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/logger" "3.10.0"
"@docusaurus/mdx-loader" "3.10.0"
"@docusaurus/module-type-aliases" "3.10.0"
"@docusaurus/theme-common" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@types/react-router-config" "^5.0.7"
combine-promises "^1.1.0"
fs-extra "^11.1.1"
@@ -1809,142 +1809,142 @@
utility-types "^3.10.0"
webpack "^5.88.1"
"@docusaurus/plugin-content-pages@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.1.tgz#8c6ffc2079ed0262548ecc4df1dea6add6aa9673"
integrity sha512-huJpaRPMl42nsFwuCXvV8bVDj2MazuwRJIUylI/RSlmZeJssVoZXeCjVf1y+1Drtpa9SKcdGn8yoJ76IRJijtw==
"@docusaurus/plugin-content-pages@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.0.tgz#7670cbb3c849f434949f542bfdfded1580a13165"
integrity sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/mdx-loader" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/mdx-loader" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
fs-extra "^11.1.1"
tslib "^2.6.0"
webpack "^5.88.1"
"@docusaurus/plugin-css-cascade-layers@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.1.tgz#440578d95cbe1a6120936fa83df868d2626cd1d8"
integrity sha512-r//fn+MNHkE1wCof8T29VAQezt1enGCpsFxoziBbvLgBM4JfXN2P3rxrBaavHmvLvm7lYkpJeitcDthwnmWCTw==
"@docusaurus/plugin-css-cascade-layers@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.0.tgz#71e318d842be95f92be6c3dca00ceea4971d0edb"
integrity sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
tslib "^2.6.0"
"@docusaurus/plugin-debug@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.10.1.tgz#b8b7b24d9a7d185fd8a56a030f90145d3bfd8239"
integrity sha512-9KqOpKNfAyqGZykRb9LhIT/vyRF6sm/ykhjj/39JvaJahDS+jZJE0Z1Wfz9q3DUNDTMNN0Q7u/kk4rKKU+IJuA==
"@docusaurus/plugin-debug@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.10.0.tgz#e77f924604e1e09d5d90fe0bdf23a3be8ea3307e"
integrity sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
fs-extra "^11.1.1"
react-json-view-lite "^2.3.0"
tslib "^2.6.0"
"@docusaurus/plugin-google-analytics@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.1.tgz#ac15afc77386e0352edb8a1698d993aa5de36ffc"
integrity sha512-8o0P1KtmgdYQHH+oInitPpRWI0Of5XednAX4+DMhQNSmGSRNrsEEHg1ebv35m9AgRClfAytCJ5jA9KvcASTyuA==
"@docusaurus/plugin-google-analytics@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.0.tgz#22c7e976fe4d970c7cd1c73c9723d9a5786c6e37"
integrity sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
tslib "^2.6.0"
"@docusaurus/plugin-google-gtag@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.1.tgz#0482b83b9bc411aa99a432be2b39d2e53a00e2e0"
integrity sha512-pu3xIUo5o/zCMLfUY9BO5KOwSH0zIsAGyFRPvXHayFSA5XIhCU/SFuB0g0ZNjFn9niZLCaNvoeAuOGFJZq0fdw==
"@docusaurus/plugin-google-gtag@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.0.tgz#c38a2ba638257851cc845b934506b80c08d47f96"
integrity sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@types/gtag.js" "^0.0.20"
tslib "^2.6.0"
"@docusaurus/plugin-google-tag-manager@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.1.tgz#eaf5765d6f82b4fb661d92a793d1883f9d1ec106"
integrity sha512-f6fyGHiCm7kJHBtAisGQS5oNBnpnMTYQZxDXeVrnw/3zWU+LMA22pr6UHGYkBKDbN+qPC5QHG3NuOfzQLq3+Lw==
"@docusaurus/plugin-google-tag-manager@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.0.tgz#5469c923cc1ad4608399d0b17e5fcacd8e030d56"
integrity sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
tslib "^2.6.0"
"@docusaurus/plugin-sitemap@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.1.tgz#66a6974bb2fd1b9d8f5cb0f3c5ecd2201c118565"
integrity sha512-C26MbmmqgdjkDq1htaZ3aD7LzEDKFWXfpyQpt0EOUThuq5nV77zDaedV20yHcVo9p+3ey9aZ4pbHA0D3QcZTzg==
"@docusaurus/plugin-sitemap@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.0.tgz#35d59d46803f279f22aa64fc1bd18c048f12662b"
integrity sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/logger" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/logger" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
fs-extra "^11.1.1"
sitemap "^7.1.1"
tslib "^2.6.0"
"@docusaurus/plugin-svgr@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.1.tgz#c217c24d6d23fd2bc6f54d44c040635b49d6b36e"
integrity sha512-6SFxsmjWFkVLDmBUvFK6i72QjUwqyQFe4Ovz+SUJophJjOyVG3ZZG5IQpBC/kX/Gfv1yWeU9nWauH6F6Q7QX/Q==
"@docusaurus/plugin-svgr@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.0.tgz#8ada2e6dd8318d20206a9b044fc091a5794ba3f0"
integrity sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@svgr/core" "8.1.0"
"@svgr/webpack" "^8.1.0"
tslib "^2.6.0"
webpack "^5.88.1"
"@docusaurus/preset-classic@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.10.1.tgz#faf330d96aedc9083a59bec09d966ae4dfc8b2fb"
integrity sha512-YO/FL8v1zmbxoTso6mjMz/RDjhaTJxb1UpFFTDdY5847LLDCeyYiYlrhyTbgN1RIN3xnkLKZ9Lj1x8hUzI4JOg==
"@docusaurus/preset-classic@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.10.0.tgz#74b6facdaf568bcd41ec90cae9aebb7ca0ac8619"
integrity sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/plugin-content-blog" "3.10.1"
"@docusaurus/plugin-content-docs" "3.10.1"
"@docusaurus/plugin-content-pages" "3.10.1"
"@docusaurus/plugin-css-cascade-layers" "3.10.1"
"@docusaurus/plugin-debug" "3.10.1"
"@docusaurus/plugin-google-analytics" "3.10.1"
"@docusaurus/plugin-google-gtag" "3.10.1"
"@docusaurus/plugin-google-tag-manager" "3.10.1"
"@docusaurus/plugin-sitemap" "3.10.1"
"@docusaurus/plugin-svgr" "3.10.1"
"@docusaurus/theme-classic" "3.10.1"
"@docusaurus/theme-common" "3.10.1"
"@docusaurus/theme-search-algolia" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/plugin-content-blog" "3.10.0"
"@docusaurus/plugin-content-docs" "3.10.0"
"@docusaurus/plugin-content-pages" "3.10.0"
"@docusaurus/plugin-css-cascade-layers" "3.10.0"
"@docusaurus/plugin-debug" "3.10.0"
"@docusaurus/plugin-google-analytics" "3.10.0"
"@docusaurus/plugin-google-gtag" "3.10.0"
"@docusaurus/plugin-google-tag-manager" "3.10.0"
"@docusaurus/plugin-sitemap" "3.10.0"
"@docusaurus/plugin-svgr" "3.10.0"
"@docusaurus/theme-classic" "3.10.0"
"@docusaurus/theme-common" "3.10.0"
"@docusaurus/theme-search-algolia" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/theme-classic@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.10.1.tgz#deed8cf73cc0f56113e53775cbb3b168c3c61566"
integrity sha512-VU1RK0qb2pab0si4r7HFK37cYco8VzqLj3u1PspVipSr/z/GPVKHO4/HXbnePqHoWDk8urjyGSeatH0NIMBM1A==
"@docusaurus/theme-classic@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.10.0.tgz#d937915c691189f27ced649c822994d839ea565b"
integrity sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/logger" "3.10.1"
"@docusaurus/mdx-loader" "3.10.1"
"@docusaurus/module-type-aliases" "3.10.1"
"@docusaurus/plugin-content-blog" "3.10.1"
"@docusaurus/plugin-content-docs" "3.10.1"
"@docusaurus/plugin-content-pages" "3.10.1"
"@docusaurus/theme-common" "3.10.1"
"@docusaurus/theme-translations" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/logger" "3.10.0"
"@docusaurus/mdx-loader" "3.10.0"
"@docusaurus/module-type-aliases" "3.10.0"
"@docusaurus/plugin-content-blog" "3.10.0"
"@docusaurus/plugin-content-docs" "3.10.0"
"@docusaurus/plugin-content-pages" "3.10.0"
"@docusaurus/theme-common" "3.10.0"
"@docusaurus/theme-translations" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@mdx-js/react" "^3.0.0"
clsx "^2.0.0"
copy-text-to-clipboard "^3.2.0"
@@ -1959,15 +1959,15 @@
tslib "^2.6.0"
utility-types "^3.10.0"
"@docusaurus/theme-common@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.10.1.tgz#cbfec82b1b107be5c229811ed9caae14a501361c"
integrity sha512-0YtmIeoNo1fIw65LO8+/1dPgmDV86UmhMkow37gzjytuiCSQm9xob6PJy0L4kuQEMTLfUOGvkXvZr7GPrHquMA==
"@docusaurus/theme-common@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.10.0.tgz#70b419ccfdf62f092299354a72d1692e81be597d"
integrity sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==
dependencies:
"@docusaurus/mdx-loader" "3.10.1"
"@docusaurus/module-type-aliases" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/mdx-loader" "3.10.0"
"@docusaurus/module-type-aliases" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router-config" "*"
@@ -1977,48 +1977,48 @@
tslib "^2.6.0"
utility-types "^3.10.0"
"@docusaurus/theme-live-codeblock@^3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-3.10.1.tgz#29e6ddee467d816205ad611fd7bf10f00db5bdef"
integrity sha512-MKG/0zreelS6YlupQAoKmS5nCw9RRKwDHihJg2FinsU1+rqbrOYNYVq//eQy+m649k9b8XCazEw9VUMTFhpCTg==
"@docusaurus/theme-live-codeblock@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-3.10.0.tgz#05a38c6bfac479fd698f18f27ca06ebb126633d9"
integrity sha512-1Ycxu0dBAhEXzXPQ1dQW01aY1MNi7TCTUOBtIF0GcNrQBFj74XxhDqv/T6GxYBsaN+6QnIDs1T+D43iV2/r2hQ==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/theme-common" "3.10.1"
"@docusaurus/theme-translations" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/theme-common" "3.10.0"
"@docusaurus/theme-translations" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@philpl/buble" "^0.19.7"
clsx "^2.0.0"
fs-extra "^11.1.1"
react-live "^4.1.6"
tslib "^2.6.0"
"@docusaurus/theme-mermaid@^3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.1.tgz#dada9c50c780524d246906234ace8a35446f26fc"
integrity sha512-2gxpmln8Pc4EN1oWzshQEx2HTs67jk14v7MmgqGs8ZU7Nm8oihg+fTouof2u4vN8DtB3Fln4cDJu4UprSX1S3Q==
"@docusaurus/theme-mermaid@^3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.0.tgz#6581ccf16d27e4c02fe8c7cf15488862f27be9c8"
integrity sha512-Y2xrlwhIJ80oOZIO3PXL6A7J869splfcMI87E3NKpYsy3zJxOyV+BP1QMtGi59ajKgU868HPuyyn6J+6BZGOBg==
dependencies:
"@docusaurus/core" "3.10.1"
"@docusaurus/module-type-aliases" "3.10.1"
"@docusaurus/theme-common" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/module-type-aliases" "3.10.0"
"@docusaurus/theme-common" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
mermaid ">=11.6.0"
tslib "^2.6.0"
"@docusaurus/theme-search-algolia@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.1.tgz#6f422058711629ce8d7c2f17e1e54efa075c626e"
integrity sha512-OTaARARVZj2GvkJQjB+1jOIxntRaXea+G+fMsNqrZBAU1O1vJKDW22R7kECOHW27oJCLFN9HKaZeRrfAUyviug==
"@docusaurus/theme-search-algolia@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.0.tgz#0ff57fe58db6abde8f5ad2877e459cd2fa6e7464"
integrity sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==
dependencies:
"@algolia/autocomplete-core" "^1.19.2"
"@docsearch/react" "^3.9.0 || ^4.3.2"
"@docusaurus/core" "3.10.1"
"@docusaurus/logger" "3.10.1"
"@docusaurus/plugin-content-docs" "3.10.1"
"@docusaurus/theme-common" "3.10.1"
"@docusaurus/theme-translations" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@docusaurus/core" "3.10.0"
"@docusaurus/logger" "3.10.0"
"@docusaurus/plugin-content-docs" "3.10.0"
"@docusaurus/theme-common" "3.10.0"
"@docusaurus/theme-translations" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
algoliasearch "^5.37.0"
algoliasearch-helper "^3.26.0"
clsx "^2.0.0"
@@ -2028,10 +2028,10 @@
tslib "^2.6.0"
utility-types "^3.10.0"
"@docusaurus/theme-translations@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.10.1.tgz#c3119a015652290eea560ca45ac775963d6eb75b"
integrity sha512-cLMyaKivjBVWKMJuWqyFVVgtqe8DPJNPkog0bn8W1MDVAKcPdxRFycBfC1We1RaNp7Rdk513bmtW78RR6OBxBw==
"@docusaurus/theme-translations@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.10.0.tgz#8fdc23d29bd7f907db49c36cf65e2123d96be300"
integrity sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==
dependencies:
fs-extra "^11.1.1"
tslib "^2.6.0"
@@ -2041,10 +2041,10 @@
resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.10.1.tgz#1db31b4a4a5c914bdffa80070a35b6365d34f2e8"
integrity sha512-rYvB7yqkdqWIpAbDzQljGfM4cDBkLTbhmagZBEcsyj6oPUsz47lmW2pYdN1j+7sGFgltbAmQH62xfbrij4Eh6Q==
"@docusaurus/types@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.10.1.tgz#d42837938ae43ca2be0ca47e63e00476b5eb94be"
integrity sha512-XYMK8k1szDCFMw2V+Xyen0g7Kee1sP3dtFnl7vkGkZOkeAJ/oPDQPL8iz4HBKOo/cwU8QeV6onVjMqtP+tFzsw==
"@docusaurus/types@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.10.0.tgz#a69232bba74b738fcf4671fd5f0f079366dd3d13"
integrity sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==
dependencies:
"@mdx-js/mdx" "^3.0.0"
"@types/history" "^4.7.11"
@@ -2057,36 +2057,36 @@
webpack "^5.95.0"
webpack-merge "^5.9.0"
"@docusaurus/utils-common@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.10.1.tgz#6350b4898691e765de750f90eade0e0fa7902d99"
integrity sha512-5mFSgEADtnFxFH7RLw02QA5MpU5JVUCj0MPeIvi/aF4Fi45tQRIuTwXoXDqJ+1VfQJuYJGz3SI63wmGz4HvXzA==
"@docusaurus/utils-common@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.10.0.tgz#2a6dc76b312664fca7234d33607c085318ff1ae3"
integrity sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==
dependencies:
"@docusaurus/types" "3.10.1"
"@docusaurus/types" "3.10.0"
tslib "^2.6.0"
"@docusaurus/utils-validation@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.10.1.tgz#ddbcce997a5506424cdd16abf6845cc51692acae"
integrity sha512-cRv1X69jwaWv47waglllgZVWzeBFLhl53XT/XED/83BerVBTC5FTP8WTcVl8Z6sZOegDSwitu/wpCSPCDOT6lg==
"@docusaurus/utils-validation@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.10.0.tgz#a2418d7f31980d991fd3a1f39c8aad8820b36812"
integrity sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==
dependencies:
"@docusaurus/logger" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/logger" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
fs-extra "^11.2.0"
joi "^17.9.2"
js-yaml "^4.1.0"
lodash "^4.17.21"
tslib "^2.6.0"
"@docusaurus/utils@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.10.1.tgz#535968caa2c9bff69f997a081b98b95b3c5d3785"
integrity sha512-3ojeJry9xBYdJO6qoyyzqeJFSJBVx2mXhyDzSdjwL2+URFQMf+h25gG38iswGImicK0ELjTd1EL2xzk8hf3QPw==
"@docusaurus/utils@3.10.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.10.0.tgz#ea7d7b0d325b60f728decc00bb3908d00ef86faf"
integrity sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==
dependencies:
"@docusaurus/logger" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@docusaurus/logger" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
escape-string-regexp "^4.0.0"
execa "^5.1.1"
file-loader "^6.2.0"
@@ -13328,7 +13328,7 @@ renderkid@^3.0.0:
repeat-string@^1.5.2:
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==
require-directory@^2.1.1:
@@ -15368,7 +15368,7 @@ webpack@^5.106.2, webpack@^5.88.1, webpack@^5.95.0:
watchpack "^2.5.1"
webpack-sources "^3.3.4"
webpackbar@^7.0.0:
webpackbar@^6.0.1, webpackbar@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-7.0.0.tgz#7228d32881af2392381b6514499ddea73cdf218a"
integrity sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q==

2
query-context-sidecar/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules/
dist/

View File

@@ -0,0 +1,55 @@
# 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.
# Stage 1: Install superset-frontend dependencies
FROM node:20-alpine AS deps
WORKDIR /app
# Copy full superset-frontend tree so workspace dependency resolution stays consistent
COPY superset-frontend/ ./superset-frontend/
WORKDIR /app/superset-frontend
RUN npm ci --ignore-scripts
# Stage 2: Build the webpack bundle
FROM node:20-alpine AS builder
WORKDIR /app
# Copy installed node_modules from deps stage
COPY --from=deps /app/superset-frontend/node_modules ./superset-frontend/node_modules
# Copy superset-frontend source
COPY superset-frontend/ ./superset-frontend/
# Copy sidecar source and config
COPY query-context-sidecar/package.json query-context-sidecar/package-lock.json* ./query-context-sidecar/
COPY query-context-sidecar/webpack.config.js query-context-sidecar/tsconfig.json ./query-context-sidecar/
COPY query-context-sidecar/src/ ./query-context-sidecar/src/
WORKDIR /app/query-context-sidecar
RUN npm ci
RUN npm run build
# Stage 3: Minimal runtime
FROM node:20-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY --from=builder /app/query-context-sidecar/dist ./dist
USER node
CMD ["node", "dist/index.js"]

2130
query-context-sidecar/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "query-context-sidecar",
"version": "1.0.0",
"description": "Node.js sidecar that converts form_data to query_context using Superset frontend buildQuery functions",
"private": true,
"scripts": {
"build": "webpack --mode production",
"build:dev": "webpack --mode development",
"start": "node dist/index.js",
"dev": "webpack --mode development --watch"
},
"devDependencies": {
"css-loader": "^6.8.1",
"null-loader": "^4.0.1",
"style-loader": "^3.3.3",
"ts-loader": "^9.5.1",
"typescript": "^5.3.3",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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 { QueryFormData } from '@superset-ui/core';
import { getBuildQuery } from '../runtimeRegistry';
export default function buildCartodiagramQuery(formData: QueryFormData) {
const {
selected_chart: selectedChartString,
geom_column: geometryColumn,
extra_form_data: extraFormData,
} = formData as QueryFormData & {
selected_chart: string;
geom_column: string;
extra_form_data?: Record<string, unknown>;
};
const selectedChart = JSON.parse(selectedChartString);
const vizType = selectedChart.viz_type as string;
const chartFormData = JSON.parse(selectedChart.params) as Record<string, unknown>;
chartFormData.extra_form_data = {
...(chartFormData.extra_form_data as Record<string, unknown>),
...(extraFormData || {}),
};
const groupby = Array.isArray(chartFormData.groupby)
? (chartFormData.groupby as string[])
: [];
chartFormData.groupby = [geometryColumn, ...groupby];
const buildQuery = getBuildQuery(vizType);
if (!buildQuery) {
throw new Error(`Unsupported selected chart viz_type: ${vizType}`);
}
return buildQuery(chartFormData);
}

View File

@@ -0,0 +1,26 @@
/**
* 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 './polyfills';
import { registerAllBuildQueries } from './registry';
import { startServer } from './server';
registerAllBuildQueries();
startServer();

View File

@@ -0,0 +1,87 @@
/**
* 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.
*/
const g = globalThis as any;
if (typeof g.window === 'undefined') {
g.window = g;
}
g.window.featureFlags = {};
if (typeof g.document === 'undefined') {
g.document = {
getElementById: () => null,
createElement: () => ({
setAttribute: () => {},
style: {},
appendChild: () => {},
}),
createTextNode: () => ({}),
head: { appendChild: () => {} },
body: { appendChild: () => {} },
addEventListener: () => {},
removeEventListener: () => {},
querySelectorAll: () => [],
querySelector: () => null,
};
}
if (typeof g.navigator === 'undefined') {
g.navigator = {
userAgent: 'node.js',
language: 'en',
};
}
if (typeof g.HTMLElement === 'undefined') {
g.HTMLElement = class HTMLElement {};
}
if (typeof g.location === 'undefined') {
g.location = {
href: '',
origin: '',
protocol: 'http:',
host: 'localhost',
hostname: 'localhost',
port: '',
pathname: '/',
search: '',
hash: '',
};
}
if (typeof g.getComputedStyle === 'undefined') {
g.getComputedStyle = () => ({});
}
if (typeof g.requestAnimationFrame === 'undefined') {
g.requestAnimationFrame = (cb: () => void) => setTimeout(cb, 0);
}
if (typeof g.matchMedia === 'undefined') {
g.matchMedia = () => ({
matches: false,
addListener: () => {},
removeListener: () => {},
addEventListener: () => {},
removeEventListener: () => {},
});
}

View File

@@ -0,0 +1,114 @@
/**
* 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 bigNumberBuildQuery from '@superset-ui/plugin-chart-echarts/BigNumber/BigNumberWithTrendline/buildQuery';
import bigNumberPoPBuildQuery from '@superset-ui/plugin-chart-echarts/BigNumber/BigNumberPeriodOverPeriod/buildQuery';
import bigNumberTotalBuildQuery from '@superset-ui/plugin-chart-echarts/BigNumber/BigNumberTotal/buildQuery';
import boxPlotBuildQuery from '@superset-ui/plugin-chart-echarts/BoxPlot/buildQuery';
import bubbleBuildQuery from '@superset-ui/plugin-chart-echarts/Bubble/buildQuery';
import funnelBuildQuery from '@superset-ui/plugin-chart-echarts/Funnel/buildQuery';
import ganttBuildQuery from '@superset-ui/plugin-chart-echarts/Gantt/buildQuery';
import gaugeBuildQuery from '@superset-ui/plugin-chart-echarts/Gauge/buildQuery';
import graphBuildQuery from '@superset-ui/plugin-chart-echarts/Graph/buildQuery';
import heatmapBuildQuery from '@superset-ui/plugin-chart-echarts/Heatmap/buildQuery';
import histogramBuildQuery from '@superset-ui/plugin-chart-echarts/Histogram/buildQuery';
import mixedTimeseriesBuildQuery from '@superset-ui/plugin-chart-echarts/MixedTimeseries/buildQuery';
import pieBuildQuery from '@superset-ui/plugin-chart-echarts/Pie/buildQuery';
import radarBuildQuery from '@superset-ui/plugin-chart-echarts/Radar/buildQuery';
import sankeyBuildQuery from '@superset-ui/plugin-chart-echarts/Sankey/buildQuery';
import sunburstBuildQuery from '@superset-ui/plugin-chart-echarts/Sunburst/buildQuery';
import timeseriesBuildQuery from '@superset-ui/plugin-chart-echarts/Timeseries/buildQuery';
import treeBuildQuery from '@superset-ui/plugin-chart-echarts/Tree/buildQuery';
import treemapBuildQuery from '@superset-ui/plugin-chart-echarts/Treemap/buildQuery';
import waterfallBuildQuery from '@superset-ui/plugin-chart-echarts/Waterfall/buildQuery';
import handlebarsBuildQuery from '@superset-ui/plugin-chart-handlebars/plugin/buildQuery';
import pivotTableBuildQuery from '@superset-ui/plugin-chart-pivot-table/plugin/buildQuery';
import wordCloudBuildQuery from '@superset-ui/plugin-chart-word-cloud/plugin/buildQuery';
import tableBuildQuery from '@superset-ui/plugin-chart-table/buildQuery';
import agGridTableBuildQuery from '@superset-ui/plugin-chart-ag-grid-table/buildQuery';
import pointClusterMapBuildQuery from '@superset-ui/plugin-chart-point-cluster-map/buildQuery';
import deckArcBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Arc/buildQuery';
import deckContourBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Contour/buildQuery';
import deckGridBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Grid/buildQuery';
import deckHeatmapBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Heatmap/buildQuery';
import deckHexBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Hex/buildQuery';
import deckPathBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Path/buildQuery';
import deckPolygonBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Polygon/buildQuery';
import deckScatterBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Scatter/buildQuery';
import deckScreengridBuildQuery from '@superset-ui/preset-chart-deckgl/layers/Screengrid/buildQuery';
import filterRangeBuildQuery from 'src/filters/components/Range/buildQuery';
import filterSelectBuildQuery from 'src/filters/components/Select/buildQuery';
import filterTimeColumnBuildQuery from 'src/filters/components/TimeColumn/buildQuery';
import filterTimeGrainBuildQuery from 'src/filters/components/TimeGrain/buildQuery';
import cartodiagramBuildQuery from './buildQuery/cartodiagram';
import { registerBuildQuery } from './runtimeRegistry';
export function registerAllBuildQueries(): void {
registerBuildQuery('big_number', bigNumberBuildQuery as any);
registerBuildQuery('big_number_total', bigNumberTotalBuildQuery as any);
registerBuildQuery('pop_kpi', bigNumberPoPBuildQuery as any);
registerBuildQuery('box_plot', boxPlotBuildQuery as any);
registerBuildQuery('bubble_v2', bubbleBuildQuery as any);
registerBuildQuery('funnel', funnelBuildQuery as any);
registerBuildQuery('gantt_chart', ganttBuildQuery as any);
registerBuildQuery('gauge_chart', gaugeBuildQuery as any);
registerBuildQuery('graph_chart', graphBuildQuery as any);
registerBuildQuery('heatmap_v2', heatmapBuildQuery as any);
registerBuildQuery('histogram_v2', histogramBuildQuery as any);
registerBuildQuery('mixed_timeseries', mixedTimeseriesBuildQuery as any);
registerBuildQuery('pie', pieBuildQuery as any);
registerBuildQuery('radar', radarBuildQuery as any);
registerBuildQuery('sankey_v2', sankeyBuildQuery as any);
registerBuildQuery('sunburst_v2', sunburstBuildQuery as any);
registerBuildQuery('tree_chart', treeBuildQuery as any);
registerBuildQuery('treemap_v2', treemapBuildQuery as any);
registerBuildQuery('waterfall', waterfallBuildQuery as any);
registerBuildQuery('echarts_timeseries', timeseriesBuildQuery as any);
registerBuildQuery('echarts_area', timeseriesBuildQuery as any);
registerBuildQuery('echarts_timeseries_bar', timeseriesBuildQuery as any);
registerBuildQuery('echarts_timeseries_line', timeseriesBuildQuery as any);
registerBuildQuery('echarts_timeseries_smooth', timeseriesBuildQuery as any);
registerBuildQuery('echarts_timeseries_scatter', timeseriesBuildQuery as any);
registerBuildQuery('echarts_timeseries_step', timeseriesBuildQuery as any);
registerBuildQuery('pivot_table_v2', pivotTableBuildQuery as any);
registerBuildQuery('table', tableBuildQuery as any);
registerBuildQuery('ag-grid-table', agGridTableBuildQuery as any);
registerBuildQuery('point_cluster', pointClusterMapBuildQuery as any);
registerBuildQuery('handlebars', handlebarsBuildQuery as any);
registerBuildQuery('word_cloud', wordCloudBuildQuery as any);
registerBuildQuery('cartodiagram', cartodiagramBuildQuery as any);
registerBuildQuery('deck_arc', deckArcBuildQuery as any);
registerBuildQuery('deck_contour', deckContourBuildQuery as any);
registerBuildQuery('deck_grid', deckGridBuildQuery as any);
registerBuildQuery('deck_heatmap', deckHeatmapBuildQuery as any);
registerBuildQuery('deck_hex', deckHexBuildQuery as any);
registerBuildQuery('deck_path', deckPathBuildQuery as any);
registerBuildQuery('deck_polygon', deckPolygonBuildQuery as any);
registerBuildQuery('deck_scatter', deckScatterBuildQuery as any);
registerBuildQuery('deck_screengrid', deckScreengridBuildQuery as any);
registerBuildQuery('filter_select', filterSelectBuildQuery as any);
registerBuildQuery('filter_range', filterRangeBuildQuery as any);
registerBuildQuery('filter_timecolumn', filterTimeColumnBuildQuery as any);
registerBuildQuery('filter_timegrain', filterTimeGrainBuildQuery as any);
}

View File

@@ -0,0 +1,34 @@
/**
* 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.
*/
export type BuildQueryFn = (formData: Record<string, unknown>) => unknown;
const registry = new Map<string, BuildQueryFn>();
export function registerBuildQuery(vizType: string, fn: BuildQueryFn): void {
registry.set(vizType, fn);
}
export function getBuildQuery(vizType: string): BuildQueryFn | undefined {
return registry.get(vizType);
}
export function listVizTypes(): string[] {
return Array.from(registry.keys()).sort();
}

View File

@@ -0,0 +1,28 @@
/**
* 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 { getBuildQuery } from './runtimeRegistry';
export default function getChartBuildQueryRegistry() {
return {
get(vizType: string) {
return getBuildQuery(vizType);
},
};
}

View File

@@ -0,0 +1,166 @@
/**
* 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 http from 'http';
import { URL } from 'url';
import buildQueryContext from './stubs/buildQueryContext';
import { getBuildQuery, listVizTypes } from './runtimeRegistry';
const PORT = parseInt(process.env.PORT || '3030', 10);
const MAX_BODY_BYTES = parseInt(
process.env.QUERY_CONTEXT_MAX_BODY_BYTES || `${10 * 1024 * 1024}`,
10,
);
const ALLOWED_ORIGINS = new Set(
(process.env.QUERY_CONTEXT_ALLOWED_ORIGINS || '')
.split(',')
.map(origin => origin.trim())
.filter(Boolean),
);
class HttpRequestError extends Error {
statusCode: number;
constructor(statusCode: number, message: string) {
super(message);
this.statusCode = statusCode;
}
}
function readBody(req: http.IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
let totalBytes = 0;
req.on('data', (chunk: Buffer) => {
totalBytes += chunk.length;
if (totalBytes > MAX_BODY_BYTES) {
req.destroy();
reject(new HttpRequestError(413, 'Request body too large'));
return;
}
chunks.push(chunk);
});
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
req.on('error', reject);
});
}
function isAllowedOrigin(origin?: string): boolean {
if (!origin) {
return true;
}
if (ALLOWED_ORIGINS.size === 0) {
return true;
}
return ALLOWED_ORIGINS.has(origin);
}
function jsonResponse(res: http.ServerResponse, status: number, data: unknown): void {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
}
async function handleBuildQueryContext(
req: http.IncomingMessage,
res: http.ServerResponse,
): Promise<void> {
if (!isAllowedOrigin(req.headers.origin)) {
jsonResponse(res, 403, { error: 'Origin not allowed' });
return;
}
let body: string;
try {
body = await readBody(req);
} catch (err: any) {
if (err instanceof HttpRequestError) {
jsonResponse(res, err.statusCode, { error: err.message });
return;
}
throw err;
}
let parsed: any;
try {
parsed = JSON.parse(body);
} catch {
jsonResponse(res, 400, { error: 'Invalid JSON body' });
return;
}
const formData = parsed.form_data;
if (!formData || !formData.viz_type) {
jsonResponse(res, 400, {
error: 'Missing form_data or form_data.viz_type',
});
return;
}
try {
const buildQuery = getBuildQuery(formData.viz_type);
const queryContext = buildQuery
? buildQuery(formData)
: buildQueryContext(formData);
jsonResponse(res, 200, { query_context: queryContext });
} catch (err: any) {
console.error('Error building query context for %s:', formData.viz_type, err);
jsonResponse(res, 500, {
error: `Failed to build query context: ${err.message}`,
});
}
}
function handleVizTypes(res: http.ServerResponse): void {
const vizTypes = listVizTypes();
jsonResponse(res, 200, { viz_types: vizTypes, count: vizTypes.length });
}
function handleHealth(res: http.ServerResponse): void {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('OK');
}
export function startServer(): void {
const server = http.createServer(async (req, res) => {
const url = req.url ? new URL(req.url, `http://localhost:${PORT}`).pathname : '';
const method = req.method || '';
try {
if (url === '/health' && (method === 'GET' || method === 'HEAD')) {
handleHealth(res);
} else if (url === '/api/v1/viz-types' && method === 'GET') {
handleVizTypes(res);
} else if (url === '/api/v1/build-query-context' && method === 'POST') {
await handleBuildQueryContext(req, res);
} else {
jsonResponse(res, 404, { error: 'Not found' });
}
} catch (err) {
console.error('Unhandled error:', err);
jsonResponse(res, 500, { error: 'Internal server error' });
}
});
server.listen(PORT, () => {
console.log(`Query context sidecar listening on port ${PORT}`);
});
}

View File

@@ -0,0 +1,68 @@
/**
* 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 buildQueryObject from '@superset-ui/core/query/buildQueryObject';
import DatasourceKey from '@superset-ui/core/query/DatasourceKey';
import { normalizeTimeColumn } from '@superset-ui/core/query/normalizeTimeColumn';
import { isXAxisSet } from '@superset-ui/core/query/getXAxis';
import {
QueryFieldAliases,
QueryFormData,
} from '@superset-ui/core/query/types/QueryFormData';
import { QueryContext, QueryObject } from '@superset-ui/core/query/types/Query';
const WRAP_IN_ARRAY = (baseQueryObject: QueryObject) => [baseQueryObject];
type BuildFinalQueryObjects = (baseQueryObject: QueryObject) => QueryObject[];
export default function buildQueryContext(
formData: QueryFormData,
options?:
| {
buildQuery?: BuildFinalQueryObjects;
queryFields?: QueryFieldAliases;
}
| BuildFinalQueryObjects,
): QueryContext {
const { queryFields, buildQuery = WRAP_IN_ARRAY } =
typeof options === 'function'
? { buildQuery: options, queryFields: {} }
: options || {};
let queries = buildQuery(buildQueryObject(formData, queryFields));
queries.forEach(query => {
if (Array.isArray(query.post_processing)) {
query.post_processing = query.post_processing.filter(Boolean);
}
});
if (isXAxisSet(formData)) {
queries = queries.map(query => normalizeTimeColumn(formData, query));
}
return {
datasource: new DatasourceKey(formData.datasource).toObject(),
force: formData.force || false,
queries,
form_data: formData,
result_format: formData.result_format || 'json',
result_type: formData.result_type || 'full',
};
}

View File

@@ -0,0 +1,20 @@
/**
* 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.
*/
export default {};

View File

@@ -0,0 +1,35 @@
/**
* 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.
*/
export { aggregationOperator } from '@superset-ui/chart-controls/operators/aggregateOperator';
export { boxplotOperator } from '@superset-ui/chart-controls/operators/boxplotOperator';
export { contributionOperator } from '@superset-ui/chart-controls/operators/contributionOperator';
export { flattenOperator } from '@superset-ui/chart-controls/operators/flattenOperator';
export { histogramOperator } from '@superset-ui/chart-controls/operators/histogramOperator';
export { pivotOperator } from '@superset-ui/chart-controls/operators/pivotOperator';
export { prophetOperator } from '@superset-ui/chart-controls/operators/prophetOperator';
export { rankOperator } from '@superset-ui/chart-controls/operators/rankOperator';
export { renameOperator } from '@superset-ui/chart-controls/operators/renameOperator';
export { resampleOperator } from '@superset-ui/chart-controls/operators/resampleOperator';
export { rollingWindowOperator } from '@superset-ui/chart-controls/operators/rollingWindowOperator';
export { sortOperator } from '@superset-ui/chart-controls/operators/sortOperator';
export { timeCompareOperator } from '@superset-ui/chart-controls/operators/timeCompareOperator';
export { timeComparePivotOperator } from '@superset-ui/chart-controls/operators/timeComparePivotOperator';
export { extractExtraMetrics } from '@superset-ui/chart-controls/operators/utils/extractExtraMetrics';
export { isTimeComparison } from '@superset-ui/chart-controls/operators/utils/isTimeComparison';

View File

@@ -0,0 +1,28 @@
/**
* 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.
*/
export { default as buildQueryContext } from './buildQueryContext';
export { default as getChartBuildQueryRegistry } from '../runtimeRegistryAdapter';
export type { BuildQuery } from '@superset-ui/core/chart/registries/ChartBuildQueryRegistrySingleton';
export * from '@superset-ui/core/query';
export * from '@superset-ui/core/utils';
export * from '@superset-ui/core/validator';
export * from '@superset-ui/core/color';

View File

@@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "ES2019",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"jsx": "react",
"outDir": "dist",
"baseUrl": ".",
"paths": {
"@superset-ui/core": ["../superset-frontend/packages/superset-ui-core/src"],
"@superset-ui/core/*": ["../superset-frontend/packages/superset-ui-core/src/*"],
"@apache-superset/core": ["../superset-frontend/packages/superset-core/src"],
"@apache-superset/core/*": ["../superset-frontend/packages/superset-core/src/*"],
"@superset-ui/chart-controls": ["../superset-frontend/packages/superset-ui-chart-controls/src"],
"@superset-ui/plugin-chart-echarts/*": ["../superset-frontend/plugins/plugin-chart-echarts/src/*"],
"@superset-ui/plugin-chart-table/*": ["../superset-frontend/plugins/plugin-chart-table/src/*"],
"@superset-ui/plugin-chart-pivot-table/*": ["../superset-frontend/plugins/plugin-chart-pivot-table/src/*"],
"@superset-ui/plugin-chart-handlebars/*": ["../superset-frontend/plugins/plugin-chart-handlebars/src/*"],
"@superset-ui/plugin-chart-word-cloud/*": ["../superset-frontend/plugins/plugin-chart-word-cloud/src/*"],
"@superset-ui/plugin-chart-cartodiagram/*": ["../superset-frontend/plugins/plugin-chart-cartodiagram/src/*"],
"@superset-ui/plugin-chart-ag-grid-table/*": ["../superset-frontend/plugins/plugin-chart-ag-grid-table/src/*"],
"@superset-ui/plugin-chart-point-cluster-map/*": ["../superset-frontend/plugins/plugin-chart-point-cluster-map/src/*"],
"@superset-ui/preset-chart-deckgl/*": ["../superset-frontend/plugins/preset-chart-deckgl/src/*"],
"@superset-ui/legacy-preset-chart-nvd3/*": ["../superset-frontend/plugins/legacy-preset-chart-nvd3/src/*"],
"src/*": ["../superset-frontend/src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,137 @@
/**
* 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.
*/
const path = require('path');
const webpack = require('webpack');
const FRONTEND_DIR = path.resolve(__dirname, '../superset-frontend');
module.exports = {
target: 'node',
mode: 'production',
entry: './src/index.ts',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'commonjs2',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
modules: [path.join(FRONTEND_DIR, 'node_modules'), FRONTEND_DIR, 'node_modules'],
alias: {
'@superset-ui/core': path.join(FRONTEND_DIR, 'packages/superset-ui-core/src'),
'@superset-ui/chart-controls': path.join(
FRONTEND_DIR,
'packages/superset-ui-chart-controls/src',
),
'@superset-ui/switchboard': path.join(
FRONTEND_DIR,
'packages/superset-ui-switchboard/src',
),
'@apache-superset/core': path.join(FRONTEND_DIR, 'packages/superset-core/src'),
'@superset-ui/plugin-chart-echarts': path.join(
FRONTEND_DIR,
'plugins/plugin-chart-echarts/src',
),
'@superset-ui/plugin-chart-table': path.join(
FRONTEND_DIR,
'plugins/plugin-chart-table/src',
),
'@superset-ui/plugin-chart-pivot-table': path.join(
FRONTEND_DIR,
'plugins/plugin-chart-pivot-table/src',
),
'@superset-ui/plugin-chart-handlebars': path.join(
FRONTEND_DIR,
'plugins/plugin-chart-handlebars/src',
),
'@superset-ui/plugin-chart-word-cloud': path.join(
FRONTEND_DIR,
'plugins/plugin-chart-word-cloud/src',
),
'@superset-ui/plugin-chart-cartodiagram': path.join(
FRONTEND_DIR,
'plugins/plugin-chart-cartodiagram/src',
),
'@superset-ui/plugin-chart-ag-grid-table': path.join(
FRONTEND_DIR,
'plugins/plugin-chart-ag-grid-table/src',
),
'@superset-ui/plugin-chart-point-cluster-map': path.join(
FRONTEND_DIR,
'plugins/plugin-chart-point-cluster-map/src',
),
'@superset-ui/preset-chart-deckgl': path.join(
FRONTEND_DIR,
'plugins/preset-chart-deckgl/src',
),
},
},
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'ts-loader',
options: {
transpileOnly: true,
configFile: path.resolve(__dirname, 'tsconfig.json'),
},
},
exclude: /node_modules/,
},
{
test: /\.(png|jpe?g|gif|svg|ico)$/i,
use: 'null-loader',
},
{
test: /\.(css|less|scss|sass)$/i,
use: 'null-loader',
},
],
},
plugins: [
new webpack.NormalModuleReplacementPlugin(
/^@superset-ui\/core$/,
path.resolve(__dirname, 'src/stubs/superset-ui-core.ts'),
),
new webpack.NormalModuleReplacementPlugin(
/^@superset-ui\/chart-controls$/,
path.resolve(__dirname, 'src/stubs/superset-ui-chart-controls.ts'),
),
new webpack.NormalModuleReplacementPlugin(
/react-markdown/,
path.resolve(__dirname, 'src/stubs/empty.ts'),
),
new webpack.NormalModuleReplacementPlugin(
/remark-rehype/,
path.resolve(__dirname, 'src/stubs/empty.ts'),
),
new webpack.NormalModuleReplacementPlugin(
/remark-gfm/,
path.resolve(__dirname, 'src/stubs/empty.ts'),
),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
],
optimization: {
minimize: false,
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -194,13 +194,13 @@
"pretty-ms": "^9.3.0",
"query-string": "9.3.1",
"re-resizable": "^6.11.2",
"react": "^18.2.0",
"react": "^17.0.2",
"react-arborist": "^3.5.0",
"react-checkbox-tree": "^1.8.0",
"react-diff-viewer-continued": "^4.2.2",
"react-dnd": "^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-intersection-observer": "^10.0.3",
"react-json-tree": "^0.20.0",
@@ -211,6 +211,7 @@
"react-reverse-portal": "^2.3.0",
"react-router-dom": "^5.3.4",
"react-search-input": "^0.11.3",
"react-sortable-hoc": "^2.0.0",
"react-split": "^2.0.9",
"react-table": "^7.8.0",
"react-transition-group": "^4.4.5",
@@ -243,13 +244,14 @@
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.28.6",
"@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-typescript": "^7.28.5",
"@babel/register": "^7.23.7",
"@babel/runtime": "^7.29.2",
"@babel/runtime-corejs3": "^7.29.2",
"@babel/types": "^7.28.6",
"@cypress/react": "^8.0.2",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/jest": "^11.14.2",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
@@ -271,9 +273,10 @@
"@swc/core": "^1.15.32",
"@swc/plugin-emotion": "^14.9.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/react": "^14.0.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^12.8.3",
"@types/content-disposition": "^0.5.9",
"@types/dom-to-image": "^2.6.7",
@@ -283,8 +286,8 @@
"@types/json-bigint": "^1.0.4",
"@types/mousetrap": "^1.6.15",
"@types/node": "^25.6.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/react": "^17.0.83",
"@types/react-dom": "^17.0.26",
"@types/react-loadable": "^5.5.11",
"@types/react-redux": "^7.1.10",
"@types/react-resizable": "^3.0.8",
@@ -303,7 +306,7 @@
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^3.0.0",
"babel-plugin-lodash": "^3.3.4",
"baseline-browser-mapping": "^2.10.24",
"baseline-browser-mapping": "^2.10.21",
"cheerio": "1.2.0",
"concurrently": "^9.2.1",
"copy-webpack-plugin": "^14.0.0",
@@ -370,7 +373,7 @@
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.3",
"webpack-manifest-plugin": "^5.0.1",
"webpack-sources": "^3.4.1",
"webpack-sources": "^3.4.0",
"webpack-visualizer-plugin2": "^2.0.0"
},
"peerDependencies": {

View File

@@ -37,7 +37,7 @@
"cross-env": "^10.1.0",
"fs-extra": "^11.3.4",
"jest": "^30.3.0",
"yeoman-test": "^11.4.2"
"yeoman-test": "^11.3.1"
},
"engines": {
"npm": ">= 4.0.0",

View File

@@ -75,15 +75,16 @@
"devDependencies": {
"@babel/cli": "^7.28.6",
"@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-typescript": "^7.28.5",
"typescript": "^5.0.0",
"@emotion/styled": "^11.14.1",
"@types/lodash": "^4.17.24",
"@testing-library/dom": "^9.3.4",
"@testing-library/dom": "^8.20.1",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^14.0.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "*",
"@testing-library/user-event": "*",
"@types/react": "*",
"@types/react-loadable": "*",
@@ -97,8 +98,8 @@
"@fontsource/ibm-plex-mono": "^5.2.7",
"@fontsource/inter": "^5.2.6",
"nanoid": "^5.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-loadable": "^5.5.0",
"tinycolor2": "*",
"lodash": "^4.18.1",

View File

@@ -18,7 +18,7 @@
*/
import userEvent from '@testing-library/user-event';
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 { themeObject } from './theme';
@@ -33,7 +33,7 @@ const Providers = ({ children }: { children: React.ReactNode }) => (
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>,
): RenderResult => render(ui, { wrapper: Providers, ...options });
) => render(ui, { wrapper: Providers, ...options });
export {
createEvent,

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import React from 'react';
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { ThemeProvider } from '@emotion/react';
import { theme as antdTheme } from 'antd';
import {

View File

@@ -33,16 +33,17 @@
"@ant-design/icons": "^5.6.1",
"@emotion/react": "^11.4.1",
"@superset-ui/core": "*",
"@testing-library/dom": "^9.3.4",
"@testing-library/dom": "^8.20.1",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^14.0.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "*",
"@testing-library/user-event": "*",
"ace-builds": "^1.4.14",
"brace": "^0.11.1",
"memoize-one": "^5.1.1",
"react": "^18.2.0",
"react": "^17.0.2",
"react-ace": "^10.1.0",
"react-dom": "^18.2.0"
"react-dom": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -91,9 +91,10 @@
"@emotion/cache": "^11.4.0",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.14.1",
"@testing-library/dom": "^9.3.4",
"@testing-library/dom": "^8.20.1",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^14.0.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "*",
"@testing-library/user-event": "*",
"@types/react": "*",
"@types/react-loadable": "*",
@@ -101,8 +102,8 @@
"@types/tinycolor2": "*",
"antd": "^5.26.0",
"nanoid": "^5.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-loadable": "^5.5.0",
"tinycolor2": "*"
},

View File

@@ -17,15 +17,19 @@
* under the License.
*/
import type { ReactElement, ReactNode } from 'react';
import { Tooltip, type TooltipPlacement } from '@superset-ui/core/components';
import type { ReactElement } from 'react';
import {
Tooltip,
type TooltipPlacement,
type IconType,
} from '@superset-ui/core/components';
import { css, useTheme } from '@apache-superset/core/theme';
export interface ActionProps {
label: string;
tooltip?: string | ReactElement;
placement?: TooltipPlacement;
icon: ReactNode;
icon: IconType;
onClick: () => void;
}

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { useJsonValidation } from './useJsonValidation';
describe('useJsonValidation', () => {

View File

@@ -16,7 +16,16 @@
* specific language governing permissions and limitations
* 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 type { PlaceholderProps } from './types';
@@ -84,16 +93,15 @@ export function AsyncEsmComponent<
return promise;
}
type AsyncComponent = React.ForwardRefExoticComponent<
React.PropsWithoutRef<FullProps> & React.RefAttributes<unknown>
type AsyncComponent = ForwardRefExoticComponent<
PropsWithoutRef<FullProps> & RefAttributes<ComponentType<FullProps>>
> & {
preload?: typeof waitForPromise;
};
// @ts-expect-error -- generic forwardRef has PropsWithoutRef incompatibility with FullProps
const AsyncComponent: AsyncComponent = forwardRef(function AsyncComponent(
props: FullProps,
ref,
ref: RefObject<ComponentType<FullProps>>,
) {
const [loaded, setLoaded] = useState(component !== undefined);
useEffect(() => {

View File

@@ -24,6 +24,7 @@ import type {
ButtonVariantType,
ButtonColorType,
} from 'antd/es/button';
import { IconType } from '@superset-ui/core/components/Icons/types';
import type { TooltipPlacement } from '../Tooltip/types';
export type { AntdButtonProps, ButtonType, ButtonVariantType, ButtonColorType };
@@ -48,5 +49,5 @@ export type ButtonProps = Omit<AntdButtonProps, 'css'> & {
buttonStyle?: ButtonStyle;
cta?: boolean;
showMarginRight?: boolean;
icon?: ReactNode;
icon?: IconType;
};

View File

@@ -73,7 +73,7 @@ export const Component = (props: DropdownContainerProps) => {
const [overflowingState, setOverflowingState] = useState<OverflowingState>();
const containerRef = useRef<DropdownRef>(null);
const onOverflowingStateChange = useCallback(
(value: OverflowingState) => {
value => {
if (!isEqual(overflowingState, value)) {
setItems(generateItems(value));
setOverflowingState(value);

View File

@@ -17,6 +17,7 @@
* under the License.
*/
import type { CSSProperties, ReactElement, RefObject, ReactNode } from 'react';
import { IconType } from '../Icons';
/**
* Container item.
@@ -69,7 +70,7 @@ export interface DropdownContainerProps {
/**
* Icon of the dropdown trigger.
*/
dropdownTriggerIcon?: ReactNode;
dropdownTriggerIcon?: IconType;
/**
* Text of the dropdown trigger.
*/

View File

@@ -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');
});

View File

@@ -23,7 +23,6 @@ import {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
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 { Tooltip } from '../Tooltip';
import { Input } from '../Input';
import type { InputRef } from '../Input';
import type { DynamicEditableTitleProps } from './types';
const titleStyles = (theme: SupersetTheme) => css`
@@ -77,10 +75,8 @@ export const DynamicEditableTitle = memo(
const [isEditing, setIsEditing] = useState(false);
const [showTooltip, setShowTooltip] = useState(false);
const [currentTitle, setCurrentTitle] = useState(title || '');
const [inputWidth, setInputWidth] = useState<number>(0);
const sizerRef = useRef<HTMLSpanElement>(null);
const inputRef = useRef<InputRef>(null);
const { width: inputWidth, ref: sizerRef } = useResizeDetector();
const { width: containerWidth, ref: containerRef } = useResizeDetector({
refreshMode: 'debounce',
});
@@ -89,33 +85,27 @@ export const DynamicEditableTitle = memo(
setCurrentTitle(title);
}, [title]);
useEffect(() => {
if (isEditing) {
if (isEditing && sizerRef?.current) {
// move cursor and scroll to the end
const inputElement = inputRef.current?.input;
if (inputElement) {
const { length } = inputElement.value;
inputElement.setSelectionRange(length, length);
inputElement.scrollLeft = inputElement.scrollWidth;
if (sizerRef.current.setSelectionRange) {
const { length } = sizerRef.current.value;
sizerRef.current.setSelectionRange(length, length);
sizerRef.current.scrollLeft = sizerRef.current.scrollWidth;
}
}
}, [isEditing]);
// a trick to make the input grow when user types text
// we make an additional span component, place it somewhere out of view and
// mirror the input value, then measure the span synchronously (pre-paint)
// 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.
// we make additional span component, place it somewhere out of view and copy input
// then we can measure the width of that span to resize the input element
useLayoutEffect(() => {
if (sizerRef.current) {
if (sizerRef?.current) {
sizerRef.current.textContent = currentTitle || placeholder;
setInputWidth(sizerRef.current.offsetWidth);
}
}, [currentTitle, placeholder]);
}, [currentTitle, placeholder, sizerRef]);
useEffect(() => {
const inputElement = inputRef.current?.input;
const inputElement = sizerRef.current?.input;
if (inputElement) {
if (inputElement.scrollWidth > inputElement.clientWidth) {
@@ -147,17 +137,9 @@ export const DynamicEditableTitle = memo(
const handleChange = useCallback(
(ev: ChangeEvent<HTMLInputElement>) => {
if (!canEdit) {
if (!canEdit || !isEditing) {
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);
},
[canEdit, isEditing],
@@ -186,7 +168,6 @@ export const DynamicEditableTitle = memo(
}
>
<Input
ref={inputRef}
data-test="editable-title-input"
variant="borderless"
aria-label={label ?? t('Title')}

View File

@@ -17,6 +17,7 @@
* under the License.
*/
import type { ReactNode, SyntheticEvent } from 'react';
import type { IconType } from '@superset-ui/core/components';
export type EmptyStateSize = 'small' | 'medium' | 'large';
@@ -25,7 +26,7 @@ export type EmptyStateProps = {
description?: ReactNode;
image?: ReactNode | string;
buttonText?: ReactNode;
buttonIcon?: ReactNode;
buttonIcon?: IconType;
buttonAction?: (event: SyntheticEvent) => void;
/** Controls image size. Defaults to 'medium'. */
size?: EmptyStateSize;

View File

@@ -20,7 +20,7 @@ import { Form as AntdForm } from 'antd';
import { FormProps } from './types';
function CustomForm(props: FormProps) {
return <AntdForm {...(props as any)} />;
return <AntdForm {...props} />;
}
export const Form = Object.assign(CustomForm, {

View File

@@ -41,6 +41,7 @@ test('renders with monospace prop', () => {
// test stories from the storybook!
test('renders all the storybook gallery variants', () => {
// @ts-expect-error: Suppress TypeScript error for LabelGallery usage
const { container } = render(<LabelGallery />);
const nonInteractiveLabelCount = 4;
const renderedLabelCount = options.length * 2 + nonInteractiveLabelCount;

View File

@@ -21,7 +21,6 @@ import type { BackgroundPosition } from './ImageLoader';
export interface LinkProps {
to: string;
children?: ReactNode;
}
export interface ListViewCardProps {

View File

@@ -194,7 +194,7 @@ const MetadataBar = ({ items, tooltipPlacement = 'top' }: MetadataBarProps) => {
}
const onResize = useCallback(
(width: number | undefined) => {
width => {
// 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.
const breakpoint =

View File

@@ -54,7 +54,7 @@ export function FormModal({
}, [onSave, resetForm]);
const handleFormSubmit = useCallback(
async (values: object) => {
async values => {
try {
setIsSaving(true);
await formSubmitHandler(values);

View File

@@ -104,9 +104,6 @@ export const StyledModal = styled(BaseModal)<StyledModalProps>`
right: 0;
display: flex;
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 {

View File

@@ -17,7 +17,7 @@
* under the License.
*/
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 { DraggableProps } from 'react-draggable';
import { ButtonStyle } from '../Button/types';
@@ -68,8 +68,7 @@ export interface StyledModalProps {
export type { ModalFuncProps };
export interface FormModalProps extends Omit<ModalProps, 'children'> {
children: ReactNode | ((form: FormInstance) => ReactNode);
export interface FormModalProps extends ModalProps {
initialValues?: object;
formSubmitHandler: (values: object) => Promise<void>;
onSave: () => void;

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { ReactNode, ReactElement, memo } from 'react';
import { ReactNode, ReactElement } from 'react';
import { t } from '@apache-superset/core/translation';
import { css, SupersetTheme, useTheme } from '@apache-superset/core/theme';
import { Icons } from '@superset-ui/core/components/Icons';
@@ -118,64 +118,62 @@ export type PageHeaderWithActionsProps = {
};
};
export const PageHeaderWithActions = memo(
({
editableTitleProps,
showTitlePanelItems,
certificatiedBadgeProps,
showFaveStar,
faveStarProps,
titlePanelAdditionalItems,
rightPanelAdditionalItems,
additionalActionsMenu,
menuDropdownProps,
showMenuDropdown = true,
tooltipProps,
}: PageHeaderWithActionsProps) => {
const theme = useTheme();
return (
<div css={headerStyles} className="header-with-actions">
<div className="title-panel">
<DynamicEditableTitle {...editableTitleProps} />
{showTitlePanelItems && (
<div css={buttonsStyles}>
{certificatiedBadgeProps?.certifiedBy && (
<CertifiedBadge {...certificatiedBadgeProps} />
)}
{showFaveStar && <FaveStar {...faveStarProps} />}
{titlePanelAdditionalItems}
</div>
export const PageHeaderWithActions = ({
editableTitleProps,
showTitlePanelItems,
certificatiedBadgeProps,
showFaveStar,
faveStarProps,
titlePanelAdditionalItems,
rightPanelAdditionalItems,
additionalActionsMenu,
menuDropdownProps,
showMenuDropdown = true,
tooltipProps,
}: PageHeaderWithActionsProps) => {
const theme = useTheme();
return (
<div css={headerStyles} className="header-with-actions">
<div className="title-panel">
<DynamicEditableTitle {...editableTitleProps} />
{showTitlePanelItems && (
<div css={buttonsStyles}>
{certificatiedBadgeProps?.certifiedBy && (
<CertifiedBadge {...certificatiedBadgeProps} />
)}
{showFaveStar && <FaveStar {...faveStarProps} />}
{titlePanelAdditionalItems}
</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 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>
);
};

View File

@@ -17,7 +17,7 @@
* under the License.
*/
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 TableCollection from '.';

View File

@@ -91,7 +91,7 @@ export function mapColumns<T extends object>(
return columns.map(column => {
const { isSorted, isSortedDesc } = getSortingInfo(headerGroups, column.id);
return {
title: column.Header as ReactNode,
title: column.Header,
dataIndex: column.id?.includes('.') ? column.id.split('.') : column.id,
hidden: column.hidden,
key: column.id,
@@ -121,7 +121,7 @@ export function mapColumns<T extends object>(
column,
});
}
return val as ReactNode;
return val;
},
className: column.className,
};

View File

@@ -19,14 +19,6 @@
import { render, screen, userEvent, waitFor } from '@superset-ui/core/spec';
import { TableView, TableViewProps } from '.';
// Mock window.scrollTo to prevent jsdom "Not implemented" errors
beforeAll(() => {
window.scrollTo = jest.fn();
});
afterAll(() => {
jest.restoreAllMocks();
});
const mockedProps: TableViewProps = {
columns: [
{
@@ -133,25 +125,27 @@ test('should change page when pagination is clicked', async () => {
expect(screen.getByText('Emily')).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(() => {
expect(screen.getAllByRole('cell')).toHaveLength(3);
expect(screen.getByText('321')).toBeInTheDocument();
expect(screen.getByText('10')).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(() => {
expect(screen.getAllByRole('cell')).toHaveLength(3);
expect(screen.getByText('123')).toBeInTheDocument();
expect(screen.getByText('27')).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 () => {
@@ -246,7 +240,8 @@ test('should handle server-side pagination', async () => {
render(<TableView {...serverPaginationProps} />);
// Click next page
await userEvent.click(screen.getByTitle('Next Page'));
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await waitFor(() => {
expect(onServerPagination).toHaveBeenCalledWith({
@@ -306,7 +301,9 @@ test('should scroll to top when scrollTopOnPagination is true', async () => {
};
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(() => {
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} />);
await userEvent.click(screen.getByTitle('Next Page'));
// Click next page
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await waitFor(() => {
expect(screen.getByText('321')).toBeInTheDocument();

View File

@@ -16,10 +16,10 @@
* specific language governing permissions and limitations
* 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 { 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 TableCollection from '@superset-ui/core/components/TableCollection';
import { TableSize } from '@superset-ui/core/components/Table';
@@ -117,45 +117,43 @@ const RawTableView = ({
...props
}: TableViewProps) => {
const tableRef = useRef<HTMLTableElement>(null);
const effectivePageSize = initialPageSize ?? DEFAULT_PAGE_SIZE;
const [pageIndex, setPageIndex] = useState(initialPageIndex ?? 0);
const initialState = useMemo(
() => ({
pageSize: effectivePageSize,
pageIndex: 0,
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
pageIndex: initialPageIndex ?? 0,
sortBy: initialSortBy,
}),
[effectivePageSize, initialSortBy],
[initialPageSize, initialPageIndex, initialSortBy],
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
page,
rows,
prepareRow,
gotoPage,
setSortBy,
state: { sortBy },
state: { pageIndex, sortBy },
} = useTable(
{
columns,
data,
initialState,
manualPagination: true,
manualPagination: serverPagination,
manualSortBy: serverPagination,
pageCount: serverPagination
? Math.ceil(totalCount / initialState.pageSize)
: undefined,
autoResetSortBy: false,
},
useFilters,
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(() => {
switch (emptyWrapperType) {
case EmptyWrapperType.Small:
@@ -166,6 +164,11 @@ const RawTableView = ({
}
}, [emptyWrapperType]);
const content = useMemo(
() => (withPagination ? page : rows),
[withPagination, page, rows],
);
const isEmpty = useMemo(
() => !loading && content.length === 0,
[loading, content.length],
@@ -189,9 +192,10 @@ const RawTableView = ({
const handlePageChange = useCallback(
(p: number) => {
if (scrollTopOnPagination) handleScrollToTop();
setPageIndex(p);
gotoPage(p);
},
[scrollTopOnPagination, handleScrollToTop],
[scrollTopOnPagination, handleScrollToTop, gotoPage],
);
const paginationProps = useMemo(() => {
@@ -207,7 +211,7 @@ const RawTableView = ({
if (serverPagination) {
return {
pageIndex,
pageSize: effectivePageSize,
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
totalCount,
onPageChange: handlePageChange,
};
@@ -215,7 +219,7 @@ const RawTableView = ({
return {
pageIndex,
pageSize: effectivePageSize,
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
totalCount: data.length,
onPageChange: handlePageChange,
};
@@ -223,28 +227,28 @@ const RawTableView = ({
withPagination,
serverPagination,
pageIndex,
effectivePageSize,
initialPageSize,
totalCount,
data.length,
handlePageChange,
]);
useEffect(() => {
if (serverPagination && pageIndex !== (initialPageIndex ?? 0)) {
if (serverPagination && pageIndex !== initialState.pageIndex) {
onServerPagination({
pageIndex,
});
}
}, [initialPageIndex, onServerPagination, pageIndex, serverPagination]);
}, [initialState.pageIndex, onServerPagination, pageIndex, serverPagination]);
useEffect(() => {
if (serverPagination && !isEqual(sortBy, initialSortBy)) {
if (serverPagination && !isEqual(sortBy, initialState.sortBy)) {
onServerPagination({
pageIndex: 0,
sortBy,
});
}
}, [initialSortBy, onServerPagination, serverPagination, sortBy]);
}, [initialState.sortBy, onServerPagination, serverPagination, sortBy]);
return (
<TableViewStyles {...props} ref={tableRef}>

View File

@@ -97,8 +97,8 @@ const StyledPlus = styled.span`
export default function TruncatedList<ListItemType>({
items,
renderVisibleItem = item => item as ReactNode,
renderTooltipItem = item => item as ReactNode,
renderVisibleItem = item => item,
renderTooltipItem = item => item,
getKey = item => item as unknown as Key,
maxLinks = 20,
}: TruncatedListProps<ListItemType>) {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { useChangeEffect } from './useChangeEffect';
test('call callback the first time with undefined and value', () => {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { useComponentDidMount } from './useComponentDidMount';
test('the effect should only be executed on the first render', () => {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { useComponentDidUpdate } from './useComponentDidUpdate';
test('the effect should not be executed on the first render', () => {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook, act } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { useElementOnScreen } from './useElementOnScreen';
const observeMock = jest.fn();
@@ -46,9 +46,10 @@ test('should return isSticky as true when intersectionRatio < 1', async () => {
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
);
const callback = IntersectionObserverMock.mock.calls[0][0];
act(() => {
callback([{ isIntersecting: true, intersectionRatio: 0.5 }]);
});
const callBack = 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);
});
@@ -57,9 +58,10 @@ test('should return isSticky as false when intersectionRatio >= 1', async () =>
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
);
const callback = IntersectionObserverMock.mock.calls[0][0];
act(() => {
callback([{ isIntersecting: true, intersectionRatio: 1 }]);
});
const callBack = 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);
});

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { usePrevious } from './usePrevious';
test('get undefined on the first render when initialValue is not defined', () => {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import useCSSTextTruncation from './useCSSTextTruncation';
afterEach(() => {

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { RefObject } from 'react';
import useChildElementTruncation from './useChildElementTruncation';

View File

@@ -249,8 +249,7 @@ export type Extensions = Partial<{
'navbar.right-menu.item.icon': ComponentType<RightMenuItemIconProps>;
'navbar.right': ComponentType;
'report-modal.dropdown.item.icon': ComponentType;
'root.context.provider': ComponentType<{ children?: ReactNode }>;
'root.context.provider': ComponentType;
'welcome.message': ComponentType;
'welcome.banner': ComponentType;
'welcome.main.replacement': ComponentType;

View File

@@ -143,7 +143,7 @@ describe('SuperChart', () => {
);
expect(await screen.findByText('Custom Fallback!')).toBeInTheDocument();
expect(CustomFallbackComponent).toHaveBeenCalled();
expect(CustomFallbackComponent).toHaveBeenCalledTimes(1);
});
test('call onErrorBoundary', async () => {
expectedErrors = 1;

View File

@@ -33,7 +33,7 @@
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"react": "^18.2.0"
"react": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -34,6 +34,6 @@
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.2.0"
"react": "^17.0.2"
}
}

View File

@@ -31,7 +31,7 @@
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"react": "^18.2.0"
"react": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -31,7 +31,7 @@
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"react": "^18.2.0"
"react": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -36,6 +36,6 @@
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"react": "^18.2.0"
"react": "^17.0.2"
}
}

View File

@@ -32,9 +32,9 @@
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"@testing-library/react": "^12.1.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -32,7 +32,7 @@
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"react": "^18.2.0"
"react": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -39,6 +39,6 @@
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@apache-superset/core": "*",
"react": "^18.2.0"
"react": "^17.0.2"
}
}

View File

@@ -43,6 +43,6 @@
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"dayjs": "^1.11.19",
"react": "^18.2.0"
"react": "^17.0.2"
}
}

View File

@@ -39,13 +39,14 @@
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@testing-library/dom": "^9.3.4",
"@testing-library/dom": "^8.20.1",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^14.0.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "*",
"@testing-library/user-event": "*",
"@types/react": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -247,7 +247,7 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
[serverPagination, debouncedSearch, searchId],
);
const handleColSort = (colId: string, sortDir: string | null) => {
const handleColSort = (colId: string, sortDir: string) => {
const isSortable = shouldSort({
colId,
sortDir,
@@ -301,12 +301,10 @@ const AgGridDataTable: FunctionComponent<AgGridTableProps> = memo(
};
const handleColumnHeaderClick = useCallback(
(params: { column?: { colId?: string; sort?: string | null } }) => {
params => {
const colId = params?.column?.colId;
const sortDir = params?.column?.sort;
if (colId && sortDir !== undefined) {
handleColSort(colId, sortDir);
}
handleColSort(colId, sortDir);
},
[serverPagination, gridInitialState, percentMetrics, onSortChange],
);

View File

@@ -147,7 +147,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
]);
const handleColumnStateChange = useCallback(
(agGridState: Record<string, unknown>) => {
agGridState => {
if (onChartStateChange) {
onChartStateChange(agGridState);
}

View File

@@ -70,9 +70,5 @@ export const TextCellRenderer = (params: CellRendererProps) => {
}
}
return (
<div>
{valueFormatted ?? (value instanceof Date ? value.toISOString() : value)}
</div>
);
return <div>{valueFormatted ?? value}</div>;
};

View File

@@ -43,7 +43,7 @@ export const shouldSort = ({
gridInitialState,
}: {
colId: string;
sortDir: string | null;
sortDir: string;
percentMetrics: string[];
serverPagination: boolean;
gridInitialState: GridState;

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { GenericDataType } from '@apache-superset/core/common';
import {
supersetTheme,

View File

@@ -47,7 +47,7 @@
"geostyler-wfs-parser": "^3.0.1",
"ol": "^10.8.0",
"polished": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}

View File

@@ -19,7 +19,7 @@
import Layer from 'ol/layer/Layer';
import { FrameState } from 'ol/Map';
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 { ChartConfig, ChartLayerOptions, ChartSizeValues } from '../types';
import { createChartComponent } from '../util/chartUtil';
@@ -31,14 +31,7 @@ import Loader from '../images/loading.gif';
* Custom OpenLayers layer that displays charts on given locations.
*/
export class ChartLayer extends Layer {
charts: {
htmlElement: HTMLDivElement;
root: Root;
coordinate: number[];
width: number;
height: number;
feature: any;
}[] = [];
charts: any[] = [];
chartConfigs: ChartConfig = {
type: 'FeatureCollection',
@@ -173,7 +166,7 @@ export class ChartLayer extends Layer {
*/
removeAllChartElements() {
this.charts.forEach(chart => {
chart.root.unmount();
ReactDOM.unmountComponentAtNode(chart.htmlElement);
chart.htmlElement.remove();
});
this.charts = [];
@@ -198,12 +191,10 @@ export class ChartLayer extends Layer {
this.theme,
this.locale,
);
const root = createRoot(container);
root.render(chartComponent);
ReactDOM.render(chartComponent, container);
return {
htmlElement: container,
root,
coordinate: getProjectedCoordinateFromPointGeoJson(feature.geometry),
width: chartWidth,
height: chartHeight,
@@ -236,7 +227,7 @@ export class ChartLayer extends Layer {
this.theme,
this.locale,
);
chart.root.render(chartComponent);
ReactDOM.render(chartComponent, chart.htmlElement);
return {
...chart,

View File

@@ -41,11 +41,6 @@ describe('ChartLayer', () => {
chartLayer.charts = [
{
htmlElement: document.createElement('div'),
root: { render: jest.fn(), unmount: jest.fn() } as any,
coordinate: [0, 0],
width: 100,
height: 100,
feature: {},
},
];

View File

@@ -38,7 +38,7 @@
"dayjs": "^1.11.19",
"echarts": "*",
"memoize-one": "*",
"react": "^18.2.0"
"react": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -57,7 +57,7 @@ export default function EchartsMixedTimeseries({
);
const getCrossFilterDataMask = useCallback(
(seriesName: string, seriesIndex: number) => {
(seriesName, seriesIndex) => {
const selected: string[] = Object.values(selectedValues || {});
let values: string[];
if (selected.includes(seriesName)) {

View File

@@ -26,7 +26,7 @@ import {
import { useCallback } from 'react';
import Echart from '../components/Echart';
import { NULL_STRING } from '../constants';
import { EventHandlers, TreePathInfo } from '../types';
import { EventHandlers } from '../types';
import { extractTreePathInfo } from './constants';
import { TreemapTransformedProps } from './types';
import { formatSeriesName } from '../utils/series';
@@ -46,7 +46,7 @@ export default function EchartsTreemap({
coltypeMapping,
}: TreemapTransformedProps) {
const getCrossFilterDataMask = useCallback(
(data: Record<string, unknown>, treePathInfo: TreePathInfo[]) => {
(data, treePathInfo) => {
if (data?.children) {
return undefined;
}
@@ -96,7 +96,7 @@ export default function EchartsTreemap({
);
const handleChange = useCallback(
(data: Record<string, unknown>, treePathInfo: TreePathInfo[]) => {
(data, treePathInfo) => {
if (!emitCrossFilters || groupby.length === 0) {
return;
}

View File

@@ -1400,25 +1400,6 @@ test('getAxisType with forced categorical', () => {
);
});
test('getAxisType treats numeric as category for bar charts', () => {
expect(
getAxisType(
false,
false,
GenericDataType.Numeric,
EchartsTimeseriesSeriesType.Bar,
),
).toEqual(AxisType.Category);
expect(
getAxisType(
false,
false,
GenericDataType.Numeric,
EchartsTimeseriesSeriesType.Line,
),
).toEqual(AxisType.Value);
});
test('getMinAndMaxFromBounds returns empty object when not truncating', () => {
expect(
getMinAndMaxFromBounds(

View File

@@ -0,0 +1,95 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
## @superset-ui/plugin-chart-handlebars
[![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-handlebars.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/plugin-chart-handlebars)
[![Libraries.io](https://img.shields.io/librariesio/release/npm/%40superset-ui%2Fplugin-chart-handlebars?style=flat)](https://libraries.io/npm/@superset-ui%2Fplugin-chart-handlebars)
This plugin renders the data using a handlebars template.
### Usage
Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to
lookup this chart throughout the app.
```js
import HandlebarsChartPlugin from '@superset-ui/plugin-chart-handlebars';
new HandlebarsChartPlugin().configure({ key: 'handlebars' }).register();
```
Then use it via `SuperChart`. See
[storybook](https://apache-superset.github.io/superset-ui/?selectedKind=plugin-chart-handlebars) for
more details.
```js
<SuperChart
chartType="handlebars"
width={600}
height={600}
formData={...}
queriesData={[{
data: {...},
}]}
/>
```
### File structure generated
```
├── package.json
├── README.md
├── tsconfig.json
├── src
│   ├── Handlebars.tsx
│   ├── images
│   │   └── thumbnail.png
│   ├── index.ts
│   ├── plugin
│   │   ├── buildQuery.ts
│   │   ├── controlPanel.ts
│   │   ├── index.ts
│   │   └── transformProps.ts
│   └── types.ts
├── test
│   └── index.test.ts
└── types
└── external.d.ts
```
### Available Handlebars Helpers in Superset
Below, you will find a list of all currently registered helpers in the Handlebars plugin for Superset. These helpers are registered and managed in the file [`HandlebarsViewer.tsx`](./path/to/HandlebarsViewer.tsx).
#### List of Registered Helpers:
1. **`dateFormat`**: Formats a date using a specified format.
- **Usage**: `{{dateFormat my_date format="MMMM YYYY"}}`
- **Default format**: `YYYY-MM-DD`.
2. **`stringify`**: Converts an object into a JSON string or returns a string representation of non-object values.
- **Usage**: `{{stringify myObj}}`.
3. **`formatNumber`**: Formats a number using locale-specific formatting.
- **Usage**: `{{formatNumber number locale="en-US"}}`.
- **Default locale**: `en-US`.
4. **`parseJson`**: Parses a JSON string into a JavaScript object.
- **Usage**: `{{parseJson jsonString}}`.

View File

@@ -39,9 +39,9 @@
"handlebars": "^4.7.8",
"lodash": "^4.18.1",
"dayjs": "^1.11.19",
"react": "^18.2.0",
"react": "^17.0.2",
"react-ace": "^10.1.0",
"react-dom": "^18.2.0"
"react-dom": "^17.0.2"
},
"devDependencies": {
"@types/jest": "^30.0.0",

View File

@@ -1,4 +1,4 @@
/**
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
@@ -24,7 +24,7 @@ import {
import { t } from '@apache-superset/core/translation';
import { validateNonEmpty } from '@superset-ui/core';
import { useTheme } from '@apache-superset/core/theme';
import { InfoTooltip } from '@superset-ui/core/components';
import { InfoTooltip, SafeMarkdown } from '@superset-ui/core/components';
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
import { debounceFunc } from '../../consts';
@@ -37,30 +37,44 @@ const HandlebarsTemplateControl = (
props: CustomControlConfig<HandlebarsCustomControlProps>,
) => {
const theme = useTheme();
const val = String(
props?.value ? props?.value : props?.default ? props?.default : '',
);
const helperDescriptionsHeader = t(
'Available Handlebars Helpers in Superset:',
);
const helperDescriptions = [
{ key: 'dateFormat', descKey: 'Formats a date using a specified format.' },
{ key: 'stringify', descKey: 'Converts an object to a JSON string.' },
{
key: 'formatNumber',
descKey: 'Formats a number using locale-specific formatting.',
},
{
key: 'parseJson',
descKey: 'Parses a JSON string into a JavaScript object.',
},
];
const helpersTooltipContent = `
${helperDescriptionsHeader}
${helperDescriptions
.map(({ key, descKey }) => `- **${key}**: ${t(descKey)}`)
.join('\n')}
`;
return (
<div>
<ControlHeader>
<div>
{typeof props.label === 'function' ? null : props.label}
{props.label}
<InfoTooltip
iconStyle={{ marginLeft: theme.sizeUnit }}
tooltip={
<span>
{t('See ')}{' '}
<a
href="https://superset.apache.org/docs/using-superset/handlebars-chart"
target="_blank"
rel="noopener noreferrer"
>
{t('the Handlebars chart documentation')}
</a>{' '}
{t('for a list of available helpers.')}
</span>
}
tooltip={<SafeMarkdown source={helpersTooltipContent} />}
/>
</div>
</ControlHeader>
@@ -90,6 +104,7 @@ export const handlebarsTemplateControlSetItem: ControlSetItem = {
isInt: false,
renderTrigger: true,
valueKey: null,
validators: [validateNonEmpty],
mapStateToProps: ({ form_data }) => ({
value: form_data?.handlebarsTemplate ?? form_data?.handlebars_template,

View File

@@ -49,7 +49,7 @@ const StyleControl = (props: CustomControlConfig<StyleCustomControlProps>) => {
<div>
<ControlHeader>
<div>
{typeof props.label === 'function' ? null : props.label}
{props.label}
{htmlSanitization && (
<InfoTooltip
iconStyle={{ marginLeft: theme.sizeUnit }}

View File

@@ -33,8 +33,8 @@
"@superset-ui/core": "*",
"lodash": "^4.18.1",
"prop-types": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@babel/types": "^7.29.0",

View File

@@ -36,8 +36,8 @@
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^17.0.2 || ^19.0.0",
"react-dom": "^17.0.2 || ^19.0.0"
},
"publishConfig": {
"access": "public"

View File

@@ -39,14 +39,15 @@
"@apache-superset/core": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"@testing-library/dom": "^9.3.4",
"@testing-library/dom": "^8.20.1",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^14.0.0",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "*",
"@testing-library/user-event": "*",
"@types/react": "*",
"match-sorter": "^8.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"publishConfig": {
"access": "public"

View File

@@ -130,12 +130,13 @@ const processComparisonTotals = (
Object.keys(totalRecord).forEach(key => {
if (totalRecord[key] !== undefined && !key.includes(comparisonSuffix)) {
transformedTotals[`Main ${key}`] =
parseFloat(transformedTotals[`Main ${key}`]?.toString() || '0') +
parseFloat(totalRecord[key]?.toString() || '0');
parseInt(transformedTotals[`Main ${key}`]?.toString() || '0', 10) +
parseInt(totalRecord[key]?.toString() || '0', 10);
transformedTotals[`# ${key}`] =
parseFloat(transformedTotals[`# ${key}`]?.toString() || '0') +
parseFloat(
parseInt(transformedTotals[`# ${key}`]?.toString() || '0', 10) +
parseInt(
totalRecord[`${key}__${comparisonSuffix}`]?.toString() || '0',
10,
);
const { valueDifference, percentDifferenceNum } = calculateDifferences(
transformedTotals[`Main ${key}`] as number,

View File

@@ -39,7 +39,7 @@
"@superset-ui/core": "*",
"@types/lodash": "*",
"@types/react": "*",
"react": "^18.2.0"
"react": "^17.0.2"
},
"devDependencies": {
"@types/d3-cloud": "^1.2.9"

View File

@@ -67,8 +67,8 @@
"@superset-ui/core": "*",
"dayjs": "^1.11.19",
"mapbox-gl": ">=1.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^17.0.2 || ^19.0.0",
"react-dom": "^17.0.2 || ^19.0.0"
},
"peerDependenciesMeta": {
"mapbox-gl": {

View File

@@ -139,8 +139,7 @@ const CategoricalDeckGLContainer = (props: CategoricalDeckGLContainerProps) => {
const setTooltip = useCallback((tooltip: TooltipProps['tooltip']) => {
const { current } = containerRef;
if (current) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(current as any).setTooltip(tooltip);
current.setTooltip(tooltip);
}
}, []);

View File

@@ -194,5 +194,5 @@ export const DeckGLContainerStyledWrapper = styled(DeckGLContainer)`
`;
export type DeckGLContainerHandle = typeof DeckGLContainer & {
setTooltip: (tooltip: TooltipProps['tooltip']) => void;
setTooltip: (tooltip: ReactNode) => void;
};

View File

@@ -97,10 +97,10 @@ describe('getAggFunc', () => {
});
describe('commonLayerProps', () => {
const mockSetTooltip = jest.fn() as any;
const mockSetTooltip = jest.fn();
const mockSetTooltipContent = jest.fn(
() => (o: JsonObject) => `Tooltip for ${o}`,
) as any;
);
const mockOnSelect = jest.fn();
test('returns correct props when js_tooltip is provided', () => {

View File

@@ -97,7 +97,6 @@ export function createWrapper(options?: Options) {
}
if (useDnd) {
// @ts-expect-error react-dnd types not updated for React 18
result = <DndProvider backend={HTML5Backend}>{result}</DndProvider>;
}

View File

@@ -65,7 +65,7 @@ const ContentWrapper = styled.div`
overflow: auto;
`;
const AppLayout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const AppLayout: React.FC = ({ children }) => {
const queryEditorId = useSelector<SqlLabRootState, string>(
({ sqlLab: { tabHistory } }) => tabHistory.slice(-1)[0],
);

View File

@@ -19,14 +19,16 @@
import { isValidElement } from 'react';
import { render } from 'spec/helpers/testing-library';
import ColumnElement from 'src/SqlLab/components/ColumnElement';
import { table } from 'src/SqlLab/fixtures';
import { mockedActions, table } from 'src/SqlLab/fixtures';
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('ColumnElement', () => {
const mockedProps = {
actions: mockedActions,
column: table.columns[0],
};
test('is valid with props', () => {
expect(isValidElement(<ColumnElement column={table.columns[0]} />)).toBe(
true,
);
expect(isValidElement(<ColumnElement {...mockedProps} />)).toBe(true);
});
test('renders a proper primary key', () => {
const { container } = render(<ColumnElement column={table.columns[0]} />);

View File

@@ -116,31 +116,19 @@ describe('EditorWrapper', () => {
);
});
test('skips rerendering for updating cursor position', async () => {
test('skips rerendering for updating cursor position', () => {
const store = createStore(initialState, reducerIndex);
setup(defaultQueryEditor, store);
await waitFor(() => expect(MockEditorHost).toHaveBeenCalled());
const renderCountBeforeCursor = MockEditorHost.mock.calls.length;
expect(MockEditorHost).toHaveBeenCalled();
const renderCount = MockEditorHost.mock.calls.length;
const updatedCursorPosition = { row: 1, column: 9 };
act(() => {
store.dispatch(
queryEditorSetCursorPosition(defaultQueryEditor, updatedCursorPosition),
);
});
// Cursor position change should NOT trigger a re-render
expect(MockEditorHost).toHaveBeenCalledTimes(renderCountBeforeCursor);
const renderCountBeforeDb = MockEditorHost.mock.calls.length;
act(() => {
store.dispatch(queryEditorSetDb(defaultQueryEditor, 2));
});
// DB change SHOULD trigger a re-render
await waitFor(() =>
expect(MockEditorHost.mock.calls.length).toBeGreaterThan(
renderCountBeforeDb,
),
store.dispatch(
queryEditorSetCursorPosition(defaultQueryEditor, updatedCursorPosition),
);
expect(MockEditorHost).toHaveBeenCalledTimes(renderCount);
store.dispatch(queryEditorSetDb(defaultQueryEditor, 2));
expect(MockEditorHost).toHaveBeenCalledTimes(renderCount + 1);
});
test('clears selectedText when selection becomes empty', async () => {

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