Compare commits

...

13 Commits

Author SHA1 Message Date
Mehmet Salih Yavuz
76129fc236 fix(CollectionControl): preserve generated id when replacing an item
The WeakMap-stored fallback id was keyed by item ref, but onChangeItem
spreads to produce a new object — orphaning the entry and forcing a
fresh nanoid on every edit. That changed the row's React/dnd key,
remounting the row and dropping focus / local UI state.

Carry the generated id from the old item ref to the new one so keyless
rows keep stable identity across edits.
2026-05-04 20:30:14 +03:00
Mehmet Salih Yavuz
e576d1d2bb fix(CollectionControl): assign stable ids to keyless items
itemIds fell back to String(i) when keyAccessor returned falsy, so two
items with no `key` field collapsed to the same id and broke dnd-kit
reordering after a drag (and React key reuse via shared key).

Maintain a per-instance WeakMap<item, nanoid> so keyless items get a
stable id tied to their object reference. Items with a real key still
use it, so existing call sites are unaffected.
2026-05-04 19:42:59 +03:00
Mehmet Salih Yavuz
41a22d7918 chore: Upgrade to React 18 (#38563)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-05-04 19:19:36 +03:00
Amin Ghadersohi
28239c18d4 feat(mcp): warn when execute_sql template_params used with templating disabled (#39858) 2026-05-04 12:14:44 -04:00
dependabot[bot]
6205afbaa0 chore(deps-dev): bump webpack-sources from 3.4.0 to 3.4.1 in /superset-frontend (#39851) 2026-05-04 22:25:31 +07:00
EMMANUELA OPURUM
dc1c0f6ba1 docs: add user-facing Handlebars chart page with full helpers reference (#39591)
Co-authored-by: Emmanuela Opurum <youremail@example.com>
2026-05-02 13:16:39 -04:00
dependabot[bot]
ad73395c89 chore(deps-dev): bump yeoman-test from 11.3.1 to 11.4.2 in /superset-frontend (#39816)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-02 13:46:47 +07:00
Evan Rusackas
867e173427 chore(deps): drop stale legacy-plugin-chart-map-box lockfile entry (#39825)
Co-authored-by: Superset Dev <dev@superset.apache.org>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 13:35:25 +07:00
dependabot[bot]
c90c8612ad chore(deps): bump @docusaurus/faster from 3.10.0 to 3.10.1 in /docs (#39804)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
2026-05-02 13:32:37 +07:00
Abdul Rehman
b14cca15f6 fix(table): preserve decimals in totals row when Time Comparison is enabled (#39747) 2026-05-02 13:31:54 +07:00
dependabot[bot]
9d4384e49e chore(deps-dev): bump @babel/preset-env from 7.29.2 to 7.29.3 in /superset-frontend (#39822)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-02 12:38:54 +07:00
jesperct
d8dd2d99b3 fix(time-comparison): use chart row_limit instead of instance config in offset queries (#39490) 2026-05-01 16:24:59 -07:00
dependabot[bot]
dbe26d81ce chore(deps-dev): bump baseline-browser-mapping from 2.10.21 to 2.10.24 in /superset-frontend (#39759)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 16:24:23 -07:00
195 changed files with 6062 additions and 7690 deletions

View File

@@ -0,0 +1,143 @@
---
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.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",
"@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",
"@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.0",
"@docusaurus/module-type-aliases": "^3.10.1",
"@docusaurus/tsconfig": "^3.10.1",
"@eslint/js": "^9.39.2",
"@types/js-yaml": "^4.0.9",
@@ -124,8 +124,7 @@
"resolutions": {
"react-redux": "^9.2.0",
"@reduxjs/toolkit": "^2.5.0",
"baseline-browser-mapping": "^2.9.19",
"webpackbar": "^7.0.0"
"baseline-browser-mapping": "^2.9.19"
},
"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.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==
"@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==
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.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/logger" "3.10.1"
"@docusaurus/utils" "3.10.1"
babel-plugin-dynamic-import-node "^2.3.3"
fs-extra "^11.1.1"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@babel/core" "^7.25.9"
"@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"
"@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"
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 "^6.0.1"
webpackbar "^7.0.0"
"@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==
"@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==
dependencies:
"@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"
"@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"
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.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz#be1b435c33df09d743473d3fadda67b4568dfae3"
integrity sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==
"@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==
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.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/faster/-/faster-3.10.0.tgz#0758a93196f685537aa7700bde62faf926e6c817"
integrity sha512-GNPtVH14ISjHfSwnHu3KiFGf86ICmJSQDeSv/QaanpBgiZGOtgZaslnC5q8WiguxM1EVkwcGxPuD8BXF4eggKw==
"@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==
dependencies:
"@docusaurus/types" "3.10.0"
"@docusaurus/types" "3.10.1"
"@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.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.10.0.tgz#2bacbd004dd78e3da926dbe8f6fa9a930856575d"
integrity sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==
"@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==
dependencies:
chalk "^4.1.2"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@docusaurus/logger" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@docusaurus/logger" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@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.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==
"@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==
dependencies:
"@docusaurus/types" "3.10.0"
"@docusaurus/types" "3.10.1"
"@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.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==
"@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==
dependencies:
"@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"
"@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"
eta "^2.2.0"
fs-extra "^11.1.1"
lodash "^4.17.21"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@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"
"@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"
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.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==
"@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==
dependencies:
"@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"
"@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"
"@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.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==
"@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==
dependencies:
"@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"
"@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"
fs-extra "^11.1.1"
tslib "^2.6.0"
webpack "^5.88.1"
"@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==
"@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==
dependencies:
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
fs-extra "^11.1.1"
react-json-view-lite "^2.3.0"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@types/gtag.js" "^0.0.20"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@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"
"@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"
fs-extra "^11.1.1"
sitemap "^7.1.1"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@docusaurus/core" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@docusaurus/core" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@svgr/core" "8.1.0"
"@svgr/webpack" "^8.1.0"
tslib "^2.6.0"
webpack "^5.88.1"
"@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==
"@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==
dependencies:
"@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/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/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==
"@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==
dependencies:
"@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"
"@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"
"@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.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==
"@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==
dependencies:
"@docusaurus/mdx-loader" "3.10.0"
"@docusaurus/module-type-aliases" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/mdx-loader" "3.10.1"
"@docusaurus/module-type-aliases" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
"@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.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==
"@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==
dependencies:
"@docusaurus/core" "3.10.0"
"@docusaurus/theme-common" "3.10.0"
"@docusaurus/theme-translations" "3.10.0"
"@docusaurus/utils-validation" "3.10.0"
"@docusaurus/core" "3.10.1"
"@docusaurus/theme-common" "3.10.1"
"@docusaurus/theme-translations" "3.10.1"
"@docusaurus/utils-validation" "3.10.1"
"@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.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==
"@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==
dependencies:
"@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"
"@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"
mermaid ">=11.6.0"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@algolia/autocomplete-core" "^1.19.2"
"@docsearch/react" "^3.9.0 || ^4.3.2"
"@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"
"@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"
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.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==
"@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==
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.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.10.0.tgz#a69232bba74b738fcf4671fd5f0f079366dd3d13"
integrity sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==
"@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==
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.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.10.0.tgz#2a6dc76b312664fca7234d33607c085318ff1ae3"
integrity sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==
"@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==
dependencies:
"@docusaurus/types" "3.10.0"
"@docusaurus/types" "3.10.1"
tslib "^2.6.0"
"@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==
"@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==
dependencies:
"@docusaurus/logger" "3.10.0"
"@docusaurus/utils" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/logger" "3.10.1"
"@docusaurus/utils" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
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.0":
version "3.10.0"
resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.10.0.tgz#ea7d7b0d325b60f728decc00bb3908d00ef86faf"
integrity sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==
"@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==
dependencies:
"@docusaurus/logger" "3.10.0"
"@docusaurus/types" "3.10.0"
"@docusaurus/utils-common" "3.10.0"
"@docusaurus/logger" "3.10.1"
"@docusaurus/types" "3.10.1"
"@docusaurus/utils-common" "3.10.1"
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.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
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@^6.0.1, webpackbar@^7.0.0:
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==

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": "^17.0.2",
"react": "^18.2.0",
"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": "^17.0.2",
"react-dom": "^18.2.0",
"react-google-recaptcha": "^3.1.0",
"react-intersection-observer": "^10.0.3",
"react-json-tree": "^0.20.0",
@@ -211,7 +211,6 @@
"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",
@@ -244,14 +243,13 @@
"@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.2",
"@babel/preset-env": "^7.29.3",
"@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",
@@ -273,10 +271,9 @@
"@swc/core": "^1.15.32",
"@swc/plugin-emotion": "^14.9.0",
"@swc/plugin-transform-imports": "^12.5.0",
"@testing-library/dom": "^8.20.1",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^12.8.3",
"@types/content-disposition": "^0.5.9",
"@types/dom-to-image": "^2.6.7",
@@ -286,8 +283,8 @@
"@types/json-bigint": "^1.0.4",
"@types/mousetrap": "^1.6.15",
"@types/node": "^25.6.0",
"@types/react": "^17.0.83",
"@types/react-dom": "^17.0.26",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/react-loadable": "^5.5.11",
"@types/react-redux": "^7.1.10",
"@types/react-resizable": "^3.0.8",
@@ -306,7 +303,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.21",
"baseline-browser-mapping": "^2.10.24",
"cheerio": "1.2.0",
"concurrently": "^9.2.1",
"copy-webpack-plugin": "^14.0.0",
@@ -373,7 +370,7 @@
"webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.3",
"webpack-manifest-plugin": "^5.0.1",
"webpack-sources": "^3.4.0",
"webpack-sources": "^3.4.1",
"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.3.1"
"yeoman-test": "^11.4.2"
},
"engines": {
"npm": ">= 4.0.0",

View File

@@ -75,16 +75,15 @@
"devDependencies": {
"@babel/cli": "^7.28.6",
"@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.2",
"@babel/preset-env": "^7.29.3",
"@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": "^8.20.1",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "*",
"@testing-library/react": "^12.1.5",
"@testing-library/react-hooks": "*",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "*",
"@types/react": "*",
"@types/react-loadable": "*",
@@ -98,8 +97,8 @@
"@fontsource/ibm-plex-mono": "^5.2.7",
"@fontsource/inter": "^5.2.6",
"nanoid": "^5.0.9",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"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 } from '@testing-library/react';
import { render, RenderOptions, RenderResult } 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'>,
) => render(ui, { wrapper: Providers, ...options });
): RenderResult => 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-hooks';
import { renderHook } from '@testing-library/react';
import { ThemeProvider } from '@emotion/react';
import { theme as antdTheme } from 'antd';
import {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,6 @@ 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 };
@@ -49,5 +48,5 @@ export type ButtonProps = Omit<AntdButtonProps, 'css'> & {
buttonStyle?: ButtonStyle;
cta?: boolean;
showMarginRight?: boolean;
icon?: IconType;
icon?: ReactNode;
};

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 => {
(value: OverflowingState) => {
if (!isEqual(overflowingState, value)) {
setItems(generateItems(value));
setOverflowingState(value);

View File

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

View File

@@ -0,0 +1,80 @@
/**
* 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,6 +23,7 @@ import {
useCallback,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import { t } from '@apache-superset/core/translation';
@@ -30,6 +31,7 @@ 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`
@@ -75,8 +77,10 @@ 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 { width: inputWidth, ref: sizerRef } = useResizeDetector();
const sizerRef = useRef<HTMLSpanElement>(null);
const inputRef = useRef<InputRef>(null);
const { width: containerWidth, ref: containerRef } = useResizeDetector({
refreshMode: 'debounce',
});
@@ -85,27 +89,33 @@ export const DynamicEditableTitle = memo(
setCurrentTitle(title);
}, [title]);
useEffect(() => {
if (isEditing && sizerRef?.current) {
if (isEditing) {
// move cursor and scroll to the end
if (sizerRef.current.setSelectionRange) {
const { length } = sizerRef.current.value;
sizerRef.current.setSelectionRange(length, length);
sizerRef.current.scrollLeft = sizerRef.current.scrollWidth;
const inputElement = inputRef.current?.input;
if (inputElement) {
const { length } = inputElement.value;
inputElement.setSelectionRange(length, length);
inputElement.scrollLeft = inputElement.scrollWidth;
}
}
}, [isEditing]);
// a trick to make the input grow when user types text
// 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
// 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.
useLayoutEffect(() => {
if (sizerRef?.current) {
if (sizerRef.current) {
sizerRef.current.textContent = currentTitle || placeholder;
setInputWidth(sizerRef.current.offsetWidth);
}
}, [currentTitle, placeholder, sizerRef]);
}, [currentTitle, placeholder]);
useEffect(() => {
const inputElement = sizerRef.current?.input;
const inputElement = inputRef.current?.input;
if (inputElement) {
if (inputElement.scrollWidth > inputElement.clientWidth) {
@@ -137,9 +147,17 @@ export const DynamicEditableTitle = memo(
const handleChange = useCallback(
(ev: ChangeEvent<HTMLInputElement>) => {
if (!canEdit || !isEditing) {
if (!canEdit) {
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],
@@ -168,6 +186,7 @@ export const DynamicEditableTitle = memo(
}
>
<Input
ref={inputRef}
data-test="editable-title-input"
variant="borderless"
aria-label={label ?? t('Title')}

View File

@@ -17,7 +17,6 @@
* under the License.
*/
import type { ReactNode, SyntheticEvent } from 'react';
import type { IconType } from '@superset-ui/core/components';
export type EmptyStateSize = 'small' | 'medium' | 'large';
@@ -26,7 +25,7 @@ export type EmptyStateProps = {
description?: ReactNode;
image?: ReactNode | string;
buttonText?: ReactNode;
buttonIcon?: IconType;
buttonIcon?: ReactNode;
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} />;
return <AntdForm {...(props as any)} />;
}
export const Form = Object.assign(CustomForm, {

View File

@@ -41,7 +41,6 @@ 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,6 +21,7 @@ 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 => {
(width: number | undefined) => {
// 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 => {
async (values: object) => {
try {
setIsSaving(true);
await formSubmitHandler(values);

View File

@@ -104,6 +104,9 @@ 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 { ModalFuncProps } from 'antd';
import type { FormInstance, ModalFuncProps } from 'antd';
import type { ResizableProps } from 're-resizable';
import type { DraggableProps } from 'react-draggable';
import { ButtonStyle } from '../Button/types';
@@ -68,7 +68,8 @@ export interface StyledModalProps {
export type { ModalFuncProps };
export interface FormModalProps extends ModalProps {
export interface FormModalProps extends Omit<ModalProps, 'children'> {
children: ReactNode | ((form: FormInstance) => ReactNode);
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 } from 'react';
import { ReactNode, ReactElement, memo } 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,62 +118,64 @@ export type PageHeaderWithActionsProps = {
};
};
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>
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>
)}
</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-hooks';
import { renderHook } from '@testing-library/react';
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,
title: column.Header as ReactNode,
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;
return val as ReactNode;
},
className: column.className,
};

View File

@@ -19,6 +19,14 @@
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: [
{
@@ -125,27 +133,25 @@ test('should change page when pagination is clicked', async () => {
expect(screen.getByText('Emily')).toBeInTheDocument();
expect(screen.queryByText('Kate')).not.toBeInTheDocument();
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await userEvent.click(screen.getByTitle('Next Page'));
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();
const page1 = screen.getByRole('listitem', { name: '1' });
await userEvent.click(page1);
await userEvent.click(screen.getByTitle('Previous Page'));
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 () => {
@@ -240,8 +246,7 @@ test('should handle server-side pagination', async () => {
render(<TableView {...serverPaginationProps} />);
// Click next page
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await userEvent.click(screen.getByTitle('Next Page'));
await waitFor(() => {
expect(onServerPagination).toHaveBeenCalledWith({
@@ -301,9 +306,7 @@ test('should scroll to top when scrollTopOnPagination is true', async () => {
};
render(<TableView {...scrollProps} />);
// Click next page
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await userEvent.click(screen.getByTitle('Next Page'));
await waitFor(() => {
expect(scrollToSpy).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
@@ -324,9 +327,7 @@ test('should NOT scroll to top when scrollTopOnPagination is false', async () =>
};
render(<TableView {...scrollProps} />);
// Click next page
const page2 = screen.getByRole('listitem', { name: '2' });
await userEvent.click(page2);
await userEvent.click(screen.getByTitle('Next Page'));
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 } from 'react';
import { memo, useEffect, useRef, useMemo, useCallback, useState } from 'react';
import { isEqual } from 'lodash';
import { styled } from '@apache-superset/core/theme';
import { useFilters, usePagination, useSortBy, useTable } from 'react-table';
import { useFilters, 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,43 +117,45 @@ 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: initialPageSize ?? DEFAULT_PAGE_SIZE,
pageIndex: initialPageIndex ?? 0,
pageSize: effectivePageSize,
pageIndex: 0,
sortBy: initialSortBy,
}),
[initialPageSize, initialPageIndex, initialSortBy],
[effectivePageSize, initialSortBy],
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
page,
rows,
prepareRow,
gotoPage,
setSortBy,
state: { pageIndex, sortBy },
state: { sortBy },
} = useTable(
{
columns,
data,
initialState,
manualPagination: serverPagination,
manualPagination: true,
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:
@@ -164,11 +166,6 @@ const RawTableView = ({
}
}, [emptyWrapperType]);
const content = useMemo(
() => (withPagination ? page : rows),
[withPagination, page, rows],
);
const isEmpty = useMemo(
() => !loading && content.length === 0,
[loading, content.length],
@@ -192,10 +189,9 @@ const RawTableView = ({
const handlePageChange = useCallback(
(p: number) => {
if (scrollTopOnPagination) handleScrollToTop();
gotoPage(p);
setPageIndex(p);
},
[scrollTopOnPagination, handleScrollToTop, gotoPage],
[scrollTopOnPagination, handleScrollToTop],
);
const paginationProps = useMemo(() => {
@@ -211,7 +207,7 @@ const RawTableView = ({
if (serverPagination) {
return {
pageIndex,
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
pageSize: effectivePageSize,
totalCount,
onPageChange: handlePageChange,
};
@@ -219,7 +215,7 @@ const RawTableView = ({
return {
pageIndex,
pageSize: initialPageSize ?? DEFAULT_PAGE_SIZE,
pageSize: effectivePageSize,
totalCount: data.length,
onPageChange: handlePageChange,
};
@@ -227,28 +223,28 @@ const RawTableView = ({
withPagination,
serverPagination,
pageIndex,
initialPageSize,
effectivePageSize,
totalCount,
data.length,
handlePageChange,
]);
useEffect(() => {
if (serverPagination && pageIndex !== initialState.pageIndex) {
if (serverPagination && pageIndex !== (initialPageIndex ?? 0)) {
onServerPagination({
pageIndex,
});
}
}, [initialState.pageIndex, onServerPagination, pageIndex, serverPagination]);
}, [initialPageIndex, onServerPagination, pageIndex, serverPagination]);
useEffect(() => {
if (serverPagination && !isEqual(sortBy, initialState.sortBy)) {
if (serverPagination && !isEqual(sortBy, initialSortBy)) {
onServerPagination({
pageIndex: 0,
sortBy,
});
}
}, [initialState.sortBy, onServerPagination, serverPagination, sortBy]);
}, [initialSortBy, 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,
renderTooltipItem = item => item,
renderVisibleItem = item => item as ReactNode,
renderTooltipItem = item => item as ReactNode,
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-hooks';
import { renderHook } from '@testing-library/react';
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-hooks';
import { renderHook } from '@testing-library/react';
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-hooks';
import { renderHook } from '@testing-library/react';
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 } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import { useElementOnScreen } from './useElementOnScreen';
const observeMock = jest.fn();
@@ -46,10 +46,9 @@ test('should return isSticky as true when intersectionRatio < 1', async () => {
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
);
const callback = IntersectionObserverMock.mock.calls[0][0];
const callBack = callback([{ isIntersecting: true, intersectionRatio: 0.5 }]);
const observer = new IntersectionObserverMock(callBack, {});
const newDiv = document.createElement('div');
observer.observe(newDiv);
act(() => {
callback([{ isIntersecting: true, intersectionRatio: 0.5 }]);
});
expect(hook.result.current[1]).toEqual(true);
});
@@ -58,10 +57,9 @@ test('should return isSticky as false when intersectionRatio >= 1', async () =>
useElementOnScreen({ rootMargin: '-50px 0px 0px 0px' }),
);
const callback = IntersectionObserverMock.mock.calls[0][0];
const callBack = callback([{ isIntersecting: true, intersectionRatio: 1 }]);
const observer = new IntersectionObserverMock(callBack, {});
const newDiv = document.createElement('div');
observer.observe(newDiv);
act(() => {
callback([{ isIntersecting: true, intersectionRatio: 1 }]);
});
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-hooks';
import { renderHook } from '@testing-library/react';
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-hooks';
import { renderHook } from '@testing-library/react';
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-hooks';
import { renderHook } from '@testing-library/react';
import { RefObject } from 'react';
import useChildElementTruncation from './useChildElementTruncation';

View File

@@ -249,7 +249,8 @@ export type Extensions = Partial<{
'navbar.right-menu.item.icon': ComponentType<RightMenuItemIconProps>;
'navbar.right': ComponentType;
'report-modal.dropdown.item.icon': ComponentType;
'root.context.provider': ComponentType;
'root.context.provider': ComponentType<{ children?: ReactNode }>;
'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).toHaveBeenCalledTimes(1);
expect(CustomFallbackComponent).toHaveBeenCalled();
});
test('call onErrorBoundary', async () => {
expectedErrors = 1;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ export const shouldSort = ({
gridInitialState,
}: {
colId: string;
sortDir: string;
sortDir: string | null;
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-hooks';
import { renderHook } from '@testing-library/react';
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": "^17.0.2",
"react-dom": "^17.0.2"
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

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

View File

@@ -41,6 +41,11 @@ 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": "^17.0.2"
"react": "^18.2.0"
},
"publishConfig": {
"access": "public"

View File

@@ -57,7 +57,7 @@ export default function EchartsMixedTimeseries({
);
const getCrossFilterDataMask = useCallback(
(seriesName, seriesIndex) => {
(seriesName: string, seriesIndex: number) => {
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 } from '../types';
import { EventHandlers, TreePathInfo } 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, treePathInfo) => {
(data: Record<string, unknown>, treePathInfo: TreePathInfo[]) => {
if (data?.children) {
return undefined;
}
@@ -96,7 +96,7 @@ export default function EchartsTreemap({
);
const handleChange = useCallback(
(data, treePathInfo) => {
(data: Record<string, unknown>, treePathInfo: TreePathInfo[]) => {
if (!emitCrossFilters || groupby.length === 0) {
return;
}

View File

@@ -1,95 +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.
-->
## @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": "^17.0.2",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-dom": "^17.0.2"
"react-dom": "^18.2.0"
},
"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, SafeMarkdown } from '@superset-ui/core/components';
import { InfoTooltip } from '@superset-ui/core/components';
import { CodeEditor } from '../../components/CodeEditor/CodeEditor';
import { ControlHeader } from '../../components/ControlHeader/controlHeader';
import { debounceFunc } from '../../consts';
@@ -37,44 +37,30 @@ 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>
{props.label}
{typeof props.label === 'function' ? null : props.label}
<InfoTooltip
iconStyle={{ marginLeft: theme.sizeUnit }}
tooltip={<SafeMarkdown source={helpersTooltipContent} />}
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>
}
/>
</div>
</ControlHeader>
@@ -104,7 +90,6 @@ 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>
{props.label}
{typeof props.label === 'function' ? null : props.label}
{htmlSanitization && (
<InfoTooltip
iconStyle={{ marginLeft: theme.sizeUnit }}

View File

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

View File

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

View File

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

View File

@@ -533,7 +533,12 @@ export default function TableChart<D extends DataRecord = DataRecord>(
// so that cross-filters work on the receiving chart
const resolvedCol = columnLabelToNameMap[col] ?? col;
const val = ensureIsArray(updatedFilters?.[col]);
if (!val.length || val[0] === null || (val[0] instanceof DateWithFormatter && val[0].input === null))
if (
!val.length ||
val[0] === null ||
(val[0] instanceof DateWithFormatter &&
val[0].input === null)
)
return {
col: resolvedCol,
op: 'IS NULL' as const,
@@ -646,24 +651,22 @@ export default function TableChart<D extends DataRecord = DataRecord>(
// DateWithFormatter objects wrap nulls, so we must check both
if (
dataRecordValue == null ||
(dataRecordValue instanceof DateWithFormatter && dataRecordValue.input == null)
(dataRecordValue instanceof DateWithFormatter &&
dataRecordValue.input == null)
) {
drillToDetailFilters.push({
col: col.key,
op: 'IS NULL' as any,
val: null,
});
} else if (col.dataType === GenericDataType.Temporal && timeGrain) {
const startTime =
dataRecordValue instanceof Date
? dataRecordValue
: new Date(dataRecordValue as string | number);
const [rangeStartTime, rangeEndTime] = getTimeRangeFromGranularity(
startTime,
timeGrain,
);
const [rangeStartTime, rangeEndTime] =
getTimeRangeFromGranularity(startTime, timeGrain);
const timeRangeValue = `${rangeStartTime.toISOString()} : ${rangeEndTime.toISOString()}`;
drillToDetailFilters.push({
@@ -696,7 +699,11 @@ export default function TableChart<D extends DataRecord = DataRecord>(
filters: [
{
col: cellPoint.key,
op: (cellPoint.value == null || (cellPoint.value instanceof DateWithFormatter && cellPoint.value.input == null) ? 'IS NULL' : '==') as any,
op: (cellPoint.value == null ||
(cellPoint.value instanceof DateWithFormatter &&
cellPoint.value.input == null)
? 'IS NULL'
: '==') as any,
val: extractTextFromHTML(cellPoint.value),
},
],

View File

@@ -130,13 +130,12 @@ const processComparisonTotals = (
Object.keys(totalRecord).forEach(key => {
if (totalRecord[key] !== undefined && !key.includes(comparisonSuffix)) {
transformedTotals[`Main ${key}`] =
parseInt(transformedTotals[`Main ${key}`]?.toString() || '0', 10) +
parseInt(totalRecord[key]?.toString() || '0', 10);
parseFloat(transformedTotals[`Main ${key}`]?.toString() || '0') +
parseFloat(totalRecord[key]?.toString() || '0');
transformedTotals[`# ${key}`] =
parseInt(transformedTotals[`# ${key}`]?.toString() || '0', 10) +
parseInt(
parseFloat(transformedTotals[`# ${key}`]?.toString() || '0') +
parseFloat(
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": "^17.0.2"
"react": "^18.2.0"
},
"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": "^17.0.2 || ^19.0.0",
"react-dom": "^17.0.2 || ^19.0.0"
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"peerDependenciesMeta": {
"mapbox-gl": {

View File

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

View File

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

View File

@@ -97,10 +97,10 @@ describe('getAggFunc', () => {
});
describe('commonLayerProps', () => {
const mockSetTooltip = jest.fn();
const mockSetTooltip = jest.fn() as any;
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,6 +97,7 @@ 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 }) => {
const AppLayout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const queryEditorId = useSelector<SqlLabRootState, string>(
({ sqlLab: { tabHistory } }) => tabHistory.slice(-1)[0],
);

View File

@@ -19,16 +19,14 @@
import { isValidElement } from 'react';
import { render } from 'spec/helpers/testing-library';
import ColumnElement from 'src/SqlLab/components/ColumnElement';
import { mockedActions, table } from 'src/SqlLab/fixtures';
import { 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 {...mockedProps} />)).toBe(true);
expect(isValidElement(<ColumnElement column={table.columns[0]} />)).toBe(
true,
);
});
test('renders a proper primary key', () => {
const { container } = render(<ColumnElement column={table.columns[0]} />);

View File

@@ -116,19 +116,31 @@ describe('EditorWrapper', () => {
);
});
test('skips rerendering for updating cursor position', () => {
test('skips rerendering for updating cursor position', async () => {
const store = createStore(initialState, reducerIndex);
setup(defaultQueryEditor, store);
expect(MockEditorHost).toHaveBeenCalled();
const renderCount = MockEditorHost.mock.calls.length;
await waitFor(() => expect(MockEditorHost).toHaveBeenCalled());
const renderCountBeforeCursor = MockEditorHost.mock.calls.length;
const updatedCursorPosition = { row: 1, column: 9 };
store.dispatch(
queryEditorSetCursorPosition(defaultQueryEditor, updatedCursorPosition),
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,
),
);
expect(MockEditorHost).toHaveBeenCalledTimes(renderCount);
store.dispatch(queryEditorSetDb(defaultQueryEditor, 2));
expect(MockEditorHost).toHaveBeenCalledTimes(renderCount + 1);
});
test('clears selectedText when selection becomes empty', async () => {

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import fetchMock from 'fetch-mock';
import { act, renderHook } from '@testing-library/react-hooks';
import { act, renderHook, waitFor } from '@testing-library/react';
import { COMMON_ERR_MESSAGES } from '@superset-ui/core';
import {
createWrapper,
@@ -121,7 +121,7 @@ test('skips fetching validation if validator is undefined', () => {
});
test('returns validation if validator is configured', async () => {
const { result, waitFor } = initialize(true);
const { result } = initialize(true);
await waitFor(() =>
expect(fetchMock.callHistory.calls(queryValidationApiRoute)).toHaveLength(
1,
@@ -143,7 +143,7 @@ test('returns server error description', async () => {
fetchMock.post(queryValidationApiRoute, {
throws: new Error(errorMessage),
});
const { result, waitFor } = initialize(true);
const { result } = initialize(true);
await waitFor(
() =>
expect(result.current.data).toEqual([
@@ -164,7 +164,7 @@ test('returns session expire description when CSRF token expired', async () => {
fetchMock.post(queryValidationApiRoute, {
throws: new Error(errorMessage),
});
const { result, waitFor } = initialize(true);
const { result } = initialize(true);
await waitFor(
() =>
expect(result.current.data).toEqual([

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import fetchMock from 'fetch-mock';
import { act, renderHook } from '@testing-library/react-hooks';
import { act, renderHook, waitFor } from '@testing-library/react';
import { getExtensionsRegistry } from '@superset-ui/core';
import {
createWrapper,
@@ -104,7 +104,7 @@ test('returns keywords including fetched function_names data', async () => {
const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);
const { result, waitFor } = renderHook(
const { result } = renderHook(
() =>
useKeywords({
queryEditorId: 'testqueryid',
@@ -241,7 +241,7 @@ test('returns column keywords among selected tables', async () => {
);
});
const { result, waitFor } = renderHook(
const { result } = renderHook(
() =>
useKeywords({
queryEditorId: expectQueryEditorId,
@@ -302,7 +302,7 @@ test('returns long keywords with detail', async () => {
),
);
});
const { result, waitFor } = renderHook(
const { result } = renderHook(
() =>
useKeywords({
queryEditorId: 'testqueryid',

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { FC } from 'react';
import { FC, ReactNode } from 'react';
import { t } from '@apache-superset/core/translation';
import { styled, css } from '@apache-superset/core/theme';
import { ModalTrigger } from '@superset-ui/core/components';
@@ -92,7 +92,7 @@ const ShortcutCode = styled.code`
padding: ${({ theme }) => `${theme.sizeUnit}px ${theme.sizeUnit * 2}px`};
`;
const KeyboardShortcutButton: FC<{}> = ({ children }) => (
const KeyboardShortcutButton: FC<{ children?: ReactNode }> = ({ children }) => (
<ModalTrigger
modalTitle={t('Keyboard shortcuts')}
modalBody={

View File

@@ -39,7 +39,9 @@ import getBootstrapData from 'src/utils/getBootstrapData';
const SQL_LAB_URL = '/sqllab';
const PopEditorTab: React.FC = ({ children }) => {
const PopEditorTab: React.FC<{ children?: React.ReactNode }> = ({
children,
}) => {
const [isLoading, setIsLoading] = useState(false);
const [queryEditorId, setQueryEditorId] = useState<string>();
const { requestedQuery } = useLocationState();

View File

@@ -50,14 +50,23 @@ import { StaticPosition, StyledTooltip, ModalResultSetWrapper } from './styles';
interface QueryTableQuery extends Omit<
QueryResponse,
'state' | 'sql' | 'progress' | 'results' | 'duration' | 'started'
| 'state'
| 'sql'
| 'progress'
| 'results'
| 'duration'
| 'started'
| 'user'
| 'db'
> {
state?: Record<string, any>;
sql?: Record<string, any>;
progress?: Record<string, any>;
results?: Record<string, any>;
state?: ReactNode;
sql?: ReactNode;
progress?: ReactNode;
results?: ReactNode;
duration?: ReactNode;
started?: ReactNode;
user?: ReactNode;
db?: ReactNode;
}
interface QueryTableProps {
@@ -249,7 +258,7 @@ const QueryTable = ({
return queries
.map(query => {
const { state, sql, progress, ...rest } = query;
const { state, sql, progress, results: _results, ...rest } = query;
const q = rest as QueryTableQuery;
const status = statusAttributes[state] || statusAttributes.error;
@@ -265,7 +274,7 @@ const QueryTable = ({
buttonStyle="link"
onClick={() => onUserClicked(q.userId)}
>
{q.user}
{q.user as ReactNode}
</Button>
);
q.db = (
@@ -274,7 +283,7 @@ const QueryTable = ({
buttonStyle="link"
onClick={() => onDbClicked(q.dbId)}
>
{q.db}
{q.db as ReactNode}
</Button>
);
q.started = (

View File

@@ -16,13 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
import { useMemo, FC, ReactElement } from 'react';
import { useMemo, FC, ReactElement, type ReactNode } from 'react';
import { t } from '@apache-superset/core/translation';
import { styled, useTheme, SupersetTheme } from '@apache-superset/core/theme';
import { Button, DropdownButton } from '@superset-ui/core/components';
import { IconType, Icons } from '@superset-ui/core/components/Icons';
import { Icons } from '@superset-ui/core/components/Icons';
import { detectOS } from 'src/utils/common';
import { QueryButtonProps } from 'src/SqlLab/types';
import useQueryEditor from 'src/SqlLab/hooks/useQueryEditor';
@@ -45,9 +45,9 @@ const buildTextAndIcon = (
shouldShowStopButton: boolean,
selectedText: string | undefined,
theme: SupersetTheme,
): { text: string; icon?: IconType } => {
): { text: string; icon?: ReactNode } => {
let text = t('Run');
let icon: IconType | undefined = <Icons.CaretRightOutlined />;
let icon: ReactNode = <Icons.CaretRightOutlined />;
if (selectedText) {
text = t('Run selection');
icon = <Icons.StepForwardOutlined />;

View File

@@ -17,6 +17,7 @@
* under the License.
*/
import * as reactRedux from 'react-redux';
import { act } from 'react';
import {
cleanup,
fireEvent,
@@ -136,7 +137,7 @@ describe('SaveDatasetModal', () => {
const overwriteRadioBtn = screen.getByRole('radio', {
name: /overwrite existing/i,
});
userEvent.click(overwriteRadioBtn);
await userEvent.click(overwriteRadioBtn);
// Overwrite confirmation button should be disabled at this point
const overwriteConfirmationBtn = screen.getByRole('button', {
@@ -146,15 +147,21 @@ describe('SaveDatasetModal', () => {
// Click the overwrite select component
const select = screen.getByRole('combobox', { name: /existing dataset/i })!;
userEvent.click(select);
await userEvent.click(select);
await waitFor(() =>
expect(screen.queryByText('Loading...')).not.toBeVisible(),
);
// Advance timers to flush debounced fetches in AsyncSelect
await act(async () => {
jest.runAllTimers();
});
await waitFor(() => {
const loading = screen.queryByText('Loading...');
expect(loading === null || !loading.checkVisibility()).toBe(true);
});
// Select the first "existing dataset" from the listbox
const option = screen.getAllByText('coolest table 0')[1];
userEvent.click(option);
await userEvent.click(option);
// Overwrite button should now be enabled
expect(overwriteConfirmationBtn).toBeEnabled();
@@ -168,25 +175,31 @@ describe('SaveDatasetModal', () => {
const overwriteRadioBtn = screen.getByRole('radio', {
name: /overwrite existing/i,
});
userEvent.click(overwriteRadioBtn);
await userEvent.click(overwriteRadioBtn);
// Click the overwrite select component
const select = screen.getByRole('combobox', { name: /existing dataset/i });
userEvent.click(select);
await userEvent.click(select);
await waitFor(() =>
expect(screen.queryByText('Loading...')).not.toBeVisible(),
);
// Advance timers to flush debounced fetches in AsyncSelect
await act(async () => {
jest.runAllTimers();
});
await waitFor(() => {
const loading = screen.queryByText('Loading...');
expect(loading === null || !loading.checkVisibility()).toBe(true);
});
// Select the first "existing dataset" from the listbox
const option = screen.getAllByText('coolest table 0')[1];
userEvent.click(option);
await userEvent.click(option);
// Click the overwrite button to access the confirmation screen
const overwriteConfirmationBtn = screen.getByRole('button', {
name: /overwrite/i,
});
userEvent.click(overwriteConfirmationBtn);
await userEvent.click(overwriteConfirmationBtn);
// Overwrite screen text
expect(screen.getByText(/save or overwrite dataset/i)).toBeInTheDocument();

View File

@@ -140,7 +140,7 @@ const SouthPane = ({
logAction(LOG_ACTIONS_SQLLAB_SWITCH_SOUTH_PANE_TAB, { tab: id });
};
const removeTable = useCallback(
(key, action) => {
(key: string, action: string) => {
if (action === 'remove') {
const table = pinnedTables.find(
({ dbId, catalog, schema, name }) =>

View File

@@ -292,7 +292,10 @@ const SqlEditor: FC<Props> = ({
const SqlFormExtension = extensionsRegistry.get('sqleditor.extension.form');
const startQuery = useCallback(
(ctasArg = false, ctas_method = CtasEnum.Table) => {
(
ctasArg = false,
ctas_method: (typeof CtasEnum)[keyof typeof CtasEnum] = CtasEnum.Table,
) => {
if (!database) {
return;
}

View File

@@ -18,7 +18,7 @@
*/
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { renderHook, act } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import { createWrapper } from 'spec/helpers/testing-library';
import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures';
import * as localStorageHelpers from 'src/utils/localStorageHelpers';

View File

@@ -19,7 +19,7 @@
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { initialState, defaultQueryEditor } from 'src/SqlLab/fixtures';
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import { createWrapper } from 'spec/helpers/testing-library';
import useQueryEditor from '.';

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { act, renderHook } from '@testing-library/react-hooks';
import { act, renderHook, waitFor } from '@testing-library/react';
import { SupersetClient } from '@superset-ui/core';
import { useDeckLayerMetadata } from './useDeckLayerMetadata';
@@ -52,15 +52,14 @@ test('fetches layer metadata successfully', async () => {
};
mockSupersetClientGet.mockResolvedValue(mockResponse);
const { result, waitForNextUpdate } = renderHook(() =>
useDeckLayerMetadata([1, 2]),
);
const { result } = renderHook(() => useDeckLayerMetadata([1, 2]));
expect(result.current.isLoading).toBe(true);
await waitForNextUpdate();
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.isLoading).toBe(false);
expect(result.current.layers).toEqual([
{ sliceId: 1, name: 'Layer 1', type: 'deck_scatter' },
{ sliceId: 2, name: 'Layer 2', type: 'deck_arc' },
@@ -75,13 +74,12 @@ test('handles API error and returns fallback layers', async () => {
const errorMessage = 'Network error';
mockSupersetClientGet.mockRejectedValue(new Error(errorMessage));
const { result, waitForNextUpdate } = renderHook(() =>
useDeckLayerMetadata([1, 2, 3]),
);
const { result } = renderHook(() => useDeckLayerMetadata([1, 2, 3]));
await waitForNextUpdate();
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.isLoading).toBe(false);
expect(result.current.error).toBe(errorMessage);
expect(result.current.layers).toEqual([
{ sliceId: 1, name: 'Layer 1', type: 'unknown' },
@@ -93,13 +91,12 @@ test('handles API error and returns fallback layers', async () => {
test('handles non-Error object rejection', async () => {
mockSupersetClientGet.mockRejectedValue('String error');
const { result, waitForNextUpdate } = renderHook(() =>
useDeckLayerMetadata([1]),
);
const { result } = renderHook(() => useDeckLayerMetadata([1]));
await waitForNextUpdate();
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.isLoading).toBe(false);
expect(result.current.error).toBe('Unknown error');
expect(result.current.layers).toEqual([
{ sliceId: 1, name: 'Layer 1', type: 'unknown' },
@@ -125,22 +122,25 @@ test('refetches when sliceIds change', async () => {
.mockResolvedValueOnce(mockResponse1)
.mockResolvedValueOnce(mockResponse2);
const { result, rerender, waitForNextUpdate } = renderHook(
const { result, rerender } = renderHook(
({ ids }) => useDeckLayerMetadata(ids),
{
initialProps: { ids: [1] },
},
);
await waitForNextUpdate();
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.isLoading).toBe(false);
expect(result.current.layers).toHaveLength(1);
expect(result.current.layers[0].sliceId).toBe(1);
rerender({ ids: [2, 3] });
await waitForNextUpdate();
await waitFor(() => {
expect(result.current.layers).toHaveLength(2);
});
expect(result.current.isLoading).toBe(false);
expect(result.current.layers).toHaveLength(2);
@@ -157,13 +157,12 @@ test('handles empty result from API', async () => {
};
mockSupersetClientGet.mockResolvedValue(mockResponse);
const { result, waitForNextUpdate } = renderHook(() =>
useDeckLayerMetadata([1, 2]),
);
const { result } = renderHook(() => useDeckLayerMetadata([1, 2]));
await waitForNextUpdate();
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.isLoading).toBe(false);
expect(result.current.layers).toEqual([]);
expect(result.current.error).toBe(null);
});
@@ -176,16 +175,17 @@ test('clears isLoading when sliceIds transitions from non-empty to empty', async
};
mockSupersetClientGet.mockResolvedValue(mockResponse);
const { result, rerender, waitForNextUpdate } = renderHook(
const { result, rerender } = renderHook(
({ ids }) => useDeckLayerMetadata(ids),
{
initialProps: { ids: [1] },
},
);
await waitForNextUpdate();
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.isLoading).toBe(false);
expect(result.current.layers).toHaveLength(1);
act(() => {
@@ -204,16 +204,16 @@ test('does not refetch when sliceIds array has same values', async () => {
};
mockSupersetClientGet.mockResolvedValue(mockResponse);
const { result, rerender, waitForNextUpdate } = renderHook(
const { result, rerender } = renderHook(
({ ids }) => useDeckLayerMetadata(ids),
{
initialProps: { ids: [1] },
},
);
await waitForNextUpdate();
expect(result.current.isLoading).toBe(false);
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
const callCount = mockSupersetClientGet.mock.calls.length;

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { PureComponent } from 'react';
import { ErrorInfo, PureComponent } from 'react';
import { logging } from '@apache-superset/core/utils';
import { t } from '@apache-superset/core/translation';
import {
@@ -189,10 +189,11 @@ const MessageSpan = styled.span`
class Chart extends PureComponent<ChartProps, {}> {
static defaultProps = defaultProps;
renderStartTime: any;
renderStartTime: number;
constructor(props: ChartProps) {
super(props);
this.renderStartTime = Logger.getTimestamp();
this.handleRenderContainerFailure =
this.handleRenderContainerFailure.bind(this);
}
@@ -235,16 +236,13 @@ class Chart extends PureComponent<ChartProps, {}> {
);
}
handleRenderContainerFailure(
error: Error,
info: { componentStack: string } | null,
) {
handleRenderContainerFailure(error: Error, info: ErrorInfo) {
const { actions, chartId } = this.props;
logging.warn(error);
actions.chartRenderingFailed(
error.toString(),
chartId,
info ? info.componentStack : null,
info?.componentStack ?? null,
);
actions.logEvent(LOG_ACTIONS_RENDER_CHART, {

View File

@@ -18,7 +18,7 @@
*/
import { FeatureFlag, VizType } from '@superset-ui/core';
import { render, screen } from 'spec/helpers/testing-library';
import { renderHook } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import mockState from 'spec/fixtures/mockState';
import { sliceId } from 'spec/fixtures/mockChartQueries';
import { noOp } from 'src/utils/common';
@@ -95,7 +95,9 @@ beforeEach(() => {
test('Context menu renders', () => {
const result = setup();
expect(screen.queryByTestId(CONTEXT_MENU_TEST_ID)).not.toBeInTheDocument();
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.getByTestId(CONTEXT_MENU_TEST_ID)).toBeInTheDocument();
expect(screen.getByText('Add cross-filter')).toBeInTheDocument();
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
@@ -106,7 +108,9 @@ test('Context menu contains all displayed items only', () => {
const result = setup({
displayedItems: [ContextMenuItem.DrillToDetail, ContextMenuItem.DrillBy],
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Add cross-filter')).not.toBeInTheDocument();
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
expect(screen.getByText('Drill by')).toBeInTheDocument();
@@ -122,7 +126,9 @@ test('Context menu shows "Drill by" with `can_drill`, `can_write` & `can_get_dri
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.getByText('Drill by')).toBeInTheDocument();
});
@@ -137,7 +143,9 @@ test('Context menu shows "Drill by" with `can_drill`, `can_get_drill_info` & `ca
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.getByText('Drill by')).toBeInTheDocument();
});
@@ -147,7 +155,9 @@ test('Context menu does not show "Drill by" with neither of required perms', ()
Admin: [['invalid_permission', 'Dashboard']],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill by')).not.toBeInTheDocument();
});
@@ -157,7 +167,9 @@ test('Context menu does not show "Drill by" with just `can_dril` perm', () => {
Admin: [['can_drill', 'Dashboard']],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill by')).not.toBeInTheDocument();
});
@@ -170,7 +182,9 @@ test('Context menu does not show "Drill by" with just `can_dril` & `can_write` p
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill by')).not.toBeInTheDocument();
});
@@ -184,7 +198,9 @@ test('Context menu does not show "Drill by" with just `can_drill`, `can_explore`
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill by')).not.toBeInTheDocument();
});
@@ -198,7 +214,9 @@ test('Context menu shows "Drill to detail" with `can_samples`, `can_explore` & `
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
});
@@ -212,7 +230,9 @@ test('Context menu shows "Drill to detail" with `can_drill`, `can_samples` & `ca
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
});
@@ -227,7 +247,9 @@ test('Context menu shows "Drill to detail" with `can_drill`, `can_get_drill_info
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.getByText('Drill to detail')).toBeInTheDocument();
});
@@ -237,7 +259,9 @@ test('Context menu does not show "Drill to detail" with neither of required perm
Admin: [['invalid_permission', 'Dashboard']],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
});
@@ -247,7 +271,9 @@ test('Context menu does not show "Drill to detail" with just `can_drill` perm',
Admin: [['can_drill', 'Dashboard']],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
});
@@ -260,7 +286,9 @@ test('Context menu does not show "Drill to detail" with just `can_drill` & `can_
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
});
@@ -273,7 +301,9 @@ test('Context menu does not show "Drill to detail" with `can_samples` & `can_exp
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
});
@@ -287,7 +317,9 @@ test('Context menu does not show "Drill to detail" with `can_drill`, `can_explor
],
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument();
});
@@ -303,7 +335,9 @@ test('Dataset drill info API call is made when user has drill permissions', asyn
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
await new Promise(resolve => setTimeout(resolve, 0));
@@ -321,7 +355,9 @@ test('Dataset drill info API call is not made when user lacks drill permissions'
},
});
result.current.onContextMenu(0, 0, {});
act(() => {
result.current.onContextMenu(0, 0, {});
});
await new Promise(resolve => setTimeout(resolve, 0));

View File

@@ -57,7 +57,7 @@ export interface DrillBySubmenuProps {
drillByConfig?: ContextMenuFilters['drillBy'];
formData: BaseFormData & { [key: string]: any };
onSelection?: (...args: any) => void;
onClick?: (event: MouseEvent) => void;
onClick?: (event: React.MouseEvent) => void;
onCloseMenu?: () => void;
openNewModal?: boolean;
excludedColumns?: Column[];
@@ -93,8 +93,8 @@ export const DrillBySubmenu = ({
const showSearch = columns.length > SHOW_COLUMNS_SEARCH_THRESHOLD;
const handleSelection = useCallback(
(event, column) => {
onClick(event as MouseEvent);
(event: React.MouseEvent, column: Column) => {
onClick(event);
onSelection(column, drillByConfig);
if (openNewModal && onDrillBy && dataset) {
onDrillBy(column, dataset);

View File

@@ -17,7 +17,7 @@
* under the License.
*/
import { renderHook } from '@testing-library/react-hooks';
import { renderHook } from '@testing-library/react';
import {
render,
screen,

View File

@@ -74,7 +74,7 @@ export default function TableControls({
);
const removeFilter = useCallback(
colName => {
(colName: string) => {
const updatedFilterMap = { ...filterMap };
delete updatedFilterMap[colName];
setFilters(Object.values(updatedFilterMap));
@@ -125,7 +125,7 @@ export default function TableControls({
>
{colName}
</span>
<strong data-test="filter-val">{val}</strong>
<strong data-test="filter-val">{String(val)}</strong>
</Tag>
))}
</div>

View File

@@ -116,7 +116,7 @@ export const useDrillDetailMenuItems = ({
);
const openModal = useCallback(
(filters, event) => {
(filters: BinaryQueryObjectFilterClause[], event: MouseEvent) => {
onClick(event);
onSelection();
setFilters(filters);

View File

@@ -17,7 +17,6 @@
* under the License.
*/
import {
act,
render,
screen,
waitFor,
@@ -120,11 +119,9 @@ describe('DatasourceModal', () => {
onDatasourceSave as unknown as typeof mockedProps.onDatasourceSave,
});
const saveButton = screen.getByTestId('datasource-modal-save');
await act(async () => {
fireEvent.click(saveButton);
const okButton = await screen.findByRole('button', { name: 'OK' });
okButton.click();
});
fireEvent.click(saveButton);
const okButton = await screen.findByRole('button', { name: 'OK' });
fireEvent.click(okButton);
await waitFor(() => {
expect(onDatasourceSave).toHaveBeenCalled();
});
@@ -135,18 +132,14 @@ describe('DatasourceModal', () => {
.spyOn(SupersetClient, 'put')
.mockRejectedValue(new Error('Something went wrong'));
await act(async () => {
const saveButton = screen.getByTestId('datasource-modal-save');
fireEvent.click(saveButton);
const okButton = await screen.findByRole('button', { name: 'OK' });
okButton.click();
});
const saveButton = screen.getByTestId('datasource-modal-save');
fireEvent.click(saveButton);
const okButton = await screen.findByRole('button', { name: 'OK' });
fireEvent.click(okButton);
await act(async () => {
const errorElements = await screen.findAllByText('Error saving dataset');
const errorDiv = errorElements.find(el => el.closest('div'));
expect(errorDiv).toBeInTheDocument();
});
const errorElements = await screen.findAllByText('Error saving dataset');
const errorDiv = errorElements.find(el => el.closest('div'));
expect(errorDiv).toBeInTheDocument();
putSpy.mockRestore();
});

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