mirror of
https://github.com/apache/superset.git
synced 2026-07-25 08:02:28 +00:00
Compare commits
7 Commits
chore/impo
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58ce186d09 | ||
|
|
da7730cba0 | ||
|
|
8c37d627aa | ||
|
|
5775bdbe5e | ||
|
|
c65bedf2d7 | ||
|
|
2a07f7967d | ||
|
|
b82ff27f9c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -57,6 +57,7 @@ local_config.py
|
||||
/superset_text.yml
|
||||
superset.egg-info/
|
||||
superset/bin/supersetc
|
||||
superset/extensions/.reload_trigger
|
||||
tmp
|
||||
rat-results.txt
|
||||
superset/app/
|
||||
|
||||
@@ -34,6 +34,7 @@ x-superset-volumes: &superset-volumes
|
||||
- superset_home:/app/superset_home
|
||||
- ./tests:/app/tests
|
||||
- superset_data:/app/data
|
||||
- ./local_extensions:/app/local_extensions
|
||||
x-common-build: &common-build
|
||||
context: .
|
||||
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
|
||||
|
||||
@@ -98,7 +98,9 @@ case "${1}" in
|
||||
echo " 🔒 Werkzeug debugger disabled (set SUPERSET_DEBUG_ENABLED=true to enable)"
|
||||
fi
|
||||
|
||||
flask run -p $PORT --reload $DEBUGGER_FLAG --host=0.0.0.0 --exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*"
|
||||
flask run -p $PORT --reload $DEBUGGER_FLAG --host=0.0.0.0 \
|
||||
--extra-files "/app/superset/extensions/.reload_trigger" \
|
||||
--exclude-patterns "*/node_modules/*:*/.venv/*:*/build/*:*/__pycache__/*:*/superset-frontend/*:*/superset/__init__.py"
|
||||
;;
|
||||
app-gunicorn)
|
||||
echo "Starting web app..."
|
||||
|
||||
@@ -52,7 +52,7 @@ dependencies = [
|
||||
"cron-descriptor",
|
||||
"cryptography>=49.0.0, <50.0.0",
|
||||
"deprecation>=2.1.0, <2.2.0",
|
||||
"flask>=2.2.5, <4.0.0",
|
||||
"flask>=3.1.3, <4.0.0",
|
||||
"flask-appbuilder>=5.2.2, <6.0.0",
|
||||
"flask-caching>=2.1.0, <3",
|
||||
"flask-compress>=1.13, <2.0",
|
||||
|
||||
@@ -109,7 +109,7 @@ et-xmlfile==2.0.0
|
||||
# via openpyxl
|
||||
filelock==3.20.3
|
||||
# via -r requirements/base.in
|
||||
flask==2.3.3
|
||||
flask==3.1.3
|
||||
# via
|
||||
# apache-superset (pyproject.toml)
|
||||
# flask-appbuilder
|
||||
@@ -224,6 +224,7 @@ markdown-it-py==3.0.0
|
||||
# via rich
|
||||
markupsafe==3.0.2
|
||||
# via
|
||||
# flask
|
||||
# jinja2
|
||||
# mako
|
||||
# werkzeug
|
||||
|
||||
@@ -250,7 +250,7 @@ filelock==3.20.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# virtualenv
|
||||
flask==2.3.3
|
||||
flask==3.1.3
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# apache-superset
|
||||
@@ -531,6 +531,7 @@ markdown-it-py==3.0.0
|
||||
markupsafe==3.0.2
|
||||
# via
|
||||
# -c requirements/base-constraint.txt
|
||||
# flask
|
||||
# jinja2
|
||||
# mako
|
||||
# werkzeug
|
||||
|
||||
14
superset-embedded-sdk/package-lock.json
generated
14
superset-embedded-sdk/package-lock.json
generated
@@ -3937,9 +3937,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4114,9 +4114,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"version": "8.5.23",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
|
||||
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4134,7 +4134,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.12",
|
||||
"nanoid": "^3.3.16",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
|
||||
6583
superset-frontend/cypress-base/package-lock.json
generated
6583
superset-frontend/cypress-base/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
146
superset-frontend/package-lock.json
generated
146
superset-frontend/package-lock.json
generated
@@ -3967,16 +3967,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/minimatch": {
|
||||
@@ -5233,9 +5233,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/reporters/node_modules/brace-expansion": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6871,16 +6871,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/arborist/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/arborist/node_modules/lru-cache": {
|
||||
@@ -7137,16 +7137,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/map-workspaces/node_modules/glob": {
|
||||
@@ -7297,16 +7297,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/package-json/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/package-json/node_modules/glob": {
|
||||
@@ -7559,16 +7559,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nx/devkit/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@nx/devkit/node_modules/minimatch": {
|
||||
@@ -11561,16 +11561,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tufjs/models/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@tufjs/models/node_modules/minimatch": {
|
||||
@@ -15756,9 +15756,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.5",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
|
||||
"integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
|
||||
"version": "1.20.6",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz",
|
||||
"integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -15872,9 +15872,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
|
||||
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
|
||||
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -16142,16 +16142,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cacache/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/cacache/node_modules/glob": {
|
||||
@@ -20280,16 +20280,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/eslint-scope": {
|
||||
@@ -21037,9 +21037,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -24068,16 +24068,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ignore-walk/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore-walk/node_modules/minimatch": {
|
||||
@@ -25615,9 +25615,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest-config/node_modules/brace-expansion": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -26552,9 +26552,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jest-runtime/node_modules/brace-expansion": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -29496,15 +29496,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mem-fs-editor/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/mem-fs-editor/node_modules/ejs": {
|
||||
@@ -30908,15 +30908,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/multimatch/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/multimatch/node_modules/minimatch": {
|
||||
@@ -31590,16 +31590,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nx/node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
|
||||
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/nx/node_modules/ignore": {
|
||||
@@ -39719,9 +39719,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.16",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
|
||||
"integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
|
||||
"version": "7.5.21",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz",
|
||||
"integrity": "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
@@ -42252,9 +42252,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server": {
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.5.tgz",
|
||||
"integrity": "sha512-4wZtCquSuv9CKX8oybo+mqxtxZqWz47uM1Ch94lxowBztOhWCbhqvRbfC/mODOwxgV2brY+JGZpHq58/SuVFYg==",
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz",
|
||||
"integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -42276,7 +42276,7 @@
|
||||
"graceful-fs": "^4.2.6",
|
||||
"http-proxy-middleware": "^2.0.9",
|
||||
"ipaddr.js": "^2.1.0",
|
||||
"launch-editor": "^2.6.1",
|
||||
"launch-editor": "^2.14.1",
|
||||
"open": "^10.0.3",
|
||||
"p-retry": "^6.2.0",
|
||||
"schema-utils": "^4.2.0",
|
||||
|
||||
@@ -355,6 +355,35 @@ describe('ResultSet', () => {
|
||||
expect(errorMessages).toHaveLength(errors.length);
|
||||
});
|
||||
|
||||
test('deduplicates errors present in both query.errors and query.extra.errors', async () => {
|
||||
// The async fallback path (QueryAutoRefresh) populates both
|
||||
// `query.errors` (via queryFailed) and `query.extra.errors` (via
|
||||
// refreshQueries) with the same errors. ResultSet should render each
|
||||
// error only once, not twice.
|
||||
const duplicatedErrorQuery = {
|
||||
...failedQueryWithErrors,
|
||||
extra: { errors: failedQueryWithErrors.errors },
|
||||
};
|
||||
const duplicatedErrorState = {
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
...initialState.sqlLab,
|
||||
queries: {
|
||||
[duplicatedErrorQuery.id]: duplicatedErrorQuery,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await waitFor(() => {
|
||||
setup(
|
||||
{ ...mockedProps, queryId: duplicatedErrorQuery.id },
|
||||
mockStore(duplicatedErrorState),
|
||||
);
|
||||
});
|
||||
const errorMessages = screen.getAllByTestId('error-message');
|
||||
expect(errorMessages).toHaveLength(failedQueryWithErrors.errors.length);
|
||||
});
|
||||
|
||||
test('should render a timeout error with a retrial button', async () => {
|
||||
await waitFor(() => {
|
||||
setup(
|
||||
|
||||
@@ -618,7 +618,26 @@ const ResultSet = ({
|
||||
}
|
||||
|
||||
if (query.state === QueryState.Failed) {
|
||||
const errors = [...(query.extra?.errors || []), ...(query.errors || [])];
|
||||
// A failed query can carry the same error in both `query.errors` (set by
|
||||
// the queryFailed action) and `query.extra.errors` (set by the async
|
||||
// refresh). When falling back to async polling both get populated with
|
||||
// identical errors, so deduplicate to avoid rendering each error twice.
|
||||
const seenErrors = new Set<string>();
|
||||
const errors = [
|
||||
...(query.extra?.errors || []),
|
||||
...(query.errors || []),
|
||||
].filter(error => {
|
||||
// json-bigint parsing can leave native BigInt values in payloads,
|
||||
// which JSON.stringify rejects; serialize them as strings.
|
||||
const key = JSON.stringify(error, (_, value) =>
|
||||
typeof value === 'bigint' ? value.toString() : value,
|
||||
);
|
||||
if (seenErrors.has(key)) {
|
||||
return false;
|
||||
}
|
||||
seenErrors.add(key);
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<ResultlessStyles>
|
||||
|
||||
@@ -119,9 +119,10 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
|
||||
filter_select_enabled: datasource.filter_select_enabled,
|
||||
fetch_values_predicate: datasource.fetch_values_predicate,
|
||||
schema:
|
||||
datasource.tableSelector?.schema ||
|
||||
datasource.databaseSelector?.schema ||
|
||||
datasource.schema,
|
||||
datasource.tableSelector?.schema ??
|
||||
datasource.databaseSelector?.schema ??
|
||||
datasource.schema ??
|
||||
null,
|
||||
description: datasource.description,
|
||||
main_dttm_col: datasource.main_dttm_col,
|
||||
currency_code_column: datasource.currency_code_column ?? null,
|
||||
|
||||
@@ -169,7 +169,7 @@ class ExtensionsRestApi(BaseApi):
|
||||
|
||||
@protect()
|
||||
@safe
|
||||
@expose("/<publisher>/<name>/<file>", methods=("GET",))
|
||||
@expose("/<publisher>/<name>/<path:file>", methods=("GET",))
|
||||
def content(self, publisher: str, name: str, file: str) -> Response:
|
||||
"""Get a frontend chunk of an extension.
|
||||
---
|
||||
|
||||
@@ -23,9 +23,11 @@ from typing import Callable, Iterable, TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment
|
||||
|
||||
# Matches only the static asset endpoint: /api/v1/extensions/<publisher>/<name>/<file>
|
||||
# Matches only the static asset endpoint:
|
||||
# /api/v1/extensions/<publisher>/<name>/<path:file>, where the file portion may
|
||||
# contain nested segments (worker / WASM / chunk subfolders).
|
||||
# Does not match the list (/), get (/<publisher>/<name>), or info (/_info) endpoints.
|
||||
_ASSET_PATH_RE = re.compile(r"^/api/v1/extensions/[^/]+/[^/]+/[^/]+$")
|
||||
_ASSET_PATH_RE: re.Pattern[str] = re.compile(r"^/api/v1/extensions/[^/]+/[^/]+/.+$")
|
||||
|
||||
|
||||
class ExtensionCacheMiddleware:
|
||||
|
||||
@@ -21,7 +21,6 @@ from __future__ import annotations
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -29,37 +28,207 @@ from flask import Flask
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Sentinel file Flask watches via --extra-files. Touching it on a real change
|
||||
# triggers a server reload without depending on cwd or the location of any
|
||||
# Python source file.
|
||||
RELOAD_TRIGGER: Path = Path(__file__).resolve().parent / ".reload_trigger"
|
||||
|
||||
# Guard to prevent multiple initializations
|
||||
_watcher_initialized = False
|
||||
_watcher_lock = threading.Lock()
|
||||
|
||||
|
||||
def _get_file_handler_class() -> Any:
|
||||
def _get_file_handler_class() -> Any: # noqa: C901
|
||||
"""Get the file handler class, importing watchdog only when needed."""
|
||||
try:
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
import hashlib
|
||||
|
||||
from watchdog.events import (
|
||||
FileCreatedEvent,
|
||||
FileDeletedEvent,
|
||||
FileModifiedEvent,
|
||||
FileMovedEvent,
|
||||
FileSystemEventHandler,
|
||||
)
|
||||
|
||||
class LocalExtensionFileHandler(FileSystemEventHandler):
|
||||
"""Custom file system event handler for LOCAL_EXTENSIONS directories."""
|
||||
"""Custom file system event handler for LOCAL_EXTENSIONS directories.
|
||||
|
||||
Only reacts to genuine content changes (create / modify / move) in the
|
||||
dist directory, verified by comparing a SHA-256 of the file's content.
|
||||
This avoids the Docker VirtioFS / osxfs problem where reading a file
|
||||
generates inotify events that watchdog surfaces as modifications.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
# sha256 of last-seen content, keyed by absolute path. Populated
|
||||
# from existing files in watched `dist` dirs at startup (see
|
||||
# `prime_baseline`) so that startup-noise inotify events from
|
||||
# Docker VirtioFS reads don't get treated as the first real edit.
|
||||
self._file_hashes: dict[str, str] = {}
|
||||
self._lock: threading.Lock = threading.Lock()
|
||||
# Trailing debounce: schedule a single reload after a quiet
|
||||
# window so simultaneous webpack writes coalesce into one
|
||||
# restart that fires *after* the build settles.
|
||||
self._debounce_seconds: float = 1.0
|
||||
self._pending_timer: threading.Timer | None = None
|
||||
# Monotonically increasing token identifying the most recently
|
||||
# scheduled timer. Guards the timer-already-fired race where
|
||||
# `Timer.cancel()` can't stop a callback that has begun running.
|
||||
self._reload_generation: int = 0
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────
|
||||
|
||||
@staticmethod
|
||||
def _sha256(path: str) -> str | None:
|
||||
try:
|
||||
with open(path, "rb") as fh:
|
||||
return hashlib.sha256(fh.read()).hexdigest()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
def prime_baseline(self, watch_dirs: set[str]) -> None:
|
||||
"""Pre-populate content hashes for existing files in watched
|
||||
`dist` directories. Called once at watcher startup so a
|
||||
developer's first real edit registers as a content change
|
||||
rather than as the file's 'first observation'."""
|
||||
for root_dir in watch_dirs:
|
||||
root = Path(root_dir)
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if "dist" not in path.parts:
|
||||
continue
|
||||
digest = self._sha256(str(path))
|
||||
if digest is not None:
|
||||
self._file_hashes[str(path)] = digest
|
||||
|
||||
def _content_changed(self, path: str) -> bool:
|
||||
"""Return True when the file's content differs from last seen.
|
||||
|
||||
With `prime_baseline` called at startup, the baseline reflects
|
||||
what was on disk when the watcher started. A first observation
|
||||
that differs (or doesn't exist in baseline) is treated as a
|
||||
genuine change.
|
||||
"""
|
||||
digest = self._sha256(path)
|
||||
if digest is None:
|
||||
return False
|
||||
old_digest = self._file_hashes.get(path)
|
||||
self._file_hashes[path] = digest
|
||||
# New file (not in baseline) is a real change; otherwise compare.
|
||||
return old_digest != digest
|
||||
|
||||
def _trigger_reload(self, source_path: str, generation: int) -> None:
|
||||
"""Touch the reload-trigger sentinel; Flask's --extra-files
|
||||
watcher reloads on its mtime change."""
|
||||
# A newer event may have superseded this timer after it began
|
||||
# running (`cancel()` can't stop an in-flight callback), so only
|
||||
# the most recently scheduled generation is allowed to fire. The
|
||||
# check and the sentinel touch happen inside the same critical
|
||||
# section so a `_schedule_reload` call racing in from another
|
||||
# thread can't bump the generation between the check and the
|
||||
# write and let this stale callback still fire.
|
||||
with self._lock:
|
||||
if generation != self._reload_generation:
|
||||
return
|
||||
logger.info(
|
||||
"File change settled in LOCAL_EXTENSIONS: %s", source_path
|
||||
)
|
||||
logger.info("Triggering restart by touching %s", RELOAD_TRIGGER)
|
||||
try:
|
||||
RELOAD_TRIGGER.touch()
|
||||
except OSError as e:
|
||||
logger.warning(
|
||||
"Failed to touch reload trigger %s: %s", RELOAD_TRIGGER, e
|
||||
)
|
||||
|
||||
def _schedule_reload(self, source_path: str) -> None:
|
||||
"""Trailing-debounce: cancel any pending reload and schedule a
|
||||
new one for `_debounce_seconds` from now. Each new event resets
|
||||
the timer, so the reload fires only after a quiet window."""
|
||||
with self._lock:
|
||||
if self._pending_timer is not None:
|
||||
self._pending_timer.cancel()
|
||||
self._reload_generation += 1
|
||||
timer = threading.Timer(
|
||||
self._debounce_seconds,
|
||||
self._trigger_reload,
|
||||
args=(source_path, self._reload_generation),
|
||||
)
|
||||
timer.daemon = True
|
||||
self._pending_timer = timer
|
||||
timer.start()
|
||||
|
||||
def _handle_moved(self, event: Any) -> None:
|
||||
"""Moves into/out of `dist` are explicit signals — trigger
|
||||
regardless of content match (the source may already be gone
|
||||
or the destination may not have a meaningful hash yet).
|
||||
Atomic-build workflows rename tmp -> dist (dest in dist),
|
||||
while removing an artifact renames dist -> elsewhere (src in
|
||||
dist); either side touching `dist` is a real signal."""
|
||||
dest = getattr(event, "dest_path", None)
|
||||
src = getattr(event, "src_path", None)
|
||||
# The file no longer lives at the source path; evict its
|
||||
# hash entry so the index only tracks paths that exist.
|
||||
if isinstance(src, str):
|
||||
self._file_hashes.pop(src, None)
|
||||
dist_side = next(
|
||||
(
|
||||
p
|
||||
for p in (dest, src)
|
||||
if isinstance(p, str) and "dist" in Path(p).parts
|
||||
),
|
||||
None,
|
||||
)
|
||||
if dist_side is not None:
|
||||
self._schedule_reload(dist_side)
|
||||
|
||||
# ── event handler ─────────────────────────────────────────────────
|
||||
|
||||
def on_any_event(self, event: Any) -> None:
|
||||
"""Handle any file system event in the watched directories."""
|
||||
"""Handle file system events in the watched directories."""
|
||||
if event.is_directory:
|
||||
return
|
||||
|
||||
# Only trigger on changes to files in `dist` directory
|
||||
src = getattr(event, "src_path", None)
|
||||
if not isinstance(src, str) or "dist" not in Path(src).parts:
|
||||
# Deletions don't trigger a reload (webpack clean steps delete
|
||||
# old chunks right before writing new ones, which trigger via
|
||||
# the subsequent create/modify), but the stale hash entry must
|
||||
# be evicted so `_file_hashes` doesn't grow without bound as
|
||||
# hashed chunk filenames churn across rebuilds.
|
||||
if isinstance(event, FileDeletedEvent):
|
||||
src = getattr(event, "src_path", None)
|
||||
if isinstance(src, str):
|
||||
self._file_hashes.pop(src, None)
|
||||
return
|
||||
|
||||
logger.info(
|
||||
"File change detected in LOCAL_EXTENSIONS: %s", event.src_path
|
||||
)
|
||||
# Only react to true write events; skip access / close / open etc.
|
||||
if not isinstance(
|
||||
event, (FileCreatedEvent, FileModifiedEvent, FileMovedEvent)
|
||||
):
|
||||
return
|
||||
|
||||
# Touch superset/__init__.py to trigger Flask's file watcher
|
||||
superset_init = Path("superset/__init__.py")
|
||||
logger.info("Triggering restart by touching %s", superset_init)
|
||||
os.utime(superset_init, (time.time(), time.time()))
|
||||
if isinstance(event, FileMovedEvent):
|
||||
self._handle_moved(event)
|
||||
return
|
||||
|
||||
# For Create/Modify events watchdog only sets src_path.
|
||||
target = getattr(event, "src_path", None)
|
||||
if not isinstance(target, str):
|
||||
return
|
||||
|
||||
# Only care about paths inside a `dist` directory.
|
||||
if "dist" not in Path(target).parts:
|
||||
return
|
||||
|
||||
# For Create/Modify, verify the content actually changed to
|
||||
# ignore spurious inotify events generated by Docker bind-mount
|
||||
# reads.
|
||||
if not self._content_changed(target):
|
||||
return
|
||||
|
||||
self._schedule_reload(target)
|
||||
|
||||
return LocalExtensionFileHandler
|
||||
except ImportError:
|
||||
@@ -130,11 +299,23 @@ def setup_local_extensions_watcher(app: Flask) -> None: # noqa: C901
|
||||
if not watch_dirs:
|
||||
return
|
||||
|
||||
# Ensure the sentinel exists so touch() and Flask's --extra-files watcher
|
||||
# both have a real path to operate on.
|
||||
try:
|
||||
RELOAD_TRIGGER.touch(exist_ok=True)
|
||||
except OSError as e:
|
||||
logger.warning("Could not create reload trigger %s: %s", RELOAD_TRIGGER, e)
|
||||
return
|
||||
|
||||
try:
|
||||
from watchdog.observers import Observer
|
||||
|
||||
# Set up and start the file watcher
|
||||
event_handler = handler_class()
|
||||
# Pre-populate baseline hashes from existing dist files so the
|
||||
# developer's first real edit isn't silently dropped as a "first
|
||||
# observation".
|
||||
event_handler.prime_baseline(watch_dirs)
|
||||
observer = Observer()
|
||||
|
||||
for watch_dir in watch_dirs:
|
||||
|
||||
@@ -35,12 +35,17 @@ from superset.utils.core import check_is_safe_zip
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
FRONTEND_REGEX = re.compile(r"^frontend/dist/([^/]+)$")
|
||||
# Accept nested paths inside frontend/dist so extensions can serve
|
||||
# worker / WASM / chunk subfolders. Reject any entry whose path contains "..",
|
||||
# conservatively excluding parent traversal segments so a crafted entry name
|
||||
# cannot escape the bundle directory (defense in depth; check_is_safe_zip runs
|
||||
# first).
|
||||
FRONTEND_REGEX: re.Pattern[str] = re.compile(r"^frontend/dist/(?!.*\.\.)(.+)$")
|
||||
# Reject any entry whose path contains "..", conservatively excluding parent
|
||||
# traversal segments along with the (in practice nonexistent) case of a module
|
||||
# path embedding consecutive dots, so a crafted entry name cannot produce a
|
||||
# traversal-style module path (defense in depth; check_is_safe_zip runs first).
|
||||
BACKEND_REGEX = re.compile(r"^backend/src/(?!.*\.\.)(.+)$")
|
||||
BACKEND_REGEX: re.Pattern[str] = re.compile(r"^backend/src/(?!.*\.\.)(.+)$")
|
||||
|
||||
|
||||
class InMemoryLoader(importlib.abc.Loader):
|
||||
|
||||
@@ -64,6 +64,21 @@ def test_asset_path_is_intercepted() -> None:
|
||||
assert "Cookie" not in vary
|
||||
|
||||
|
||||
def test_nested_asset_path_is_intercepted() -> None:
|
||||
headers: ResponseHeaders = call_middleware(
|
||||
"/api/v1/extensions/acme/my-ext/workers/nested/chunk.wasm",
|
||||
[("Vary", "Accept-Encoding, Cookie")],
|
||||
)
|
||||
vary: str = dict(headers).get("Vary", "")
|
||||
assert "Cookie" not in vary
|
||||
|
||||
|
||||
def test_get_endpoint_with_trailing_slash_is_not_intercepted() -> None:
|
||||
upstream: ResponseHeaders = [("Vary", "Accept-Encoding, Cookie")]
|
||||
headers = call_middleware("/api/v1/extensions/acme/my-ext/", upstream)
|
||||
assert headers == upstream
|
||||
|
||||
|
||||
def test_list_endpoint_is_not_intercepted() -> None:
|
||||
upstream = [("Vary", "Accept-Encoding, Cookie")]
|
||||
headers = call_middleware("/api/v1/extensions/", upstream)
|
||||
|
||||
59
tests/unit_tests/extensions/test_local_extensions_watcher.py
Normal file
59
tests/unit_tests/extensions/test_local_extensions_watcher.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from typing import Any
|
||||
|
||||
from watchdog.events import FileDeletedEvent, FileMovedEvent
|
||||
|
||||
from superset.extensions.local_extensions_watcher import _get_file_handler_class
|
||||
|
||||
|
||||
def _noop_schedule_reload(_path: str) -> None:
|
||||
"""Stand in for a real debounce timer in unit tests."""
|
||||
|
||||
|
||||
def make_handler() -> Any:
|
||||
handler_class = _get_file_handler_class()
|
||||
handler = handler_class()
|
||||
# Avoid spinning up real debounce timers in unit tests.
|
||||
handler._schedule_reload = _noop_schedule_reload
|
||||
return handler
|
||||
|
||||
|
||||
def test_delete_evicts_hash_entry() -> None:
|
||||
handler = make_handler()
|
||||
handler._file_hashes["/ext/dist/old-chunk.abc123.js"] = "digest"
|
||||
|
||||
handler.on_any_event(FileDeletedEvent("/ext/dist/old-chunk.abc123.js"))
|
||||
|
||||
assert "/ext/dist/old-chunk.abc123.js" not in handler._file_hashes
|
||||
|
||||
|
||||
def test_delete_of_untracked_path_is_a_noop() -> None:
|
||||
handler = make_handler()
|
||||
|
||||
handler.on_any_event(FileDeletedEvent("/ext/dist/never-seen.js"))
|
||||
|
||||
assert handler._file_hashes == {}
|
||||
|
||||
|
||||
def test_move_out_of_dist_evicts_source_hash_entry() -> None:
|
||||
handler = make_handler()
|
||||
handler._file_hashes["/ext/dist/chunk.js"] = "digest"
|
||||
|
||||
handler.on_any_event(FileMovedEvent("/ext/dist/chunk.js", "/ext/tmp/chunk.js"))
|
||||
|
||||
assert "/ext/dist/chunk.js" not in handler._file_hashes
|
||||
Reference in New Issue
Block a user