Compare commits

..

5 Commits

Author SHA1 Message Date
Claude Code
ca885b2341 docs(perf): add Dashboard Performance guide covering virtualization, lazy tabs, and chart-count guidance
Addresses a coverage gap kapa.ai surfaced: users repeatedly ask about
max chart count per dashboard, a switch to cap concurrent chart loads,
and how per-tab lazy loading works. Existing docs only mentioned
DASHBOARD_VIRTUALIZATION in passing via the feature flags page,
leaving users digging through GitHub issues and source for definitive
answers.

New page at /admin-docs/configuration/dashboard-performance covers:
- No hard chart-count cap; practical thresholds (~25/~50 friction
  points) with caveats about query complexity.
- DASHBOARD_VIRTUALIZATION (default True) — row-level viewport
  rendering, with the concrete behavior pulled from Row.tsx (1
  viewport-height render-in margin, 4 viewport-heights unmount,
  embedded mode keeps charts mounted, headless skips entirely).
- DASHBOARD_VIRTUALIZATION_DEFER_DATA (default False) — supplementary
  flag that also defers the data fetch.
- Per-tab lazy loading is on by default with no flag — content in
  inactive tabs doesn't render or fetch.
- No frontend concurrent-query limiter; concurrency is bounded by
  browser per-origin caps (~6) and backend worker count.
- Splitting strategies in order of effort: tabs → cache → async
  queries → multiple dashboards → warehouse-side pre-aggregation.
- Cross-links to Caching, Async Queries, and Feature Flags docs.
2026-05-19 00:57:08 -05:00
dependabot[bot]
ac5e8f1308 chore(deps): bump swagger-ui-react from 5.32.5 to 5.32.6 in /docs (#40056)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude <claude@anthropic.com>
2026-05-18 21:51:35 -07:00
Evan Rusackas
f98edc351e chore(deps): coordinated bump jest 30.3→30.4 + jest-environment-jsdom 29→30 (#40206)
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 11:35:19 +07:00
dependabot[bot]
4ceefb7e40 chore(deps): bump fs-extra from 11.3.2 to 11.3.5 in /superset-frontend (#39936)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hainenber <dotronghai96@gmail.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-05-18 20:38:57 -07:00
dependabot[bot]
1b9f06c840 chore(deps-dev): bump eslint-plugin-react-you-might-not-need-an-effect from 0.10.0 to 0.10.1 in /superset-frontend (#39902)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-05-18 16:28:05 -07:00
26 changed files with 2169 additions and 7198 deletions

View File

@@ -0,0 +1,168 @@
---
title: Dashboard Performance
hide_title: true
sidebar_position: 5
version: 1
---
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Dashboard Performance
A dashboard's perceived speed is determined by three independent things: how
many charts have to render, how many queries the backend can execute
concurrently, and how quickly the underlying data warehouse can return
results. Superset gives you levers for the first two; the third belongs to
your warehouse. This page covers the dashboard-side levers and the practical
guidance around them.
## Is there a maximum chart count per dashboard?
**No hard limit is enforced** — Superset has no configuration key that
caps the number of charts on a dashboard. In practice, dashboards behave
well up to a few dozen charts. Beyond that, you'll typically feel friction
on the initial load and during cross-filter / time-range updates, even with
the lazy-loading optimizations described below.
Rough thresholds to keep in mind:
- **Under ~25 charts**: usually no perceptible problem.
- **2550 charts**: still fine, but you start to want tabs to break the
page into chunks the user actually looks at.
- **Over ~50 charts**: split into multiple dashboards or use tabs
aggressively. The bottleneck is rarely Superset itself — it's the
warehouse executing dozens of queries in parallel and the browser
rendering dozens of chart frames.
These are guidelines, not guarantees. A dashboard of 100 sparkline-style
charts hitting a fast cache behaves very differently from a dashboard of
20 heavy aggregations against a cold warehouse.
## Lazy rendering — `DASHBOARD_VIRTUALIZATION`
Superset's dashboard layout is virtualized at the row level. Charts that
are far below the user's current scroll position are not rendered (and
therefore don't fetch data) until the user scrolls them into view, and they
are unmounted again if scrolled well past. This is on by default.
**Feature flag**: `DASHBOARD_VIRTUALIZATION` (default: `True`)
The flag is `stable` and marked for path-to-deprecation — meaning the
behavior will eventually be non-optional, but the flag still exists so
operators can disable it if a specific layout misbehaves.
**Behavior** (from `superset-frontend/src/dashboard/components/gridComponents/Row/Row.tsx`):
- A chart is rendered when its row scrolls within **1 viewport height** of
the visible area.
- A chart is unmounted when its row scrolls more than **4 viewport
heights** away from the visible area.
- Tabs that aren't currently selected don't render their content at all
(see below).
- The unmounting half is skipped in **embedded** mode (so an embedded
dashboard keeps its charts mounted once they've been seen, which avoids
re-fetching on scroll-up). Both halves are skipped for **headless /
bot** rendering (so screenshot / report jobs load every chart).
## Deferred data fetch — `DASHBOARD_VIRTUALIZATION_DEFER_DATA`
By default, `DASHBOARD_VIRTUALIZATION` controls *rendering* — but charts
that don't render also don't fetch data, because Superset's chart
components issue their data request on mount. `DASHBOARD_VIRTUALIZATION_DEFER_DATA`
is a supplementary flag that further defers the data request itself, useful
for backends where opening a connection or compiling a query is expensive
even if the result is later thrown away.
**Feature flag**: `DASHBOARD_VIRTUALIZATION_DEFER_DATA` (default: `False`)
Enable this if you see warehouse load spike on dashboard *open* even
though most charts are off-screen.
## Per-tab lazy loading
**This is on by default and has no flag.** A tab's content is not rendered
until the user activates that tab, so charts inside an unselected tab do
not fetch data on dashboard open. When the user clicks the tab, that
tab's charts mount and fetch in the normal way.
Practically: tabs are the single most effective tool for a large
dashboard. Splitting 60 charts across 4 tabs effectively turns dashboard
open into "load ~15 charts," and the remaining ones lazy-load only if the
user goes looking.
## Is there a switch to cap concurrent chart queries?
**No.** Superset does not implement a frontend-side concurrent-request
limiter. Each chart issues its own data request when it mounts, and the
browser handles parallelism (typically ~6 in-flight HTTP requests per
origin, then the rest queue). Backend throughput is bounded by your
Gunicorn worker count for synchronous query execution, or by your Celery
worker pool when [async queries](./async-queries-celery.mdx) are enabled.
If you need to throttle warehouse load, the right place is:
1. The warehouse itself (connection pool / concurrency limits).
2. Superset's Celery configuration (smaller worker pool when async
queries are on).
3. Splitting heavy charts across tabs or separate dashboards (each
dashboard load only fetches what's visible).
## Splitting strategies
When a dashboard outgrows comfortable performance, the options in order
of effort:
**1. Move sections into tabs.** Same dashboard, but only the active tab's
charts fetch. This is the cheapest change and often the only one needed.
**2. Cache aggressively.** A Redis cache backend (see
[Caching](./cache.mdx)) means repeat dashboard loads serve from cache
rather than re-hitting the warehouse. This is especially impactful for
dashboards opened by many users in close succession.
**3. Enable async queries.** [Async query execution](./async-queries-celery.mdx)
via Celery decouples query duration from request lifetime, so a slow
chart doesn't block the page. The user sees other charts come in as
their queries complete.
**4. Split into multiple dashboards.** Group related charts into purpose-
specific dashboards rather than one mega-dashboard. Link them from a
landing dashboard or a navigation menu.
**5. Pre-aggregate at the warehouse level.** If the same expensive
aggregation appears across many charts, materialize it as a view or
scheduled table in the warehouse so each chart query is a cheap lookup.
## Operational notes
- The feature flags above are set in `superset_config.py`, e.g.:
```python
FEATURE_FLAGS = {
"DASHBOARD_VIRTUALIZATION": True,
"DASHBOARD_VIRTUALIZATION_DEFER_DATA": True,
}
```
- See [Feature Flags](./feature-flags.mdx) for the full list of supported
flags and their lifecycle stages.
- Report and screenshot jobs (alerts, scheduled reports, dashboard
exports) intentionally bypass row virtualization so the rendered
artifact includes every chart, not just the ones above the fold.

View File

@@ -89,7 +89,7 @@
"remark-import-partial": "^0.0.2",
"reselect": "^5.2.0",
"storybook": "^8.6.18",
"swagger-ui-react": "^5.32.5",
"swagger-ui-react": "^5.32.6",
"swc-loader": "^0.2.7",
"tinycolor2": "^1.4.2",
"unist-util-visit": "^5.1.0"
@@ -127,7 +127,8 @@
"resolutions": {
"react-redux": "^9.2.0",
"@reduxjs/toolkit": "^2.5.0",
"baseline-browser-mapping": "^2.9.19"
"baseline-browser-mapping": "^2.9.19",
"swagger-client": "3.37.3"
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
}

View File

@@ -3898,59 +3898,6 @@
"@swagger-api/apidom-error" "^1.11.0"
"@swaggerexpert/json-pointer" "^2.10.1"
"@swagger-api/apidom-ns-api-design-systems@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.11.0.tgz#c9bee1def674b6b12559a97da243c9358e4f5d13"
integrity sha512-IskDsUkUtNas4guoChRKKkw0wOst64nRA24WuIjLf8ztfBdcl/oqx/cgy8pwWCUqNYvL9L3+sD5HeuokqMrySw==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-error" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-1" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
ts-mixer "^6.0.3"
"@swagger-api/apidom-ns-arazzo-1@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.11.0.tgz#ceda21f89fe970d8c6e2504e0ea644f864eabae1"
integrity sha512-n+aGSlLHyrpmCaBa9DBZkIqnNVzYAYSa010MvAwhlwtW3EbFYNwYWinbTwLqCd3leN6XWTvQYCvk0/k7/9Cq4A==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-json-schema-2020-12" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
ts-mixer "^6.0.3"
"@swagger-api/apidom-ns-asyncapi-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.11.0.tgz#9e7dcb40c6de940c6b4ad23c8413864aa3286127"
integrity sha512-SHh3naFZlXFI0gG36tNYvJ/VO8aZsjnXIQAqJHfOE6rrpl5msJrdDatmNczh+57WPZxEZA+KTXWCqNKdeu3G3Q==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-json-schema-draft-7" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
ts-mixer "^6.0.3"
"@swagger-api/apidom-ns-asyncapi-3@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.11.0.tgz#92a3f5b78f795f651114a5d7e572dadd051e62b0"
integrity sha512-4vrgNYDj68hgmgZj1eGBaBr5xqIETWn4jAioiRHek4jV1FLvmxCs3nC2nYs8CzQqqJ1bqirdiirrUpqhaQvTEA==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-asyncapi-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
ts-mixer "^6.0.3"
"@swagger-api/apidom-ns-json-schema-2019-09@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.11.0.tgz#18531aa5a09192d2f296474f458b493e64f40d5c"
@@ -4020,20 +3967,6 @@
ramda-adjunct "^5.0.0"
ts-mixer "^6.0.4"
"@swagger-api/apidom-ns-openapi-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.11.0.tgz#89b97db3173589cab4b9c57650a6d75638f870bb"
integrity sha512-cAIPJhLxm/nj1kzneNySeaTahY+hH5gkGNsgbmifGnLPsC5YOOfEVMKLj18IREdXqdnxJgRbsI9Azl4g09TPkg==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-error" "^1.11.0"
"@swagger-api/apidom-ns-json-schema-draft-4" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
ts-mixer "^6.0.3"
"@swagger-api/apidom-ns-openapi-3-0@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.11.0.tgz#4b44fb8a292439f4966bedef7d0c484926ece0e5"
@@ -4081,285 +4014,10 @@
ramda-adjunct "^5.0.0"
ts-mixer "^6.0.3"
"@swagger-api/apidom-parser-adapter-api-design-systems-json@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.11.0.tgz#b7c757fadafe6bbcfb12f897b9f7432795de9460"
integrity sha512-0OdwcnV/QF+Vs3Vj0dTmlRHEp9WQg9aBvWWl8Fq25OviyDhGGRpqgkEAOjtVYCH3XyZ1Xz+jhIDOdd5pxBajsA==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-api-design-systems" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-api-design-systems-yaml@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.11.0.tgz#7dd524f0241fd9997e8959a071a3e8b2cf58f1f6"
integrity sha512-K714DT6nFW+ZM9LTo+c120zkUjsEcIFO2DU+0cnzReRyenb1x6RZe+uOqTt7iWohnnWp2FV/j0exd/mCsxW65Q==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-api-design-systems" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-arazzo-json-1@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.11.0.tgz#8b8bafdab34b4a917a2cb68aeb0e9e94ad511fc2"
integrity sha512-z9K6XEr3AafV2EA+1pfW+8VoMCCSSpm2IU7oUTjSnhxRb5t/DZR4Qg8FEK8tRKdS2BO2kFFLb2xikrY3Qx8B+g==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-arazzo-1" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-arazzo-yaml-1@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.11.0.tgz#2a21d25375dae5bfd76ef331a581ad84fd00ccdd"
integrity sha512-HPb7Wzr+cj0IJkRRlqsK1tNCQXivuGRP4iB2yek16sQZXo2eqSUZ3j3Lz/WwWgnN/FWGAODm4bj9+EhGQ11TnA==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-arazzo-1" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-asyncapi-json-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.11.0.tgz#b9c6efff4ece06882a3aba91ed787b307618d6fe"
integrity sha512-sQenLXZRmTDQehe3JCSQpz6jpE3DhMQ0aoe2gpNqo23Gt/4oeW6nAP2h49q9Ne+CHPp0ApFUUyIXF7UTmbUWqA==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-asyncapi-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-asyncapi-json-3@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.11.0.tgz#af21567e11a9e2eaf84748d5a5a93436c19bc71a"
integrity sha512-aGnG3AYp4Qsimn1FOP0B9leYCJAQVockzHqyJj30xiNAXquBMXr6lq3L2/AEsmpDGv/x/++YJ4p2ggSxy12QNw==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-asyncapi-3" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.11.0.tgz#c8d2670275331f075714bdf6afb99a00aa53d3e4"
integrity sha512-iIRlB8B46UPiu0EkKhq1TvwloBgObASJ5ROx8rhT5+Pj+BBegE+KIY02EUKwcz5FgXJrH3XcltLiI7ZA68347Q==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-asyncapi-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-asyncapi-yaml-3@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.11.0.tgz#aaaf47cd034228d453d6426b3afcf2b429a3a497"
integrity sha512-BF2ZyQYMUNrjP1nMneX6ZD2IWBLycWpxg3yllXDCJtfdQT/IMzldIPKCNI9qoBE57lM6j2hpy+Jd86QJk20t2w==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-asyncapi-3" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-json@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.11.0.tgz#81e5919a2e4139492c0f910f8413557eb059de8a"
integrity sha512-DObW0LxYwif0erzGoXiEAZ6ecc/18LIEKxjEAc5Bw2M5I0C/iGW4y/UxAywihGvhMEo1gOvdO6w9Jh6UnuPVmA==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-ast" "^1.11.0"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-error" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
tree-sitter "=0.21.1"
tree-sitter-json "=0.24.8"
web-tree-sitter "=0.24.5"
"@swagger-api/apidom-parser-adapter-openapi-json-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.11.0.tgz#c48a0a51b4381ebf5b7c3406b90d9a25dfa48a49"
integrity sha512-dREUHAEHVry9aSGjqDpYF9Wzm1lgUkV6EgoYDflyQ9HxgCwhucDPFmUgI7UaR0G6bplnJumMcZXh1I1TGn1v7Q==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-openapi-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-openapi-json-3-0@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.11.0.tgz#e432f87615bdd5652aa7c299454ede2cbec46f4a"
integrity sha512-U/NZpvuj9IpUS48zF2tYbgW2AtTw6Yi6kXNiHUtgUEomxYdb6XQeKLDGvgeWjgAgfUROohakcH+wx713VCGxfQ==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-0" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-openapi-json-3-1@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.11.0.tgz#1c5043a00620235c0b175d03a1da4a17d41294fd"
integrity sha512-fYarNeaz39oKZ6VwqwON+IeJszidZGPvUYDfggLaar81NGimrz07y1U+DhAf96IX3qgUa2J6Fu3Bv1r57hs6Ng==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-1" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-openapi-json-3-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-json-3-2/-/apidom-parser-adapter-openapi-json-3-2-1.11.0.tgz#29bcb3388b9a452a78a01d2491e5891009755c2d"
integrity sha512-jtMoAH3R73bQUc4D2cJTUUvO4iJz9CV1W4+zoU/gT2l6h8Ji5EhZH0/VyynUk4J6mW/GdwxUN/q5z2P/DtSmfA==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-openapi-yaml-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.11.0.tgz#08a94606d3b636075aacb9af80b5fd25b4b15782"
integrity sha512-e8L4kHahgkOIzCCSGs5jTahXLInERNr37teSLS4SuqYgSVWr9AVXuNvpHNYGeMECD8briGIGfAAtnZChCGYrEA==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-openapi-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.11.0.tgz#55c35ac58764b20890456707c9176ef48f20d741"
integrity sha512-s+AXnNzLeAk28jUAeXwTSR1AlX+TXIAt2GfFgWUAV+SFw2OhRpoKYLzItN3n2UsHselqHvfyUL9xNCJBZleQtQ==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-0" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.11.0.tgz#a6dc3316738f3175d896a832701c349ec416c11a"
integrity sha512-xyUyehHhB+BSOAT7mYGqmcEozuLKxmx1Hug97O9SVgNU8QTClc95+VWrAHhJbn8juPR6y2vSwm/wrQDwb4yq7w==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-1" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-openapi-yaml-3-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-openapi-yaml-3-2/-/apidom-parser-adapter-openapi-yaml-3-2-1.11.0.tgz#bc83e0d8ec0a54c67c8a77109be56c5eef5be169"
integrity sha512-u7Y98zdjEs+0Upa8TdxOsb7z8hYJmLz9lVleRiB7rqysVga6oSDI5NAFdLVqMB6uAUuFi/tyiuiFT4Qosfd6Vw==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.11.0.tgz#6bca1a605127d904bde0419bfd2df880c6051757"
integrity sha512-FZK9KfwiTnNc+imxg7Wu2ktKhXCYPeFQZ1uZJzJL/hk1n+zyPfRY/4Aue4HzDcG8+wbItd3dRjKClFanVZAXoA==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-ast" "^1.11.0"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-error" "^1.11.0"
"@tree-sitter-grammars/tree-sitter-yaml" "=0.7.1"
"@types/ramda" "~0.30.0"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
tree-sitter "=0.22.4"
web-tree-sitter "=0.24.5"
"@swagger-api/apidom-reference@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@swagger-api/apidom-reference/-/apidom-reference-1.11.0.tgz#9deaff0a93e46058c090946394dbb83975a86a51"
integrity sha512-ftqegYrxxl9UwQFbdVOtXIqNolVd25M5u53X8fP96Wx6lEVr5Ed7B6+dzch8ttCUmKeoLIeagvt76b6BoYtnLw==
dependencies:
"@babel/runtime-corejs3" "^7.26.10"
"@swagger-api/apidom-core" "^1.11.0"
"@swagger-api/apidom-error" "^1.11.0"
"@types/ramda" "~0.30.0"
axios "^1.15.0"
minimatch "^10.2.1"
ramda "~0.30.0"
ramda-adjunct "^5.0.0"
optionalDependencies:
"@swagger-api/apidom-json-pointer" "^1.11.0"
"@swagger-api/apidom-ns-arazzo-1" "^1.11.0"
"@swagger-api/apidom-ns-asyncapi-2" "^1.11.0"
"@swagger-api/apidom-ns-openapi-2" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-0" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-1" "^1.11.0"
"@swagger-api/apidom-ns-openapi-3-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-api-design-systems-json" "^1.11.0"
"@swagger-api/apidom-parser-adapter-api-design-systems-yaml" "^1.11.0"
"@swagger-api/apidom-parser-adapter-arazzo-json-1" "^1.11.0"
"@swagger-api/apidom-parser-adapter-arazzo-yaml-1" "^1.11.0"
"@swagger-api/apidom-parser-adapter-asyncapi-json-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-asyncapi-json-3" "^1.11.0"
"@swagger-api/apidom-parser-adapter-asyncapi-yaml-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-asyncapi-yaml-3" "^1.11.0"
"@swagger-api/apidom-parser-adapter-json" "^1.11.0"
"@swagger-api/apidom-parser-adapter-openapi-json-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-openapi-json-3-0" "^1.11.0"
"@swagger-api/apidom-parser-adapter-openapi-json-3-1" "^1.11.0"
"@swagger-api/apidom-parser-adapter-openapi-json-3-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-openapi-yaml-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-openapi-yaml-3-0" "^1.11.0"
"@swagger-api/apidom-parser-adapter-openapi-yaml-3-1" "^1.11.0"
"@swagger-api/apidom-parser-adapter-openapi-yaml-3-2" "^1.11.0"
"@swagger-api/apidom-parser-adapter-yaml-1-2" "^1.11.0"
"@swaggerexpert/cookie@^2.0.2":
version "2.0.2"
@@ -4543,14 +4201,6 @@
dependencies:
defer-to-connect "^2.0.1"
"@tree-sitter-grammars/tree-sitter-yaml@=0.7.1":
version "0.7.1"
resolved "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-yaml/-/tree-sitter-yaml-0.7.1.tgz"
integrity sha512-AynBwkIoQCTgjDR33bDUp9Mqq+YTco0is3n5hRApMqG9of/6A4eQsfC1/uSEeHSUyMQSYawcAWamsexnVpIP4Q==
dependencies:
node-addon-api "^8.3.1"
node-gyp-build "^4.8.4"
"@tybys/wasm-util@^0.10.1":
version "0.10.1"
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414"
@@ -5479,13 +5129,6 @@ address@^1.0.1:
resolved "https://registry.npmjs.org/address/-/address-1.2.2.tgz"
integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz"
@@ -5847,11 +5490,6 @@ async@3.2.6:
resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz"
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
autolinker@^3.11.0:
version "3.16.2"
resolved "https://registry.npmjs.org/autolinker/-/autolinker-3.16.2.tgz"
@@ -5878,16 +5516,6 @@ available-typed-arrays@^1.0.7:
dependencies:
possible-typed-array-names "^1.0.0"
axios@^1.15.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.16.1.tgz#517e29291d19d6e8cf919ff264f4fe157261ba12"
integrity sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==
dependencies:
follow-redirects "^1.16.0"
form-data "^4.0.5"
https-proxy-agent "^5.0.1"
proxy-from-env "^2.1.0"
babel-loader@^9.2.1:
version "9.2.1"
resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz"
@@ -6424,13 +6052,6 @@ combine-promises@^1.1.0:
resolved "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz"
integrity sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
comma-separated-tokens@^2.0.0:
version "2.0.3"
resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz"
@@ -7355,11 +6976,6 @@ delaunator@5:
dependencies:
robust-predicates "^3.0.2"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@2.0.0, depd@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
@@ -8404,7 +8020,7 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726"
integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==
follow-redirects@^1.0.0, follow-redirects@^1.16.0:
follow-redirects@^1.0.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc"
integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==
@@ -8426,17 +8042,6 @@ form-data-encoder@^2.1.2:
resolved "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz"
integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==
form-data@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053"
integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
hasown "^2.0.2"
mime-types "^2.1.12"
format@^0.2.0:
version "0.2.2"
resolved "https://registry.npmjs.org/format/-/format-0.2.2.tgz"
@@ -9098,14 +8703,6 @@ http2-wrapper@^2.1.10:
quick-lru "^5.1.1"
resolve-alpn "^1.2.0"
https-proxy-agent@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
dependencies:
agent-base "6"
debug "4"
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
@@ -11162,7 +10759,7 @@ mime-types@2.1.18:
dependencies:
mime-db "~1.33.0"
mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -11228,7 +10825,7 @@ minimatch@3.1.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
minimatch@^10.2.1, minimatch@^10.2.2:
minimatch@^10.2.2:
version "10.2.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1"
integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==
@@ -11335,14 +10932,9 @@ node-addon-api@^7.0.0:
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-addon-api@^8.0.0, node-addon-api@^8.2.2, node-addon-api@^8.3.0, node-addon-api@^8.3.1:
version "8.5.0"
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz"
integrity sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==
node-domexception@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-emoji@^2.1.0:
@@ -11357,7 +10949,7 @@ node-emoji@^2.1.0:
node-fetch-commonjs@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz"
resolved "https://registry.yarnpkg.com/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz#0dd0fd4c4a314c5234f496ff7b5d9ce5a6c8feaa"
integrity sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==
dependencies:
node-domexception "^1.0.0"
@@ -11377,11 +10969,6 @@ node-fetch@^2.6.1:
dependencies:
whatwg-url "^5.0.0"
node-gyp-build@^4.8.0, node-gyp-build@^4.8.2, node-gyp-build@^4.8.4:
version "4.8.4"
resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz"
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
node-readfiles@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz"
@@ -12670,11 +12257,6 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
proxy-from-env@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba"
integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz"
@@ -14450,7 +14032,7 @@ svgo@^3.0.2, svgo@^3.2.0:
picocolors "^1.0.0"
sax "^1.5.0"
swagger-client@^3.37.3:
swagger-client@3.37.3, swagger-client@^3.37.4:
version "3.37.3"
resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.37.3.tgz#dbe3f0d22c367d4bc04cc7ddaf5224e116d46074"
integrity sha512-PZv5smQPnPwfP6mnkq96fOp/RNDKBqd8vfwE4UuwA229wsesj20yd7RadXx+9uLBC3c0H6cu/H+bnbMTWG6oUQ==
@@ -14475,10 +14057,10 @@ swagger-client@^3.37.3:
ramda "^0.30.1"
ramda-adjunct "^5.1.0"
swagger-ui-react@^5.32.5:
version "5.32.5"
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.32.5.tgz#c4650972c71aaf4107a5f742e9c45615eba6857f"
integrity sha512-u86Qx36C5FvmJFVGMF3s62dxR3l0EfUmlylJVqCJ4vL0tvfd38kNdCQan9app6Y+C25uqVAjGLYu2w87UMD35Q==
swagger-ui-react@^5.32.6:
version "5.32.6"
resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-5.32.6.tgz#00c3f99a5b1f6c0debb2ee0589018dfc79ba1d4a"
integrity sha512-2q2kXd6eDR+syyWV5HE2CkWANyr2MHPkNezG4M7fC0FPlBUZEsNgyA/2dcb9dIwgE5xd995dO42h89fNMF5/ng==
dependencies:
"@babel/runtime-corejs3" "^7.27.1"
"@scarf/scarf" "=1.4.0"
@@ -14509,7 +14091,7 @@ swagger-ui-react@^5.32.5:
reselect "^5.1.1"
serialize-error "^8.1.0"
sha.js "^2.4.12"
swagger-client "^3.37.3"
swagger-client "^3.37.4"
url-parse "^1.5.10"
xml "=1.0.1"
xml-but-prettier "^1.0.1"
@@ -14679,30 +14261,6 @@ tree-dump@^1.0.3:
resolved "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz"
integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==
tree-sitter-json@=0.24.8:
version "0.24.8"
resolved "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.24.8.tgz"
integrity sha512-Tc9ZZYwHyWZ3Tt1VEw7Pa2scu1YO7/d2BCBbKTx5hXwig3UfdQjsOPkPyLpDJOn/m1UBEWYAtSdGAwCSyagBqQ==
dependencies:
node-addon-api "^8.2.2"
node-gyp-build "^4.8.2"
tree-sitter@=0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/tree-sitter/-/tree-sitter-0.21.1.tgz#fbb34c09056700814af0e1e37688e06463ba04c4"
integrity sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==
dependencies:
node-addon-api "^8.0.0"
node-gyp-build "^4.8.0"
tree-sitter@=0.22.4:
version "0.22.4"
resolved "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.4.tgz"
integrity sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==
dependencies:
node-addon-api "^8.3.0"
node-gyp-build "^4.8.4"
trim-lines@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz"
@@ -15322,14 +14880,9 @@ web-namespaces@^2.0.0:
web-streams-polyfill@^3.0.3:
version "3.3.3"
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
web-tree-sitter@=0.24.5:
version "0.24.5"
resolved "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.5.tgz"
integrity sha512-+J/2VSHN8J47gQUAvF8KDadrfz6uFYVjxoxbKWDoXVsH2u7yLdarCnIURnrMA6uSRkgX3SdmqM5BOoQjPdSh5w==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"

File diff suppressed because it is too large Load Diff

View File

@@ -174,7 +174,7 @@
"dom-to-pdf": "^0.3.2",
"echarts": "^5.6.0",
"fast-glob": "^3.3.2",
"fs-extra": "^11.3.4",
"fs-extra": "^11.3.5",
"fuse.js": "^7.3.0",
"geolib": "^3.3.14",
"geostyler": "^18.5.1",
@@ -330,7 +330,7 @@
"eslint-plugin-no-only-tests": "^3.4.0",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-react-prefer-function-component": "^5.0.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^0.10.0",
"eslint-plugin-react-you-might-not-need-an-effect": "^0.10.1",
"eslint-plugin-storybook": "^0.8.0",
"eslint-plugin-testing-library": "^7.16.2",
"eslint-plugin-theme-colors": "file:eslint-rules/eslint-plugin-theme-colors",
@@ -340,8 +340,8 @@
"html-webpack-plugin": "^5.6.7",
"http-server": "^14.1.1",
"imports-loader": "^5.0.0",
"jest": "^30.3.0",
"jest-environment-jsdom": "^29.7.0",
"jest": "^30.4.2",
"jest-environment-jsdom": "^30.4.1",
"jest-html-reporter": "^4.4.0",
"jest-websocket-mock": "^2.5.0",
"js-yaml-loader": "^1.2.2",
@@ -412,7 +412,14 @@
"@luma.gl/gltf": "~9.2.5",
"@luma.gl/shadertools": "~9.2.5",
"@luma.gl/webgl": "~9.2.5",
"fast-xml-parser": "^5.8.0"
"fast-xml-parser": "^5.8.0",
"jest-mock": "^30.4.0",
"jest-runtime": "^30.4.0",
"@jest/globals": "^30.4.0",
"@jest/types": "^30.4.0",
"jest-util": "^30.4.0",
"jest-circus": "^30.4.0",
"jest-environment-node": "^30.4.0"
},
"readme": "ERROR: No README data found!",
"scarfSettings": {

View File

@@ -154,7 +154,7 @@ test('accepts custom style props', () => {
render(<DropdownContainer items={generateItems(2)} style={customStyle} />);
const container = screen.getByTestId('container');
expect(container).toHaveStyle('background-color: red');
expect(container).toHaveStyle('background-color: rgb(255, 0, 0)');
expect(container).toHaveStyle('padding: 10px');
});

View File

@@ -553,22 +553,26 @@ describe('SupersetClientClass', () => {
});
describe('when unauthorized', () => {
let originalLocation: any;
let authSpy: jest.SpyInstance;
let locationSpy: jest.SpyInstance;
let mockHrefValue: string;
const mockRequestUrl = 'https://host/get/url';
const mockRequestPath = '/get/url';
const mockRequestSearch = '?param=1&param=2';
const mockHref = mockRequestUrl + mockRequestSearch;
beforeEach(() => {
originalLocation = window.location;
// @ts-expect-error
delete window.location;
window.location = {
mockHrefValue = mockHref;
locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
pathname: mockRequestPath,
search: mockRequestSearch,
href: mockHref,
} as unknown as Location;
get href() {
return mockHrefValue;
},
set href(v: string) {
mockHrefValue = v;
},
} as unknown as Location);
authSpy = jest
.spyOn(SupersetClientClass.prototype, 'ensureAuth')
.mockImplementation();
@@ -578,7 +582,7 @@ describe('SupersetClientClass', () => {
afterEach(() => {
authSpy.mockReset();
window.location = originalLocation;
locationSpy.mockRestore();
});
test('should redirect', async () => {
@@ -599,11 +603,17 @@ describe('SupersetClientClass', () => {
test('should not redirect again if already on login page', async () => {
const client = new SupersetClientClass({});
window.location = {
href: '/login?next=something',
mockHrefValue = '/login?next=something';
locationSpy.mockReturnValue({
get href() {
return mockHrefValue;
},
set href(v: string) {
mockHrefValue = v;
},
pathname: '/login',
search: '?next=something',
} as unknown as Location;
} as unknown as Location);
let error;
try {

View File

@@ -360,18 +360,26 @@ describe('callApi()', () => {
});
describe('caching', () => {
const origLocation = window.location;
let locationSpy: jest.SpyInstance;
let mockProtocol = 'https:';
beforeAll(() => {
Object.defineProperty(window, 'location', { value: {} });
locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
get protocol() {
return mockProtocol;
},
set protocol(v: string) {
mockProtocol = v;
},
} as Location);
});
afterAll(() => {
Object.defineProperty(window, 'location', { value: origLocation });
locationSpy.mockRestore();
});
beforeEach(async () => {
window.location.protocol = 'https:';
mockProtocol = 'https:';
await caches.delete(constants.CACHE_KEY);
});

View File

@@ -65,7 +65,7 @@ describe('updateTextNode(node, options)', () => {
test('handles setting font', () => {
const node = updateTextNode(createTextNode(), {
style: {
font: 'italic 30px Lobster 700',
font: 'italic 700 30px Lobster',
},
});
expect(node.getAttribute('class')).toEqual('');

View File

@@ -630,9 +630,11 @@ describe('plugin-chart-table', () => {
);
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
});
test('render cell without color', () => {
@@ -669,12 +671,14 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe(
'rgba(172, 225, 196, 0.812)',
'rgba(172, 225, 196, 0.81)',
);
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
'',
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('N/A')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('N/A')).background).toBe('');
});
test('preserves muted null styling when no formatter resolves text color', () => {
@@ -1059,10 +1063,10 @@ describe('plugin-chart-table', () => {
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'',
'rgba(0, 0, 0, 0)',
);
});
@@ -1090,9 +1094,11 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByText('Maria')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe('');
});
test('render color with string column color formatter (operator containing)', () => {
@@ -1119,9 +1125,11 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe('');
});
test('render color with string column color formatter (operator not containing)', () => {
@@ -1148,10 +1156,10 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'',
'rgba(0, 0, 0, 0)',
);
});
@@ -1179,10 +1187,10 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'',
'rgba(0, 0, 0, 0)',
);
});
@@ -1209,13 +1217,13 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByText('Joe')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('Maria')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
});
@@ -1243,9 +1251,11 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByText('true')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('false')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('false')).background).toBe('');
});
test('render color with boolean column color formatter (operator is false)', () => {
@@ -1272,9 +1282,11 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByText('false')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('true')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('true')).background).toBe('');
});
test('render color with boolean column color formatter (operator is null)', () => {
@@ -1301,10 +1313,14 @@ describe('plugin-chart-table', () => {
}),
);
expect(getComputedStyle(screen.getByText('N/A')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('true')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('false')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('true')).background).toBe('');
expect(getComputedStyle(screen.getByText('false')).background).toBe('');
});
test('render color with boolean column color formatter (operator is not null)', () => {
@@ -1333,12 +1349,14 @@ describe('plugin-chart-table', () => {
const trueElements = screen.getAllByText('true');
const falseElements = screen.getAllByText('false');
expect(getComputedStyle(trueElements[0]).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(falseElements[0]).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByText('N/A')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByText('N/A')).background).toBe('');
});
test('render color with column color formatter to entire row', () => {
@@ -1367,13 +1385,13 @@ describe('plugin-chart-table', () => {
);
expect(getComputedStyle(screen.getByText('Michael')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByTitle('0.123456')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
});
@@ -1644,7 +1662,9 @@ describe('plugin-chart-table', () => {
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe(
'rgba(0, 0, 0, 0)',
);
});
test('render color with useGradient true returns gradient color', () => {
@@ -1674,9 +1694,11 @@ describe('plugin-chart-table', () => {
// When useGradient is true, should return gradient color with opacity
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
});
test('render color with useGradient undefined defaults to gradient (backward compatibility)', () => {
@@ -1705,9 +1727,11 @@ describe('plugin-chart-table', () => {
// When useGradient is undefined, should default to gradient for backward compatibility
expect(getComputedStyle(screen.getByTitle('2467063')).background).toBe(
'rgba(172, 225, 196, 1)',
'rgb(172, 225, 196)',
);
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe(
'rgba(0, 0, 0, 0)',
);
expect(getComputedStyle(screen.getByTitle('2467')).background).toBe('');
});
test('render color with useGradient false and None operator returns solid color', () => {

View File

@@ -19,6 +19,38 @@
import JSDOMEnvironment from 'jest-environment-jsdom';
// jest-environment-jsdom 30 bundles jsdom 26, which marks window.location as
// [LegacyUnforgeable] (configurable: false). jest 30's spyOn now strictly
// checks the configurable flag and throws when it's false, breaking every test
// that uses jest.spyOn(window, 'location', 'get').
//
// We intercept Object.defineProperties at module-load time (before any JSDOM
// instance is created). The interceptor makes window.location configurable
// every time jsdom creates a new Window, restoring the ability to spy on it.
// This file is only required by Jest in the test environment so the
// monkey-patch is safe.
const _originalDefineProperties = Object.defineProperties.bind(Object);
(Object as any).defineProperties = function (
obj: object,
props: PropertyDescriptorMap,
) {
if (
props !== null &&
typeof props === 'object' &&
Object.prototype.hasOwnProperty.call(props, 'location') &&
(props as any).location?.configurable === false
) {
// Allow jest.spyOn(window, 'location', 'get') to work in tests by making
// the property configurable. This deviates from the browser spec's
// [LegacyUnforgeable] requirement but is acceptable in a test environment.
props = {
...props,
location: { ...(props as any).location, configurable: true },
};
}
return _originalDefineProperties(obj, props);
};
// https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {

View File

@@ -582,16 +582,25 @@ describe('async actions', () => {
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('runQuery with query params', () => {
const { location } = window;
let locationSpy: jest.SpyInstance;
beforeAll(() => {
delete (window as any).location;
(window as any).location = new URL('http://localhost/sqllab/?foo=bar');
const u = new URL('http://localhost/sqllab/?foo=bar');
locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
href: u.href,
pathname: u.pathname,
search: u.search,
hash: u.hash,
origin: u.origin,
host: u.host,
hostname: u.hostname,
port: u.port,
protocol: u.protocol,
} as Location);
});
afterAll(() => {
delete (window as any).location;
window.location = location;
locationSpy.mockRestore();
});
const makeRequest = () => {

View File

@@ -244,11 +244,9 @@ test('reads database from localStorage when URL has db param', () => {
jest.spyOn(localStorageHelpers, 'getItem').mockReturnValue(localStorageDb);
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: { search: '?db=true' },
writable: true,
});
const locationSpy = jest
.spyOn(window, 'location', 'get')
.mockReturnValue({ ...window.location, search: '?db=true' } as Location);
const store = mockStore(createInitialState());
const { result, rerender } = renderHook(
@@ -269,10 +267,7 @@ test('reads database from localStorage when URL has db param', () => {
null,
);
Object.defineProperty(window, 'location', {
value: originalLocation,
writable: true,
});
locationSpy.mockRestore();
});
test('returns null db when dbId does not exist in databases', () => {

View File

@@ -45,15 +45,23 @@ const createProps = () => ({
submenuKey: 'share',
});
const originalLocation = window.location;
const postDashboardPermalinkMockUrl = `http://localhost/api/v1/dashboard/${DASHBOARD_ID}/permalink`;
let hrefValue = '';
let locationSpy: jest.SpyInstance;
beforeEach(() => {
jest.clearAllMocks();
// @ts-expect-error
delete window.location;
window.location = { href: '' } as any;
hrefValue = '';
locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
get href() {
return hrefValue;
},
set href(v: string) {
hrefValue = v;
},
} as Location);
fetchMock.clearHistory().removeRoutes();
fetchMock.post(
postDashboardPermalinkMockUrl,
@@ -63,7 +71,7 @@ beforeEach(() => {
});
afterEach(() => {
window.location = originalLocation;
locationSpy.mockRestore();
window.featureFlags = {};
fetchMock.clearHistory().removeRoutes();
});

View File

@@ -18,19 +18,18 @@
*/
import extractUrlParams from './extractUrlParams';
const originalWindowLocation = window.location;
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('extractUrlParams', () => {
let locationSpy: jest.SpyInstance;
beforeAll(() => {
// @ts-expect-error
delete window.location;
// @ts-expect-error
window.location = { search: '?edit=true&abc=123' };
locationSpy = jest
.spyOn(window, 'location', 'get')
.mockReturnValue({ search: '?edit=true&abc=123' } as Location);
});
afterAll(() => {
window.location = originalWindowLocation;
locationSpy.mockRestore();
});
test('returns all urlParams', () => {

View File

@@ -29,9 +29,8 @@ describe('getChartIdsFromLayout', () => {
},
};
const globalLocation = window.location;
afterEach(() => {
window.location = globalLocation;
jest.restoreAllMocks();
});
test('should encode filters', () => {
@@ -93,16 +92,11 @@ describe('getChartIdsFromLayout', () => {
});
test('should preserve unknown filters', () => {
const windowSpy = jest.spyOn(window, 'window', 'get');
windowSpy.mockImplementation(
() =>
({
location: {
origin: 'https://localhost',
search: '?unknown_param=value',
},
}) as unknown as Window & typeof globalThis,
);
jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
origin: 'https://localhost',
search: '?unknown_param=value',
} as Location);
const urlWithStandalone = getDashboardUrl({
pathname: 'path',
filters: {},
@@ -112,7 +106,6 @@ describe('getChartIdsFromLayout', () => {
expect(urlWithStandalone).toBe(
`path?unknown_param=value&standalone=${DashboardStandaloneMode.HideNav}`,
);
windowSpy.mockRestore();
});
test('should pass through a router-relative pathname unchanged', () => {
@@ -122,21 +115,18 @@ describe('getChartIdsFromLayout', () => {
hash: '',
standalone: DashboardStandaloneMode.HideNav,
});
expect(url).toBe(`/dashboard/1/?standalone=${DashboardStandaloneMode.HideNav}`);
expect(url).toBe(
`/dashboard/1/?standalone=${DashboardStandaloneMode.HideNav}`,
);
});
test('should process native filters key', () => {
const windowSpy = jest.spyOn(window, 'window', 'get');
windowSpy.mockImplementation(
() =>
({
location: {
origin: 'https://localhost',
search:
'?preselect_filters=%7B%7D&native_filters_key=024380498jdkjf-2094838',
},
}) as unknown as Window & typeof globalThis,
);
jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
origin: 'https://localhost',
search:
'?preselect_filters=%7B%7D&native_filters_key=024380498jdkjf-2094838',
} as Location);
const urlWithNativeFilters = getDashboardUrl({
pathname: 'path',
@@ -146,6 +136,5 @@ describe('getChartIdsFromLayout', () => {
expect(urlWithNativeFilters).toBe(
'path?preselect_filters=%7B%7D&native_filters_key=024380498jdkjf-2094838',
);
windowSpy.mockRestore();
});
});

View File

@@ -120,6 +120,11 @@ fetchMock.get('glob:*/api/v1/chart/*', {
});
const defaultPath = '/explore/';
afterEach(() => {
jest.restoreAllMocks();
});
const renderWithRouter = ({
search = '',
overridePathname,
@@ -134,11 +139,10 @@ const renderWithRouter = ({
history?: ReturnType<typeof createMemoryHistory>;
} = {}) => {
const path = overridePathname ?? defaultPath;
Object.defineProperty(window, 'location', {
get() {
return { pathname: path, search };
},
});
jest.spyOn(window, 'location', 'get').mockReturnValue({
pathname: path,
search,
} as Location);
const history =
existingHistory ??
createMemoryHistory({ initialEntries: [`${path}${search}`] });

View File

@@ -46,14 +46,8 @@ jest.mock('src/components/Datasource/components/DatasourceEditor', () => ({
),
}));
let originalLocation: Location;
beforeEach(() => {
originalLocation = window.location;
});
afterEach(() => {
window.location = originalLocation;
jest.restoreAllMocks();
try {
const unmatched = fetchMock.callHistory.calls('unmatched');
@@ -539,10 +533,10 @@ test('should show missing params state', () => {
});
test('should show missing dataset state', () => {
// @ts-expect-error - overriding window.location for test
delete window.location;
// @ts-expect-error - overriding window.location for test
window.location = { search: '?slice_id=152' };
jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?slice_id=152',
} as Location);
const props = createProps({ datasource: fallbackExploreInitialData.dataset });
render(<DatasourceControl {...props} />, { useRedux: true, useRouter: true });
expect(screen.getAllByText(/missing dataset/i)).toHaveLength(2);
@@ -554,10 +548,10 @@ test('should show missing dataset state', () => {
});
test('should show forbidden dataset state', () => {
// @ts-expect-error - overriding window.location for test
delete window.location;
// @ts-expect-error - overriding window.location for test
window.location = { search: '?slice_id=152' };
jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?slice_id=152',
} as Location);
const error = {
error_type: 'TABLE_SECURITY_ACCESS_ERROR',
statusText: 'FORBIDDEN',

View File

@@ -21,10 +21,24 @@ import { VizType } from '@superset-ui/core';
import { getParsedExploreURLParams } from './getParsedExploreURLParams';
const EXPLORE_BASE_URL = 'http://localhost:9000/explore/';
afterEach(() => {
jest.restoreAllMocks();
});
const setupLocation = (newUrl: string) => {
delete (window as any).location;
// @ts-expect-error
window.location = new URL(newUrl);
const u = new URL(newUrl);
jest.spyOn(window, 'location', 'get').mockReturnValue({
href: u.href,
pathname: u.pathname,
search: u.search,
hash: u.hash,
origin: u.origin,
host: u.host,
hostname: u.hostname,
port: u.port,
protocol: u.protocol,
} as Location);
};
test('get form_data_key and slice_id from search params - url when moving from dashboard to explore', () => {

View File

@@ -229,10 +229,10 @@ test('switches to Mine tab correctly', async () => {
test('handles create dashboard button click', async () => {
const assignMock = jest.fn();
Object.defineProperty(window, 'location', {
value: { assign: assignMock },
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
assign: assignMock,
} as Location);
render(
<Router history={history}>
@@ -244,6 +244,7 @@ test('handles create dashboard button click', async () => {
const createButton = screen.getByRole('button', { name: /dashboard$/i });
await userEvent.click(createButton);
expect(assignMock).toHaveBeenCalledWith('/dashboard/new');
locationSpy.mockRestore();
});
test('switches to Other tab when available', async () => {

View File

@@ -99,11 +99,10 @@ describe('logger middleware', () => {
test('should include ts, start_offset, event_name, impression_id, source, and source_id in every event', () => {
// Set window.location to include /dashboard/ so the middleware adds dashboard context
const originalHref = window.location.href;
Object.defineProperty(window, 'location', {
value: { href: `http://localhost/dashboard/${dashboardId}/` },
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
href: `http://localhost/dashboard/${dashboardId}/`,
} as Location);
try {
const fetchLog = (logger as Function)(mockStore)(next);
@@ -134,11 +133,7 @@ describe('logger middleware', () => {
expect(typeof events[0].ts).toBe('number');
expect(typeof events[0].start_offset).toBe('number');
} finally {
// Restore original location
Object.defineProperty(window, 'location', {
value: { href: originalHref },
writable: true,
});
locationSpy.mockRestore();
}
});

View File

@@ -251,23 +251,16 @@ test('handles special characters in dataset name from URL parameter', async () =
status: 200,
});
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: {
...originalLocation,
search: '?dataset=flights%C3%86%20test',
},
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?dataset=flights%C3%86%20test',
} as Location);
await renderComponent();
expect(await screen.findByText('flightsÆ test')).toBeInTheDocument();
Object.defineProperty(window, 'location', {
value: originalLocation,
writable: true,
});
locationSpy.mockRestore();
});
test('pre-selects the dataset from URL parameter and shows it in dropdown', async () => {
@@ -288,20 +281,16 @@ test('pre-selects the dataset from URL parameter and shows it in dropdown', asyn
status: 200,
});
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: { ...originalLocation, search: '?dataset=flights' },
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?dataset=flights',
} as Location);
await renderComponent();
expect(await screen.findByText('flights')).toBeInTheDocument();
Object.defineProperty(window, 'location', {
value: originalLocation,
writable: true,
});
locationSpy.mockRestore();
});
test('shows loading spinner when dataset parameter is present in URL', async () => {
@@ -329,11 +318,10 @@ test('shows loading spinner when dataset parameter is present in URL', async ()
})),
);
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: { ...originalLocation, search: '?dataset=flights' },
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?dataset=flights',
} as Location);
render(
<ChartCreation
@@ -356,10 +344,7 @@ test('shows loading spinner when dataset parameter is present in URL', async ()
expect(screen.queryByRole('status')).not.toBeInTheDocument();
});
Object.defineProperty(window, 'location', {
value: originalLocation,
writable: true,
});
locationSpy.mockRestore();
});
test('shows only exact match when loading dataset from URL, not partial matches', async () => {
@@ -406,19 +391,15 @@ test('shows only exact match when loading dataset from URL, not partial matches'
};
});
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: { ...originalLocation, search: '?dataset=flights' },
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?dataset=flights',
} as Location);
await renderComponent();
await screen.findByText('flights');
expect(screen.queryByText('flights_delayed')).not.toBeInTheDocument();
Object.defineProperty(window, 'location', {
value: originalLocation,
writable: true,
});
locationSpy.mockRestore();
});

View File

@@ -57,28 +57,28 @@ test('isAllowedScheme allows relative URLs (unparseable as absolute)', () => {
});
test('getTargetUrl reads the url query parameter', () => {
Object.defineProperty(window, 'location', {
value: { search: '?url=https%3A%2F%2Fexample.com%2Fpage' },
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
search: '?url=https%3A%2F%2Fexample.com%2Fpage',
} as Location);
expect(getTargetUrl()).toBe('https://example.com/page');
locationSpy.mockRestore();
});
test('getTargetUrl returns empty string when url param is missing', () => {
Object.defineProperty(window, 'location', {
value: { search: '' },
writable: true,
});
const locationSpy = jest
.spyOn(window, 'location', 'get')
.mockReturnValue({ search: '' } as Location);
expect(getTargetUrl()).toBe('');
locationSpy.mockRestore();
});
test('getTargetUrl does not double-decode percent-encoded values', () => {
// %253A is the double-encoding of ":" — after one decode it should remain %3A
Object.defineProperty(window, 'location', {
value: { search: '?url=javascript%253Aalert(1)' },
writable: true,
});
const locationSpy = jest
.spyOn(window, 'location', 'get')
.mockReturnValue({ search: '?url=javascript%253Aalert(1)' } as Location);
expect(getTargetUrl()).toBe('javascript%3Aalert(1)');
locationSpy.mockRestore();
});
test('trustUrl stores and isUrlTrusted retrieves a URL', () => {

View File

@@ -21,18 +21,21 @@ import { availableDomains, allowCrossDomain } from './hostNamesConfig';
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('hostNamesConfig', () => {
let locationSpy: jest.SpyInstance;
beforeEach(() => {
// Reset DOM
document.body.innerHTML = '';
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
hostname: 'localhost',
search: '',
},
writable: true,
});
locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
hostname: 'localhost',
search: '',
} as Location);
});
afterEach(() => {
locationSpy.mockRestore();
});
test('should export availableDomains as array of strings', () => {

View File

@@ -104,15 +104,10 @@ test('toQueryString should handle special characters in keys and values', () =>
});
test('getDashboardUrlParams should exclude edit parameter by default', () => {
// Mock window.location.search to include edit parameter
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: {
...originalLocation,
search: '?edit=true&standalone=false&expand_filters=1',
},
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?edit=true&standalone=false&expand_filters=1',
} as Location);
const urlParams = getDashboardUrlParams(['edit']);
const paramNames = urlParams.map(([key]) => key);
@@ -121,20 +116,14 @@ test('getDashboardUrlParams should exclude edit parameter by default', () => {
expect(paramNames).toContain('standalone');
expect(paramNames).toContain('expand_filters');
// Restore original location
window.location = originalLocation;
locationSpy.mockRestore();
});
test('getDashboardUrlParams should exclude multiple parameters when provided', () => {
// Mock window.location.search with multiple parameters
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: {
...originalLocation,
search: '?edit=true&standalone=false&debug=true&test=value',
},
writable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?edit=true&standalone=false&debug=true&test=value',
} as Location);
const urlParams = getDashboardUrlParams(['edit', 'debug']);
const paramNames = urlParams.map(([key]) => key);
@@ -144,36 +133,27 @@ test('getDashboardUrlParams should exclude multiple parameters when provided', (
expect(paramNames).toContain('standalone');
expect(paramNames).toContain('test');
// Restore original location
window.location = originalLocation;
locationSpy.mockRestore();
});
test('getUrlParam reads from window.location.search by default', () => {
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: { ...originalLocation, search: '?dashboard_page_id=from-window' },
writable: true,
configurable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '?dashboard_page_id=from-window',
} as Location);
expect(getUrlParam(URL_PARAMS.dashboardPageId)).toBe('from-window');
Object.defineProperty(window, 'location', {
value: originalLocation,
writable: true,
configurable: true,
});
locationSpy.mockRestore();
});
test('getUrlParam uses provided search string instead of window.location.search (Safari race condition fix)', () => {
// Simulate Safari race condition: window.location.search is stale (empty),
// but the correct search string is passed in from React Router's useLocation()
const originalLocation = window.location;
Object.defineProperty(window, 'location', {
value: { ...originalLocation, search: '' },
writable: true,
configurable: true,
});
const locationSpy = jest.spyOn(window, 'location', 'get').mockReturnValue({
...window.location,
search: '',
} as Location);
// Without the search override, window.location.search is stale — returns null (the bug)
expect(getUrlParam(URL_PARAMS.dashboardPageId)).toBeNull();
@@ -183,9 +163,5 @@ test('getUrlParam uses provided search string instead of window.location.search
getUrlParam(URL_PARAMS.dashboardPageId, '?dashboard_page_id=correct-id'),
).toBe('correct-id');
Object.defineProperty(window, 'location', {
value: originalLocation,
writable: true,
configurable: true,
});
locationSpy.mockRestore();
});

View File

@@ -1478,46 +1478,3 @@ def test_success_state_report_sends_and_logs_success(
ReportState.SUCCESS,
error_message=None,
)
def test_get_url_for_csv_uses_post_processed_type(
app: SupersetApp,
mocker: MockerFixture,
) -> None:
"""Regression for #25538: when an alert/report generates a CSV for a
chart, the URL must request type=POST_PROCESSED so the chart's saved
filters (including time-range filters) are applied. The original report
described a chart with a "last 30 days" filter that returned only 14
rows in the UI, but the alert CSV came back with 219 rows (the entire
unfiltered table).
POST_PROCESSED is the marker that propagates the chart's query_context
-- including its time filter -- through to the CSV renderer. A
regression that switched to FULL or RAW would replicate the original
bug.
"""
from datetime import datetime
from uuid import UUID
from superset.commands.report.execute import BaseReportState
from superset.common.chart_data import ChartDataResultFormat
app.config.update({"ALERT_REPORTS_EXECUTORS": {}})
report_schedule = create_report_schedule(mocker)
report_schedule.force_screenshot = False
report_schedule.chart_id = report_schedule.chart.id
state = BaseReportState(
report_schedule=report_schedule,
scheduled_dttm=datetime.now(),
execution_id=UUID("084e7ee6-5557-4ecd-9632-b7f39c9ec524"),
)
url = state._get_url(result_format=ChartDataResultFormat.CSV)
assert "format=csv" in url.lower(), f"expected csv format in URL: {url}"
assert "type=post_processed" in url.lower(), (
f"CSV report URL must use type=post_processed so chart filters "
f"(incl. time filters) are applied; got: {url}; see issue #25538"
)